Merge remote-tracking branch 'upstream/master' into android_goto_splash

This commit is contained in:
Gabriel Calero 2018-05-02 14:38:10 -03:00
commit c15ef56d90
202 changed files with 6466 additions and 1826 deletions

View file

@ -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.

View file

@ -63,7 +63,7 @@ struct AssetMeta {
AssetMeta() {
}
BakeVersion bakeVersion;
BakeVersion bakeVersion { INITIAL_BAKE_VERSION };
bool failedLastBake { false };
QString lastBakeErrors;
};

View file

@ -115,11 +115,7 @@ public:
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar];
lastOtherAvatarSentJoints.resize(_avatar->getJointCount());
return lastOtherAvatarSentJoints;
}
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
int processPackets(); // returns number of packets processed

View file

@ -381,6 +381,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
bool includeThisAvatar = true;
auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID());
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
lastSentJointsForOther.resize(otherAvatar->getJointCount());
bool distanceAdjust = true;
glm::vec3 viewerPosition = myPosition;
AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray

View file

@ -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);
});
}
}

View file

@ -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")

View file

@ -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 ""

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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.
/*

View file

@ -254,7 +254,7 @@ ModalWindow {
text: root.warning;
wrapMode: Text.WordWrap;
font.italic: true;
maximumLineCount: 2;
maximumLineCount: 3;
}
HiFiGlyphs {

View file

@ -254,7 +254,7 @@ ModalWindow {
text: root.warning;
wrapMode: Text.WordWrap;
font.italic: true;
maximumLineCount: 2;
maximumLineCount: 3;
}
HiFiGlyphs {

View file

@ -282,7 +282,7 @@ TabletModalWindow {
text: root.warning;
wrapMode: Text.WordWrap;
font.italic: true;
maximumLineCount: 2;
maximumLineCount: 3;
}
HiFiGlyphs {

View file

@ -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();
}

View file

@ -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
}
}
}
}

View file

@ -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 {

View file

@ -239,7 +239,6 @@ Item {
width: 62;
onLoaded: {
item.enabled = (root.purchaseStatus === "confirmed");
item.buttonGlyphText = hifi.glyphs.gift;
item.buttonText = "Gift";
item.buttonClicked = function() {

View file

@ -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()
//

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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
}

View file

@ -145,6 +145,16 @@
#include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h>
#include <AnimationLogging.h>
#include <AvatarLogging.h>
#include <ScriptEngineLogging.h>
#include <ModelFormatLogging.h>
#include <controllers/Logging.h>
#include <NetworkLogging.h>
#include <shared/StorageLogging.h>
#include <ScriptEngineLogging.h>
#include <ui/Logging.h>
#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<DialogsManager>();
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<AddressManager>().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<SoundCache>()->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<qint64>(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<QString, QString> 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<bool> 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>();
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();
}
@ -2166,6 +2208,46 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
}
void Application::updateVerboseLogging() {
bool enable = Menu::getInstance()->isOptionChecked(MenuOption::VerboseLogging);
const_cast<QLoggingCategory*>(&animation())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&animation())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&avatars())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&avatars())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&scriptengine())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&scriptengine())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&modelformat())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&modelformat())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&controllers())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&controllers())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&resourceLog())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&resourceLog())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&asset_client())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&asset_client())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&messages_client())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&messages_client())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&storagelogging())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&storagelogging())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&uiLogging())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&uiLogging())->setEnabled(QtInfoMsg, enable);
const_cast<QLoggingCategory*>(&glLogging())->setEnabled(QtDebugMsg, enable);
const_cast<QLoggingCategory*>(&glLogging())->setEnabled(QtInfoMsg, enable);
}
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
DomainHandler::ConnectionRefusedReason reasonCode = static_cast<DomainHandler::ConnectionRefusedReason>(reasonCodeInt);
@ -3037,7 +3119,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
PROFILE_RANGE(render, __FUNCTION__);
bool sandboxIsRunning = SandboxUtils::readStatus(reply->readAll());
qDebug() << "HandleSandboxStatus" << sandboxIsRunning;
enum HandControllerType {
Vive,
@ -4754,7 +4835,7 @@ void Application::updateLOD(float deltaTime) const {
}
}
void Application::pushPostUpdateLambda(void* key, std::function<void()> func) {
void Application::pushPostUpdateLambda(void* key, const std::function<void()>& func) {
std::unique_lock<std::mutex> guard(_postUpdateLambdasLock);
_postUpdateLambdas[key] = func;
}
@ -7364,7 +7445,7 @@ void Application::windowMinimizedChanged(bool minimized) {
}
}
void Application::postLambdaEvent(std::function<void()> f) {
void Application::postLambdaEvent(const std::function<void()>& f) {
if (this->thread() == QThread::currentThread()) {
f();
} else {
@ -7372,6 +7453,15 @@ void Application::postLambdaEvent(std::function<void()> f) {
}
}
void Application::sendLambdaEvent(const std::function<void()>& 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");

View file

@ -136,7 +136,8 @@ public:
Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted);
~Application();
void postLambdaEvent(std::function<void()> f) override;
void postLambdaEvent(const std::function<void()>& f) override;
void sendLambdaEvent(const std::function<void()>& 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<void()> func) override;
virtual void pushPostUpdateLambda(void* key, const std::function<void()>& func) override;
void updateMyAvatarLookAtPosition();
@ -403,8 +404,10 @@ public slots:
Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); }
void updateVerboseLogging();
Q_INVOKABLE void openAndroidActivity(const QString& activityName);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);
void onDesktopRootContextCreated(QQmlContext* qmlContext);

View file

@ -15,6 +15,11 @@
#include <DependencyManager.h>
#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:

View file

@ -1,4 +1,4 @@
//
//
// Bookmarks.h
// interface/src
//
@ -48,6 +48,9 @@ protected:
bool _isMenuSorted;
protected slots:
/**jsdoc
* @function AvatarBookmarks.deleteBookmark
*/
void deleteBookmark();
private:

View file

@ -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<DialogsManager>()->setDomainConnectionFailureVisibility(false);
}

View file

@ -15,6 +15,8 @@
#if HAS_CRASHPAD
#include <mutex>
#include <QStandardPaths>
#include <QDir>
@ -23,8 +25,8 @@
#include <client/crashpad_client.h>
#include <client/crash_report_database.h>
#include <client/settings.h>
// #include <client/annotation_list.h>
// #include <client/crashpad_info.h>
#include <client/annotation_list.h>
#include <client/crashpad_info.h>
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 <Windows.h>
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<std::mutex> 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

View file

@ -20,6 +20,7 @@
#include <UserActivityLogger.h>
#include <UUID.h>
#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) {

View file

@ -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 <em>Read-only.</em>
* @property {number} engineRunTime <em>Read-only.</em>
* @property {number} gpuTime <em>Read-only.</em>
* @property {number} avgRenderTime <em>Read-only.</em>
* @property {number} fps <em>Read-only.</em>
* @property {number} lodLevel <em>Read-only.</em>
* @property {number} lodDecreaseFPS <em>Read-only.</em>
* @property {number} lodIncreaseFPS <em>Read-only.</em>
*/
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:

View file

@ -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()));

View file

@ -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";

View file

@ -22,6 +22,9 @@
#include <DependencyManager.h>
/**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:

View file

@ -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 <em>Read-only.</em>
* @property {number} scopeOutputLeft <em>Read-only.</em>
* @property {number} scopeOutputRight <em>Read-only.</em>
* @property {number} triggerInput <em>Read-only.</em>
* @property {number} triggerOutputLeft <em>Read-only.</em>
* @property {number} triggerOutputRight <em>Read-only.</em>
*/
Q_PROPERTY(QVector<int> scopeInput READ getScopeInput)
Q_PROPERTY(QVector<int> scopeOutputLeft READ getScopeOutputLeft)
Q_PROPERTY(QVector<int> 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<int> getScopeInput() { return _scopeInputData; };
/**jsdoc
* @function AudioScope.getScopeOutputLeft
* @returns {number[]}
*/
QVector<int> getScopeOutputLeft() { return _scopeOutputLeftData; };
/**jsdoc
* @function AudioScope.getScopeOutputRight
* @returns {number[]}
*/
QVector<int> getScopeOutputRight() { return _scopeOutputRightData; };
/**jsdoc
* @function AudioScope.getTriggerInput
* @returns {number[]}
*/
QVector<int> getTriggerInput() { return _triggerInputData; };
/**jsdoc
* @function AudioScope.getTriggerOutputLeft
* @returns {number[]}
*/
QVector<int> getTriggerOutputLeft() { return _triggerOutputLeftData; };
/**jsdoc
* @function AudioScope.getTriggerOutputRight
* @returns {number[]}
*/
QVector<int> 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:

View file

@ -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<MyAvatar> 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<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& 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:

File diff suppressed because it is too large Load diff

View file

@ -615,9 +615,15 @@ void Wallet::updateImageProvider() {
securityImageProvider->setSecurityImage(_securityImage);
// inform tablet security image provider
QQmlEngine* tabletEngine = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system")->getTabletSurface()->getSurfaceContext()->engine();
securityImageProvider = reinterpret_cast<SecurityImageProvider*>(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME));
securityImageProvider->setSecurityImage(_securityImage);
TabletProxy* tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
if (tablet) {
OffscreenQmlSurface* tabletSurface = tablet->getTabletSurface();
if (tabletSurface) {
QQmlEngine* tabletEngine = tabletSurface->getSurfaceContext()->engine();
securityImageProvider = reinterpret_cast<SecurityImageProvider*>(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME));
securityImageProvider->setSecurityImage(_securityImage);
}
}
}
void Wallet::chooseSecurityImage(const QString& filename) {

View file

@ -26,6 +26,11 @@
#include <trackers/FaceTracker.h>
/**jsdoc
* The FaceTracker API helps manage facial tracking hardware.
* @namespace FaceTracker
*/
class DdeFaceTracker : public FaceTracker, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -57,7 +62,16 @@ public:
void setEyeClosingThreshold(float eyeClosingThreshold);
public slots:
/**jsdoc
* @function FaceTracker.setEnabled
* @param {boolean} enabled
*/
void setEnabled(bool enabled) override;
/**jsdoc
* @function FaceTracker.calibrate
*/
void calibrate();
private slots:

View file

@ -109,7 +109,7 @@ public:
*
* @typedef {Object} Picks.RayPickResult
* @property {number} type The intersection type.
* @property {bool} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
* @property {float} distance The distance to the intersection point from the origin of the ray.
* @property {Vec3} intersection The intersection point in world-space.
@ -123,7 +123,7 @@ public:
*
* @typedef {Object} Picks.StylusPickResult
* @property {number} type The intersection type.
* @property {bool} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
* @property {float} distance The distance to the intersection point from the origin of the ray.
* @property {Vec3} intersection The intersection point in world-space.

View file

@ -34,7 +34,17 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR
class AccountServicesScriptingInterface : public QObject {
Q_OBJECT
/**jsdoc
* The AccountServices API contains helper functions related to user connectivity
*
* @namespace AccountServices
* @property {string} username <em>Read-only.</em>
* @property {boolean} loggedIn <em>Read-only.</em>
* @property {string} findableBy
* @property {string} metaverseServerURL <em>Read-only.</em>
*/
Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged)
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged)
@ -48,11 +58,33 @@ public:
QUrl getMetaverseServerURL() { return DependencyManager::get<AccountManager>()->getMetaverseServerURL(); }
public slots:
/**jsdoc
* @function AccountServices.getDownloadInfo
* @returns {DownloadInfoResult}
*/
DownloadInfoResult getDownloadInfo();
/**jsdoc
* @function AccountServices.updateDownloadInfo
*/
void updateDownloadInfo();
/**jsdoc
* @function AccountServices.isLoggedIn
* @returns {boolean}
*/
bool isLoggedIn();
/**jsdoc
* @function AccountServices.checkAndSignalForAccessToken
* @returns {boolean}
*/
bool checkAndSignalForAccessToken();
/**jsdoc
* @function AccountServices.logOut
*/
void logOut();
private slots:
@ -66,11 +98,46 @@ private slots:
void onUsernameChanged(const QString& username);
signals:
/**jsdoc
* @function AccountServices.connected
* @returns {Signal}
*/
void connected();
/**jsdoc
* @function AccountServices.disconnected
* @param {string} reason
* @returns {Signal}
*/
void disconnected(const QString& reason);
/**jsdoc
* @function AccountServices.myUsernameChanged
* @param {string} username
* @returns {Signal}
*/
void myUsernameChanged(const QString& username);
/**jsdoc
* @function AccountServices.downloadInfoChanged
* @param {} info
* @returns {Signal}
*/
void downloadInfoChanged(DownloadInfoResult info);
/**jsdoc
* @function AccountServices.findableByChanged
* @param {string} discoverabilityMode
* @returns {Signal}
*/
void findableByChanged(const QString& discoverabilityMode);
/**jsdoc
* @function AccountServices.loggedInChanged
* @param {boolean} loggedIn
* @returns {Signal}
*/
void loggedInChanged(bool loggedIn);
private:

View file

@ -147,7 +147,7 @@ void Audio::setInputVolume(float volume) {
}
float Audio::getInputLevel() const {
return resultWithReadLock<bool>([&] {
return resultWithReadLock<float>([&] {
return _inputLevel;
});
}

View file

@ -25,6 +25,18 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_OBJECT
SINGLETON_DEPENDENCY
/**jsdoc
* The Audio API features tools to help control audio contexts and settings.
*
* @namespace Audio
* @property {boolean} muted
* @property {boolean} noiseReduction
* @property {number} inputVolume
* @property {number} inputLevel <em>Read-only.</em>
* @property {string} context <em>Read-only.</em>
* @property {} devices <em>Read-only.</em>
*/
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
@ -49,24 +61,99 @@ public:
void showMicMeter(bool show);
/**jsdoc
* @function Audio.setInputDevice
* @param {} device
* @param {boolean} isHMD
*/
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
Q_INVOKABLE void setReverb(bool enable);
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
/**jsdoc
* @function Audio.setOutputDevice
* @param {} device
* @param {boolean} isHMD
*/
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
/**jsdoc
* @function Audio.setReverb
* @param {boolean} enable
*/
Q_INVOKABLE void setReverb(bool enable);
/**jsdoc
* @function Audio.setReverbOptions
* @param {} options
*/
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
/**jsdoc
* @function Audio.startRecording
* @param {string} filename
* @returns {boolean}
*/
Q_INVOKABLE bool startRecording(const QString& filename);
/**jsdoc
* @function Audio.stopRecording
*/
Q_INVOKABLE void stopRecording();
/**jsdoc
* @function Audio.getRecording
* @returns {boolean}
*/
Q_INVOKABLE bool getRecording();
signals:
/**jsdoc
* @function Audio.nop
* @returns {Signal}
*/
void nop();
/**jsdoc
* @function Audio.mutedChanged
* @param {boolean} isMuted
* @returns {Signal}
*/
void mutedChanged(bool isMuted);
/**jsdoc
* @function Audio.noiseReductionChanged
* @param {boolean} isEnabled
* @returns {Signal}
*/
void noiseReductionChanged(bool isEnabled);
/**jsdoc
* @function Audio.inputVolumeChanged
* @param {number} volume
* @returns {Signal}
*/
void inputVolumeChanged(float volume);
/**jsdoc
* @function Audio.inputLevelChanged
* @param {number} level
* @returns {Signal}
*/
void inputLevelChanged(float level);
/**jsdoc
* @function Audio.contextChanged
* @param {string} context
* @returns {Signal}
*/
void contextChanged(const QString& context);
public slots:
/**jsdoc
* @function Audio.onContextChanged
* @returns {Signal}
*/
void onContextChanged();
private slots:

View file

@ -108,11 +108,9 @@ AudioDeviceList::~AudioDeviceList() {
// store the selected device
foreach(std::shared_ptr<AudioDevice> adevice, _devices) {
if (adevice->selectedDesktop) {
qDebug() << "Saving Desktop for" << _mode << "name" << adevice->info.deviceName();
settingDesktop.set(adevice->info.deviceName());
}
if (adevice->selectedHMD) {
qDebug() << "Saving HMD for" << _mode << "name" << adevice->info.deviceName();
settingHMD.set(adevice->info.deviceName());
}
}
@ -311,7 +309,6 @@ void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
}
}
qDebug() << "adding audio device:" << device.display << device.selectedDesktop << device.selectedHMD << _mode;
newDevices.push_back(newDevice(device));
}

View file

@ -28,7 +28,7 @@ class ScriptEngine;
/**jsdoc
* The Controller API provides facilities to interact with computer and controller hardware.
*
* <h5>Functions:</h5>
* <h5>Functions</h5>
*
* <p>Properties</p>
* <ul>
@ -143,6 +143,61 @@ class ScriptEngine;
* <li>{@link Controller.stopInputPlayback|stopInputPlayback}</li>
* </ul>
*
* <h5>Entity Methods:</h5>
*
* <p>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.</p>
*
* <table>
* <thead>
* <tr><th>Method Name</th><th>Description</th><th>Example</th></tr>
* </thead>
* <tbody>
* <tr>
* <td><code>startFarTrigger</code><br /><code>continueFarTrigger</code><br /><code>stopFarTrigger</code></td>
* <td>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.</td>
* </td>
* <td>A light switch that can be toggled on and off from a distance.</td>
* </tr>
* <tr>
* <td><code>startNearTrigger</code><br /><code>continueNearTrigger</code><br /><code>stopNearTrigger</code></td>
* <td>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.</td>
* <td>A doorbell that can be rung when a user is near.</td>
* </tr>
* <tr>
* <td><code>startDistanceGrab</code><br /><code>continueDistanceGrab</code><br /></td>
* <td>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.</td>
* <td>A comet that emits icy particle trails when a user is dragging it through the sky.</td>
* </tr>
* <tr>
* <td><code>startNearGrab</code><br /><code>continueNearGrab</code><br /></td>
* <td>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.</td>
* <td>A ball that glows when it's being held close.</td>
* </tr>
* <tr>
* <td><code>releaseGrab</code></td>
* <td>This method is called when a user releases the trigger when having been either distance or near grabbing an
* entity.</td>
* <td>Turn off the ball glow or comet trail with the user finishes grabbing it.</td>
* </tr>
* <tr>
* <td><code>startEquip</code><br /><code>continueEquip</code><br /><code>releaseEquip</code></td>
* <td>These methods are called when a user starts, continues, or stops equipping an entity.</td>
* <td>A glass that stays in the user's hand after the trigger is clicked.</td>
* </tr>
* </tbody>
* </table>
* <p>All the entity methods are called with the following two arguments:</p>
* <ul>
* <li>The entity ID.</li>
* <li>A string, <code>"hand,userID"</code> &mdash; where "hand" is <code>"left"</code> or <code>"right"</code>, and "userID"
* is the user's {@link MyAvatar|MyAvatar.sessionUUID}.</li>
* </ul>
*
* @namespace Controller
*
* @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end

View file

@ -15,6 +15,11 @@
#include <QObject>
#include <DependencyManager.h>
/**jsdoc
* The GooglePoly API allows you to interact with Google Poly models direct from inside High Fidelity.
* @namespace GooglePoly
*/
class GooglePolyScriptingInterface : public QObject, public Dependency {
Q_OBJECT
@ -22,15 +27,75 @@ public:
GooglePolyScriptingInterface();
public slots:
/**jsdoc
* @function GooglePoly.setAPIKey
* @param {string} key
*/
void setAPIKey(const QString& key);
/**jsdoc
* @function GooglePoly.getAssetList
* @param {string} keyword
* @param {string} category
* @param {string} format
* @returns {string}
*/
QString getAssetList(const QString& keyword, const QString& category, const QString& format);
/**jsdoc
* @function GooglePoly.getFBX
* @param {string} keyword
* @param {string} category
* @returns {string}
*/
QString getFBX(const QString& keyword, const QString& category);
/**jsdoc
* @function GooglePoly.getOBJ
* @param {string} keyword
* @param {string} category
* @returns {string}
*/
QString getOBJ(const QString& keyword, const QString& category);
QString getBlocks(const QString& keyword, const QString& categoryy);
/**jsdoc
* @function GooglePoly.getBlocks
* @param {string} keyword
* @param {string} category
* @returns {string}
*/
QString getBlocks(const QString& keyword, const QString& category);
/**jsdoc
* @function GooglePoly.getGLTF
* @param {string} keyword
* @param {string} category
* @returns {string}
*/
QString getGLTF(const QString& keyword, const QString& category);
/**jsdoc
* @function GooglePoly.getGLTF2
* @param {string} keyword
* @param {string} category
* @returns {string}
*/
QString getGLTF2(const QString& keyword, const QString& category);
/**jsdoc
* @function GooglePoly.getTilt
* @param {string} keyword
* @param {string} category
* @returns {string}
*/
QString getTilt(const QString& keyword, const QString& category);
/**jsdoc
* @function GooglePoly.getModelInfo
* @param {string} input
* @returns {string}
*/
QString getModelInfo(const QString& input);
private:

View file

@ -56,6 +56,19 @@ bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) {
SelectionScriptingInterface::SelectionScriptingInterface() {
}
/**jsdoc
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"avatar"</code></td><td></td></tr>
* <tr><td><code>"entity"</code></td><td></td></tr>
* <tr><td><code>"overlay"</code></td><td></td></tr>
* </tbody>
* </table>
* @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 <code>0.0</code> (not visible) to <code>1.0</code>
* (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 - <code>true</code> to enable outline smooth fall-off.
*/
QVariantMap SelectionHighlightStyle::toVariantMap() const {
QVariantMap properties;

View file

@ -82,6 +82,46 @@ protected:
render::HighlightStyle _style;
};
/**jsdoc
* The <code>Selection</code> API provides a means of grouping together avatars, entities, and overlays in named lists.
* @namespace Selection
*
* @example <caption>Outline an entity when it is grabbed by a controller.</caption>
* // 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} <code>true</code> if the selection existed and was successfully removed, otherwise <code>false</code>.
*/
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} <code>true</code> if the item was successfully added, otherwise <code>false</code>.
*/
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} <code>true</code> if the item was successfully removed, otherwise <code>false</code>.
* <codefalse</code> 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} <code>true</code> if the item was successfully cleared, otherwise <code>false</code>.
*/
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 <em>debug log</em> (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.<br />
* 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 <code>false</code> is
* returned.<br />
* 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} <code>true</code> if the selection was successfully disabled for highlight, otherwise
* <code>false</code>.
*/
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} <code>true</code> if the selection was successfully enabled on the scene, otherwise <code>false</code>.
*/
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 <code>false</code> 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:

View file

@ -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 <caption>Display a friendly greeting.</caption>
* 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} <code>true</code> if the user selects "Yes", otherwise <code>false</code>.
* @example <caption>Ask the user a question requiring a yes/no answer.</caption>
* 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 <caption>Ask the user a question requiring a text answer without waiting for the answer.</caption>
* 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 <code>null</code>.
* @example <caption>Ask the user to choose a directory.</caption>
* 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 <caption>Ask the user to choose a directory without waiting for the answer.</caption>
* 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: <code>"*.json"</code> 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: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
* @returns {string} The path and name of the file if one is chosen, otherwise <code>null</code>.
* @example <caption>Ask the user to choose an image file.</caption>
@ -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: <code>"*.json"</code> 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: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
* @example <caption>Ask the user to choose an image file without waiting for the answer.</caption>
* 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: <code>"*.json"</code> 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: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
* @returns {string} The path and name of the file if one is specified, otherwise <code>null</code>. 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: <code>"*.json"</code> 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: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
* @example <caption>Ask the user to specify a file to save to without waiting for an answer.</caption>
* 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: <code>"*.json"</code> 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: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
* @returns {string} The path and name of the asset if one is chosen, otherwise <code>null</code>.
* @example <caption>Ask the user to select an FBX asset.</caption>
@ -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: <code>"*.json"</code> 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: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. 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 <caption>Upload a file to the asset server.</caption>
* 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 <code>true</code>, a moving image is captured as an animated GIF in addition
* @param {boolean} [includeAnimated=false] - If <code>true</code>, 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 <code>0</code> the
* @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is <code>0</code> 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-<user name>-YYYY-MM-DD_HH-MM-SS'.
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
* If this parameter is <code>""</code> 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-<user name>-YYYY-MM-DD_HH-MM-SS'.
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
* If this parameter is <code>""</code> 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(""));

View file

@ -58,9 +58,8 @@ void AddressBarDialog::loadHome() {
qDebug() << "Called LoadHome";
auto locationBookmarks = DependencyManager::get<LocationBookmarks>();
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<AddressManager>()->handleLookupString(homeLocation);
}

View file

@ -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 <em>Read-only.</em>
* @property {boolean} cameraMuted <em>Read-only.</em>
* @property {boolean} isHMD <em>Read-only.</em>
* @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:

View file

@ -80,7 +80,6 @@ void DialogsManager::showFeed() {
}
void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
qDebug() << "DialogsManager::setDomainConnectionFailureVisibility: visible" << visible;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));

View file

@ -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<GeometryCache>();
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() {

View file

@ -588,6 +588,7 @@ void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode
PUSH(false);
break;
}
break;
}
case OpCode::Int:
PUSH(-rhs.intVal);

View file

@ -29,18 +29,67 @@ class AnimationCache : public ResourceCache, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
/**jsdoc
* @namespace AnimationCache
* @augments ResourceCache
*/
public:
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* Returns animation resource for particular animation
* API to manage animation cache resources.
* @namespace AnimationCache
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// 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);

View file

@ -38,24 +38,137 @@ class MixedProcessedAudioStream;
class AudioStreamStatsInterface : public QObject {
Q_OBJECT
/**jsdoc
* @class AudioStats.AudioStreamStats
* @property {number} lossRate <em>Read-only.</em>
* @property {number} lossCount <em>Read-only.</em>
* @property {number} lossRateWindow <em>Read-only.</em>
* @property {number} lossCountWindow <em>Read-only.</em>
* @property {number} framesDesired <em>Read-only.</em>
* @property {number} framesAvailable <em>Read-only.</em>
* @property {number} framesAvailableAvg <em>Read-only.</em>
* @property {number} unplayedMsMax <em>Read-only.</em>
* @property {number} starveCount <em>Read-only.</em>
* @property {number} lastStarveDurationCount <em>Read-only.</em>
* @property {number} dropCount <em>Read-only.</em>
* @property {number} overflowCount <em>Read-only.</em>
* @property {number} timegapMsMax <em>Read-only.</em>
* @property {number} timegapMsAvg <em>Read-only.</em>
* @property {number} timegapMsMaxWindow <em>Read-only.</em>
* @property {number} timegapMsAvgWindow <em>Read-only.</em>
*/
/**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 <em>Read-only.</em>
* @property {number} inputReadMsMax <em>Read-only.</em>
* @property {number} inputUnplayedMsMax <em>Read-only.</em>
* @property {number} outputUnplayedMsMax <em>Read-only.</em>
* @property {number} sentTimegapMsMax <em>Read-only.</em>
* @property {number} sentTimegapMsAvg <em>Read-only.</em>
* @property {number} sentTimegapMsMaxWindow <em>Read-only.</em>
* @property {number} sentTimegapMsAvgWindow <em>Read-only.</em>
* @property {AudioStats.AudioStreamStats} clientStream <em>Read-only.</em>
* @property {AudioStats.AudioStreamStats} mixerStream <em>Read-only.</em>
*/
/**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<QUuid, AudioStreamStats>& 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:

View file

@ -10,6 +10,7 @@
// Inline functions to implement audio dynamics processing
//
#include <stddef.h>
#include <math.h>
#include <stdint.h>

View file

@ -150,6 +150,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
// fall through to OnTime case
}
// FALLTHRU
case SequenceNumberStats::OnTime: {
// Packet is on time; parse its data to the ringbuffer
if (message.getType() == PacketType::SilentAudioFrame

View file

@ -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. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// 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<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,

View file

@ -13,6 +13,7 @@
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include <unordered_map>
AutoUpdater::AutoUpdater() {
#if defined Q_OS_WIN32
@ -43,63 +44,114 @@ void AutoUpdater::parseLatestVersionData() {
QNetworkReply* sender = qobject_cast<QNetworkReply*>(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<QString, InstallerURLs> 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();
}

View file

@ -36,10 +36,17 @@ class AutoUpdater : public QObject, public Dependency {
public:
AutoUpdater();
enum class InstallerType {
CLIENT_ONLY = 0,
FULL
};
void checkForUpdate();
const QMap<int, QMap<QString, QString>>& 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<int, QMap<QString, QString>> _builds;
QString _operatingSystem;
InstallerType _installerType { InstallerType::FULL };
QString _installerCampaign { "" };
void getLatestVersionData();
void downloadUpdateVersion(int version);

View file

@ -57,15 +57,7 @@ using AvatarPhysicsCallback = std::function<void(uint32_t)>;
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 <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.
* @function MyAvatar.setSkeletonOffset
* @param {Vec3} offset - The skeleton offset to set.
* @example <caption>Raise your avatar off the ground a little.</caption>
* // 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 <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.
* @function MyAvatar.getSkeletonOffset
* @returns {Vec3} The current skeleton offset.
* @example <caption>Report your avatar's current skeleton offset.</caption>
* 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 <caption>Report the position of your avatar's hips.</caption>
* 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 <caption>Report the position of your avatar's neck.</caption>
* 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<QVariant> 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 <caption>Report the position of your avatar's left palm.</caption>
* 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 <caption>Report the rotation of your avatar's left palm.</caption>
* 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 <caption>Report the position of your avatar's right palm.</caption>
* 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 <caption>Report the rotation of your avatar's right palm.</caption>
* 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:

View file

@ -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;

View file

@ -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<JointData>& getRawJointData() const { return _jointData; }
/**jsdoc
* @function MyAvatar.setRawJointData
* @param {JointData[]} data
*/
Q_INVOKABLE void setRawJointData(QVector<JointData> data);
/**jsdoc
* Set a specific joint's rotation and position relative to its parent.
* <p>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.</p>
* @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 <caption>Set your avatar to it's default T-pose for a while.<br />
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/t-pose.png" />
* </caption>
* // 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.
* <p>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.</p>
* @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.
* <p>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.</p>
* @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.
* <p>Note: This is slightly faster than the function variation that specifies the joint name.</p>
* @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
* <a href="https://docs.highfidelity.com/create-and-explore/avatars/avatar-standards">Avatar Standards</a>.
* @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
* <a href="https://docs.highfidelity.com/create-and-explore/avatars/avatar-standards">Avatar Standards</a>.
* @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.
* <p>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.</p>
* @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.
* <p>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.</p>
* @function MyAvatar.setJointRotation
* @param {string} name - The name of the joint.
* @param {Quat} rotation - The rotation of the joint relative to its parent.
* @example <caption>Set your avatar to its default T-pose then rotate its right arm.<br />
* <img alt="Avatar in T-pose with arm rotated"
* src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/armpose.png" /></caption>
* // 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.
* <p>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.</p>
* @function MyAvatar.setJointTranslation
* @param {string} name - The name of the joint.
* @param {Vec3} translation - The translation of the joint relative to its parent.
* @example <caption>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.<br />
* <img alt="Avatar with neck stretched"
* src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/stretched-neck.png" /></caption>
* // 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.
* <p>Note: This is slightly slower than the function variation that specifies the joint index.</p>
* @function MyAvatar.clearJointData
* @param {string} name - The name of the joint.
* @example <caption>Offset and restore the position of your avatar's head.</caption>
* // 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
* <a href="https://docs.highfidelity.com/create-and-explore/avatars/avatar-standards">Avatar Standards</a>.
* @function MyAvatar.getJointRotation
* @param {string} name - The name of the joint.
* @returns {Quat} The rotation of the joint relative to its parent.
* @example <caption>Report the rotation of your avatar's hips joint.</caption>
* 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
* <a href="https://docs.highfidelity.com/create-and-explore/avatars/avatar-standards">Avatar Standards</a>.
* @function MyAvatar.getJointTranslation
* @param {number} name - The name of the joint.
* @returns {Vec3} The translation of the joint relative to its parent.
* @example <caption>Report the translation of your avatar's hips joint.</caption>
* 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 <caption>Report the rotations of all your avatar's joints.</caption>
* print(JSON.stringify(MyAvatar.getJointRotations()));
*/
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
/**jsdoc
* @function MyAvatar.getJointTranslations
* @returns {Vec3[]}
*/
Q_INVOKABLE virtual QVector<glm::vec3> getJointTranslations() const;
/**jsdoc
* Set the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint.
* <p>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.</p>
* @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 <caption>Set your avatar to its default T-pose then rotate its right arm.<br />
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/armpose.png" />
* </caption>
* // 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<glm::quat>& jointRotations);
/**jsdoc
* @function MyAvatar.setJointTranslations
* @param {Vec3[]} translations
*/
Q_INVOKABLE virtual void setJointTranslations(const QVector<glm::vec3>& 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 <caption>Set your avatar to it's default T-pose for a while.</caption>
* // 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 <caption>Report the index of your avatar's left arm joint.</caption>
* 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 <caption>Report the names of all the joints in your current avatar.</caption>
* 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 <caption>Report the URLs of all current attachments.</caption>
* var attachments = MyAvatar.getaAttachmentData();
* for (var i = 0; i < attachments.length; i++) {
* print (attachments[i].modelURL);
* }
*/
Q_INVOKABLE QVector<AttachmentData> 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 <code>attachmentData</code> to <code>null</code>.
* @function MyAvatar.setAttachmentData
* @param {MyAvatar.AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use
* <code>null</code> to remove all attachments.
* @example <caption>Remove a hat attachment if your avatar is wearing it.</caption>
* 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>& 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.
* <p>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.</p>
* @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 <code>true</code> so that the bones of the
* attached model's skeleton are be rotated to fit the avatar's current pose. <code>isSoft</code> is used, for example,
* to have clothing that moves with the avatar.<br />
* If <code>true</code>, the <code>translation</code>, <code>rotation</code>, and <code>scale</code> parameters are
* ignored.
* @param {boolean} [allowDuplicates=false]
* @param {boolean} [useSaved=true]
* @example <caption>Attach a cowboy hat to your avatar's head.</caption>
* var attachment = {
* modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx",
* jointName: "Head",
* translation: {"x": 0, "y": 0.25, "z": 0},
* rotation: {"x": 0, "y": 0, "z": 0, "w": 1},
* scale: 1,
* isSoft: false
* };
*
* MyAvatar.attach(attachment.modelURL,
* attachment.jointName,
* attachment.translation,
* attachment.rotation,
* attachment.scale,
* attachment.isSoft);
*/
Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(),
float scale = 1.0f, bool isSoft = false,
bool allowDuplicates = false, bool useSaved = true);
/**jsdoc
* Detach the most recently attached instance of a particular model from either a specific joint or any joint.
* @function MyAvatar.detachOne
* @param {string} modelURL - The URL of the model to detach.
* @param {string} [jointName=""] - The name of the joint to detach the model from. If <code>""</code>, then the most
* recently attached model is removed from which ever joint it was attached to.
*/
Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString());
/**jsdoc
* Detach all instances of a particular model from either a specific joint or all joints.
* @function MyAvatar.detachAll
* @param {string} modelURL - The URL of the model to detach.
* @param {string} [jointName=""] - The name of the joint to detach the model from. If <code>""</code>, then the model is
* detached from all joints.
*/
Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString());
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
@ -657,19 +1062,63 @@ public:
glm::vec3 getClientGlobalPosition() const { return _globalPosition; }
glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; }
/**jsdoc
* @function MyAvatar.getAvatarEntityData
* @returns {object}
*/
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
/**jsdoc
* @function MyAvatar.setAvatarEntityData
* @param {object} avatarEntityData
*/
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
void insertDetachedEntityID(const QUuid entityID);
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
/**jsdoc
* @function MyAvatar.getSensorToWorldMatrix
* @returns {Mat4}
*/
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
/**jsdoc
* @function MyAvatar.getSensorToWorldScale
* @returns {number}
*/
// thread safe
Q_INVOKABLE float getSensorToWorldScale() const;
/**jsdoc
* @function MyAvatar.getControllerLeftHandMatrix
* @returns {Mat4}
*/
// thread safe
Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const;
/**jsdoc
* @function MyAvatar.getControllerRightHandMatrix
* @returns {Mat4}
*/
// thread safe
Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const;
/**jsdoc
* @function MyAvatar.getDataRate
* @param {string} [rateName=""]
* @returns {number}
*/
Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const;
/**jsdoc
* @function MyAvatar.getUpdateRate
* @param {string} [rateName=""]
* @returns {number}
*/
Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const;
int getJointCount() const { return _jointData.size(); }
@ -705,17 +1154,60 @@ public:
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {}
signals:
/**jsdoc
* @function MyAvatar.displayNameChanged
* @returns {Signal}
*/
void displayNameChanged();
/**jsdoc
* @function MyAvatar.sessionDisplayNameChanged
* @returns {Signal}
*/
void sessionDisplayNameChanged();
/**jsdoc
* @function MyAvatar.skeletonModelURLChanged
* @returns {Signal}
*/
void skeletonModelURLChanged();
/**jsdoc
* @function MyAvatar.lookAtSnappingChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void lookAtSnappingChanged(bool enabled);
/**jsdoc
* @function MyAvatar.sessionUUIDChanged
* @returns {Signal}
*/
void sessionUUIDChanged();
public slots:
/**jsdoc
* @function MyAvatar.sendAvatarDataPacket
* @param {boolean} [sendAll=false]
*/
void sendAvatarDataPacket(bool sendAll = false);
/**jsdoc
* @function MyAvatar.sendIdentityPacket
*/
void sendIdentityPacket();
/**jsdoc
* @function MyAvatar.setJointMappingsFromNetworkReply
*/
void setJointMappingsFromNetworkReply();
/**jsdoc
* @function MyAvatar.setSessionUUID
* @param {Uuid} sessionUUID
*/
virtual void setSessionUUID(const QUuid& sessionUUID) {
if (sessionUUID != getID()) {
if (sessionUUID == QUuid()) {
@ -727,13 +1219,45 @@ public slots:
}
}
/**jsdoc
* @function MyAvatar.getAbsoluteJointRotationInObjectFrame
* @param {number} index
* @returns {Quat}
*/
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
/**jsdoc
* @function MyAvatar.getAbsoluteJointTranslationInObjectFrame
* @param {number} index
* @returns {Vec3}
*/
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
/**jsdoc
* @function MyAvatar.setAbsoluteJointRotationInObjectFrame
* @param {number} index
* @param {Quat} rotation
* @returns {boolean}
*/
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
/**jsdoc
* @function MyAvatar.setAbsoluteJointTranslationInObjectFrame
* @param {number} index
* @param {Vec3} translation
* @returns {boolean}
*/
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
/**jsdoc
* @function MyAvatar.getTargetScale
* @returns {number}
*/
float getTargetScale() const { return _targetScale; } // why is this a slot?
/**jsdoc
* @function MyAvatar.resetLastSent
*/
void resetLastSent() { _lastToByteArray = 0; }
protected:

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AvatarHashMap_h
#define hifi_AvatarHashMap_h
@ -39,9 +40,22 @@ public:
int size() { return _avatarHash.size(); }
// Currently, your own avatar will be included as the null avatar id.
/**jsdoc
* @function AvatarManager.getAvatarIdentifiers
* @returns {Uuid[]}
*/
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
/**jsdoc
* @function AvatarManager.getAvatarsInRange
* @param {Vec3} position
* @param {number} range
* @returns {Uuid[]}
*/
Q_INVOKABLE QVector<QUuid> getAvatarsInRange(const glm::vec3& position, float rangeMeters) const;
// No JSDod because it's documwented in AvatarManager.
// Null/Default-constructed QUuids will return MyAvatar
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }
@ -49,18 +63,67 @@ public:
int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters);
signals:
/**jsdoc
* @function AvatarManager.avatarAddedEvent
* @param {Uuid} sessionUUID
* @returns {Signal}
*/
void avatarAddedEvent(const QUuid& sessionUUID);
/**jsdoc
* @function AvatarManager.avatarRemovedEvent
* @param {Uuid} sessionUUID
* @returns {Signal}
*/
void avatarRemovedEvent(const QUuid& sessionUUID);
/**jsdoc
* @function AvatarManager.avatarSessionChangedEvent
* @param {Uuid} sessionUUID
* @param {Uuid} oldSessionUUID
* @returns {Signal}
*/
void avatarSessionChangedEvent(const QUuid& sessionUUID,const QUuid& oldUUID);
public slots:
/**jsdoc
* @function AvatarManager.isAvatarInRange
* @param {string} position
* @param {string} range
* @returns {boolean}
*/
bool isAvatarInRange(const glm::vec3 & position, const float range);
protected slots:
/**jsdoc
* @function AvatarManager.sessionUUIDChanged
* @param {Uuid} sessionUUID
* @param {Uuid} oldSessionUUID
*/
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
/**jsdoc
* @function AvatarManager.processAvatarDataPacket
* @param {} message
* @param {} sendingNode
*/
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
/**jsdoc
* @function AvatarManager.processAvatarIdentityPacket
* @param {} message
* @param {} sendingNode
*/
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
/**jsdoc
* @function AvatarManager.processKillAvatar
* @param {} message
* @param {} sendingNode
*/
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
protected:

View file

@ -39,7 +39,9 @@ class UserInputMapper;
* methods.</li>
* <li>Use {@link Controller.parseMapping} or {@link Controller.loadMapping} to load a {@link Controller.MappingJSON}.</li>
* </ul>
* <p>Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take effect.
*
* <p>Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take
* effect.</p>
*
* <p>Mappings and their routes are applied according to the following rules:</p>
* <ul>
@ -49,7 +51,7 @@ class UserInputMapper;
* output that already has a route the new route is ignored.</li>
* <li>New mappings override previous mappings: each output is processed using the route in the most recently enabled
* mapping that contains that output.</li>
* </p>
* </ul>
*
* @class MappingObject
*/

View file

@ -29,7 +29,8 @@ class ScriptingInterface;
* <p>A route in a {@link MappingObject} used by the {@link Controller} API.</p>
*
* <p>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 <code>Standard</code> control, action, or script function.</p>
* {@link RouteObject#to} to apply it to a <code>Standard</code> control, action, or script function. Note: Loops are not
* permitted.</p>
*
* <p>Some methods apply to routes with number data, some apply routes with {@link Pose} data, and some apply to both route
* types.<p>

View file

@ -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:

View file

@ -25,7 +25,7 @@
#include <EntityScriptingInterface.h>
#include "EntitiesRendererLogging.h"
#include <NetworkingConstants.h>
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<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
DependencyManager::get<GeometryCache>()->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<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
_webSurface = QSharedPointer<OffscreenQmlSurface>(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>();
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<OffscreenQmlSurface> 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>();
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<QObject*>("webEngineView");
if (obj) {
// stop loading
QMetaObject::invokeMethod(obj, "stop");
}
if (rootItem && contentType == ContentType::HtmlContent) {
// stop loading
QMetaObject::invokeMethod(rootItem, "stop");
}
webSurface->pause();

View file

@ -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;

View file

@ -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());
}

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -766,6 +766,36 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesByType(const QString entity
return result;
}
QVector<QUuid> EntityScriptingInterface::findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, bool caseSensitiveSearch) const {
QVector<QUuid> result;
if (_entityTree) {
QVector<EntityItemPointer> 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<EntityItemID> entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude);

View file

@ -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<QUuid> 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 <code>true</code> 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 <caption>Report the number of entities with the name, "Light-Target".</caption>
* var entityIDs = Entities.findEntitiesByName("Light-Target", MyAvatar.position, 10, false);
* print("Number of entities with the name "Light-Target": " + entityIDs.length);
*/
Q_INVOKABLE QVector<QUuid> findEntitiesByName(const QString entityName, const glm::vec3& center, float radius,
bool caseSensitiveSearch = false ) const;
/**jsdoc
* Find the first entity intersected by a {@link PickRay}. <code>Light</code> and <code>Zone</code> entities are not
* intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable}

View file

@ -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<NodeList>();
// 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
@ -1429,6 +1402,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
case PacketType::EntityAdd:
isAdd = true; // fall through to next case
// FALLTHRU
case PacketType::EntityPhysics:
case PacketType::EntityEdit: {
quint64 startDecode = 0, endDecode = 0;
@ -1619,7 +1593,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);
}
}

View file

@ -397,12 +397,11 @@ protected:
QHash<EntityItemID, EntityItemPointer> _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<AvatarData> _myAvatar{ nullptr };

View file

@ -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<bool>, setJointRotationsSet);
READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector<glm::quat>, setJointRotations);
READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, 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<bool>([&] {
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;
}

View file

@ -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 };
};

View file

@ -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; }

View file

@ -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;

View file

@ -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<uint8_t>(tex.alphaSource, subobject.properties.at(0).value<int>());

View file

@ -249,7 +249,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
indexToDirect = true;
}
}
if (indexToDirect && data.normalIndices.isEmpty()) {
if (indexToDirect && data.colorIndices.isEmpty()) {
// hack to work around wacky Makehuman exports
data.colorsByVertex = true;
}

View file

@ -142,7 +142,6 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) {
out << prop.toInt();
break;
encodeNode(out, FBXNode());
case QMetaType::Float:
out.device()->write("F", 1);
out << prop.toFloat();

View file

@ -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");

View file

@ -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;

View file

@ -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
}
}

View file

@ -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);

View file

@ -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 };

View file

@ -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()@>

View file

@ -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;

View file

@ -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 {

View file

@ -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

View file

@ -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@> <!def that !>
//

View file

@ -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. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// 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());

View file

@ -137,12 +137,69 @@ using NetworkTexturePointer = QSharedPointer<NetworkTexture>;
Q_DECLARE_METATYPE(QWeakPointer<NetworkTexture>)
/// 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. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// 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);

Some files were not shown because too many files have changed in this diff Show more