diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e4077d5d46..33e1034128 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -35,6 +35,8 @@ #include #include "../AssignmentDynamicFactory.h" #include "../entities/AssignmentParentFinder.h" +#include +#include const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; @@ -60,7 +62,10 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : { DependencyManager::registerInheritance(); DependencyManager::set(); - + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -1060,6 +1065,10 @@ void AvatarMixer::handleOctreePacket(QSharedPointer message, Sh } void AvatarMixer::aboutToFinish() { + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff deleted file mode 100644 index 6d46970136..0000000000 Binary files a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff and /dev/null differ diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot similarity index 83% rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot index 6cdc2487a6..88936e6c51 100644 Binary files a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot and b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot differ diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg similarity index 96% rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg index de91dcae71..a3a1aac8a9 100644 --- a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg +++ b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg @@ -14,7 +14,6 @@ - @@ -97,7 +96,6 @@ - @@ -137,7 +135,6 @@ - @@ -156,4 +153,10 @@ + + + + + + diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf similarity index 83% rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf index c27553333b..dae388d2eb 100644 Binary files a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.woff new file mode 100644 index 0000000000..09d5b93f69 Binary files /dev/null and b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.woff differ diff --git a/interface/resources/fonts/hifi-glyphs-1.34/icons-reference.html b/interface/resources/fonts/hifi-glyphs-1.38/icons-reference.html similarity index 97% rename from interface/resources/fonts/hifi-glyphs-1.34/icons-reference.html rename to interface/resources/fonts/hifi-glyphs-1.38/icons-reference.html index 1c75e9e235..147087a4cf 100644 --- a/interface/resources/fonts/hifi-glyphs-1.34/icons-reference.html +++ b/interface/resources/fonts/hifi-glyphs-1.38/icons-reference.html @@ -12,7 +12,7 @@

HiFi Glyphs

-

This font was created for use in High Fidelity

+

This font was created withFontastic

CSS mapping

  • @@ -43,10 +43,6 @@
  • -
  • -
    - -
  • @@ -375,10 +371,6 @@
  • -
  • -
    - -
  • @@ -535,10 +527,6 @@
  • -
  • -
    - -
  • @@ -611,6 +599,30 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +

Character mapping

    @@ -642,10 +654,6 @@
    -
  • -
    - -
  • @@ -974,10 +982,6 @@
  • -
  • -
    - -
  • @@ -1134,10 +1138,6 @@
  • -
  • -
    - -
  • @@ -1210,6 +1210,30 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
- + \ No newline at end of file diff --git a/interface/resources/fonts/hifi-glyphs-1.34/styles.css b/interface/resources/fonts/hifi-glyphs-1.38/styles.css similarity index 97% rename from interface/resources/fonts/hifi-glyphs-1.34/styles.css rename to interface/resources/fonts/hifi-glyphs-1.38/styles.css index 59e38d455c..f6a096189b 100644 --- a/interface/resources/fonts/hifi-glyphs-1.34/styles.css +++ b/interface/resources/fonts/hifi-glyphs-1.38/styles.css @@ -59,9 +59,6 @@ .icon-headphones:before { content: "\68"; } -.icon-mic:before { - content: "\69"; -} .icon-upload:before { content: "\6a"; } @@ -308,9 +305,6 @@ .icon-voxels:before { content: "\e005"; } -.icon-lock:before { - content: "\e006"; -} .icon-visible:before { content: "\e007"; } @@ -428,9 +422,6 @@ .icon-password:before { content: "\e029"; } -.icon-rez:before { - content: "\e025"; -} .icon-keyboard-collapse:before { content: "\e02b"; } @@ -485,3 +476,21 @@ .icon-oculus:before { content: "\e036"; } +.icon-check-circled:before { + content: "\e037"; +} +.icon-rez-01:before { + content: "\e025"; +} +.icon-locked:before { + content: "\e006"; +} +.icon-unlocked:before { + content: "\e039"; +} +.icon-mic:before { + content: "\69"; +} +.icon-92-lock-01:before { + content: "\e038"; +} diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index aaeb1d2ace..dae388d2eb 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/icons/standalone-optimized.svg b/interface/resources/icons/standalone-optimized.svg new file mode 100644 index 0000000000..f721be9ebb --- /dev/null +++ b/interface/resources/icons/standalone-optimized.svg @@ -0,0 +1,27 @@ + + + + + + diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml index 0961ef2500..4e1c21c456 100644 --- a/interface/resources/qml/controlsUit/Switch.qml +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -27,6 +27,7 @@ Item { property string labelGlyphOnText: ""; property int labelGlyphOnSize: 32; property alias checked: originalSwitch.checked; + property string backgroundOnColor: "#252525"; signal onCheckedChanged; signal clicked; @@ -40,10 +41,10 @@ Item { onClicked: rootSwitch.clicked(); hoverEnabled: true - topPadding: 3; + topPadding: 1; leftPadding: 3; rightPadding: 3; - bottomPadding: 3; + bottomPadding: 1; onHoveredChanged: { if (hovered) { @@ -54,7 +55,7 @@ Item { } background: Rectangle { - color: "#252525"; + color: checked ? backgroundOnColor : "#252525"; implicitWidth: rootSwitch.switchWidth; implicitHeight: rootSwitch.height; radius: rootSwitch.switchRadius; diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 238c26236f..fc49bcf048 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -30,6 +30,7 @@ Item { property string imageUrl: ""; property var goFunction: null; property string storyId: ""; + property bool standaloneOptimized: false; property bool drillDownToPlace: false; property bool showPlace: isConcurrency; @@ -40,6 +41,7 @@ Item { property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; + property int textPadding: 10; property int smallMargin: 4; property int messageHeight: 40; @@ -267,9 +269,33 @@ Item { hoverEnabled: false onClicked: { Tablet.playSound(TabletEnums.ButtonClick); - goFunction("hifi://" + hifiUrl); + goFunction("hifi://" + hifiUrl, standaloneOptimized); } } + + Image { + id: standaloneOptomizedBadge + + anchors { + right: actionIcon.left + top: actionIcon.top + topMargin: 2 + rightMargin: 3 + } + height: root.standaloneOptimized ? 25 : 0 + width: 25 + + visible: root.standaloneOptimized && isConcurrency + fillMode: Image.PreserveAspectFit + source: "../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge + color: hifi.colors.blueHighlight + visible: root.standaloneOptimized && isConcurrency + } + StateImage { id: actionIcon; visible: !isAnnouncement; @@ -281,7 +307,8 @@ Item { right: parent.right; margins: smallMargin; } - } + } + function go() { Tablet.playSound(TabletEnums.ButtonClick); goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 1e89971938..718ebc9331 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -82,6 +82,7 @@ Column { action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), image_url: resolveUrl(data.details && data.details.image_url), + standalone_optimized: data.standalone_optimized, metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. @@ -127,6 +128,7 @@ Column { hifiUrl: model.place_name + model.path; thumbnail: model.thumbnail_url; imageUrl: model.image_url; + standaloneOptimized: model.standalone_optimized; action: model.action; timestamp: model.created_at; onlineUsers: model.online_users; @@ -187,4 +189,14 @@ Column { } } } + function isStandalone(address) { + var lowerAddress = address.toLowerCase(); + + for (var i=0; i < suggestions.count; i++) { + if (suggestions.get(i).place_name.toLowerCase() === lowerAddress) { + return suggestions.get(i).standalone_optimized; + } + } + return false; + } } diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 242ca5ab57..646fc881e1 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -376,7 +376,7 @@ Item { } FiraSansRegular { id: nameCardConnectionInfoText - visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard + visible: selected && !isMyCard && pal.activeTab == "connectionsTab" width: parent.width height: displayNameTextPixelSize size: displayNameTextPixelSize - 4 @@ -412,7 +412,7 @@ Item { } FiraSansRegular { id: nameCardRemoveConnectionText - visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard + visible: selected && !isMyCard && pal.activeTab == "connectionsTab" width: parent.width height: displayNameTextPixelSize size: displayNameTextPixelSize - 4 @@ -425,7 +425,7 @@ Item { } HifiControls.Button { id: visitConnectionButton - visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard + visible: selected && !isMyCard && pal.activeTab == "connectionsTab" text: "Visit" enabled: thisNameCard.placeName !== "" anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter @@ -450,7 +450,7 @@ Item { // Style radius: 4 color: "#c5c5c5" - visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent + visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent // Rectangle for the zero-gain point on the VU meter Rectangle { id: vuMeterZeroGain @@ -481,7 +481,7 @@ Item { id: vuMeterBase // Anchors anchors.fill: parent - visible: isMyCard || selected + visible: !isMyCard && selected // Style color: parent.color radius: parent.radius @@ -489,7 +489,7 @@ Item { // Rectangle for the VU meter audio level Rectangle { id: vuMeterLevel - visible: isMyCard || selected + visible: !isMyCard && selected // Size width: (thisNameCard.audioLevel) * parent.width // Style @@ -525,7 +525,7 @@ Item { anchors.verticalCenter: nameCardVUMeter.verticalCenter; anchors.left: nameCardVUMeter.left; // Properties - visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent; + visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent; minimumValue: -60.0 maximumValue: 20.0 stepSize: 5 @@ -572,19 +572,7 @@ Item { implicitHeight: 16 } } - RalewayRegular { - // The slider for my card is special, it controls the master gain - id: gainSliderText; - visible: isMyCard; - text: "master volume"; - size: hifi.fontSizes.tabularData; - anchors.left: parent.right; - anchors.leftMargin: 8; - color: hifi.colors.baseGrayHighlight; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignTop; - } - } + } function updateGainFromQML(avatarUuid, sliderValue, isReleased) { if (Users.getAvatarGain(avatarUuid) != sliderValue) { diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..efbf663838 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -11,12 +11,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import controlsUit 1.0 as HifiControlsUit import "../../windows" import "./" as AudioControls @@ -26,7 +26,11 @@ Rectangle { HifiConstants { id: hifi; } property var eventBridge; + // leave as blank, this is user's volume for the avatar mixer + property var myAvatarUuid: "" property string title: "Audio Settings" + property int switchHeight: 16 + property int switchWidth: 40 signal sendToScript(var message); color: hifi.colors.baseGray; @@ -38,7 +42,7 @@ Rectangle { property bool isVR: AudioScriptingInterface.context === "VR" - property real rightMostInputLevelPos: 0 + property real rightMostInputLevelPos: 450 //placeholder for control sizes and paddings //recalculates dynamically in case of UI size is changed QtObject { @@ -80,16 +84,16 @@ Rectangle { }); } - function disablePeakValues() { - root.showPeaks = false; - AudioScriptingInterface.devices.input.peakValuesEnabled = false; + function updateMyAvatarGainFromQML(sliderValue, isReleased) { + if (Users.getAvatarGain(myAvatarUuid) != sliderValue) { + Users.setAvatarGain(myAvatarUuid, sliderValue); + } } Component.onCompleted: enablePeakValues(); - Component.onDestruction: disablePeakValues(); - onVisibleChanged: visible ? enablePeakValues() : disablePeakValues(); Column { + id: column spacing: 12; anchors.top: bar.bottom anchors.bottom: parent.bottom @@ -98,65 +102,68 @@ Rectangle { Separator { } - RalewayRegular { - x: margins.paddings + muteMic.boxSize + muteMic.spacing; - size: 16; - color: "white"; - text: qsTr("Input Device Settings") - } - - ColumnLayout { - x: margins.paddings; - spacing: 16; + RowLayout { + x: 2 * margins.paddings; + spacing: columnOne.width; width: parent.width; // mute is in its own row - RowLayout { - spacing: (margins.sizeCheckBox - 10.5) * 3; - AudioControls.CheckBox { - id: muteMic - text: qsTr("Mute microphone"); - spacing: margins.sizeCheckBox - boxSize - isRedCheck: true; + ColumnLayout { + id: columnOne + spacing: 24; + x: margins.paddings + HifiControlsUit.Switch { + id: muteMic; + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: "Mute microphone"; + backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.muted; - onClicked: { + onCheckedChanged: { AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } } - AudioControls.CheckBox { - id: stereoMic - spacing: muteMic.spacing; - text: qsTr("Enable stereo input"); + HifiControlsUit.Switch { + id: stereoInput; + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Stereo input"); + backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.isStereoInput; - onClicked: { + onCheckedChanged: { AudioScriptingInterface.isStereoInput = checked; checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } } - RowLayout { - spacing: muteMic.spacing*2; //make it visually distinguish - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Enable noise reduction"); + ColumnLayout { + spacing: 24; + HifiControlsUit.Switch { + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: "Noise Reduction"; + backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.noiseReduction; - onClicked: { + onCheckedChanged: { AudioScriptingInterface.noiseReduction = checked; checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); + + HifiControlsUit.Switch { + id: audioLevelSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Audio Level Meter"); + backgroundOnColor: "#E3E3E3"; checked: AvatarInputs.showAudioTools; - onClicked: { + onCheckedChanged: { AvatarInputs.showAudioTools = checked; checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding } - onXChanged: rightMostInputLevelPos = x + width } } } @@ -171,7 +178,7 @@ Rectangle { HiFiGlyphs { width: margins.sizeCheckBox text: hifi.glyphs.mic; - color: hifi.colors.primaryHighlight; + color: hifi.colors.white; anchors.left: parent.left anchors.leftMargin: -size/4 //the glyph has empty space at left about 25% anchors.verticalCenter: parent.verticalCenter; @@ -183,8 +190,8 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: margins.sizeCheckBox size: 16; - color: hifi.colors.lightGrayText; - text: qsTr("CHOOSE INPUT DEVICE"); + color: hifi.colors.white; + text: qsTr("Choose input device"); } } @@ -210,7 +217,7 @@ Rectangle { width: parent.width - inputLevel.width clip: true checkable: !checked - checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; + checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; boxSize: margins.sizeCheckBox / 2 isRound: true text: devicename @@ -222,7 +229,7 @@ Rectangle { } } } - InputPeak { + AudioControls.InputPeak { id: inputLevel anchors.right: parent.right peak: model.peak; @@ -233,6 +240,13 @@ Rectangle { } } } + AudioControls.LoopbackAudio { + x: margins.paddings + + visible: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR); + anchors { left: parent.left; leftMargin: margins.paddings } + } Separator {} @@ -247,7 +261,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; width: margins.sizeCheckBox text: hifi.glyphs.unmuted; - color: hifi.colors.primaryHighlight; + color: hifi.colors.white; size: 36; } @@ -257,8 +271,8 @@ Rectangle { anchors.leftMargin: margins.sizeCheckBox anchors.verticalCenter: parent.verticalCenter; size: 16; - color: hifi.colors.lightGrayText; - text: qsTr("CHOOSE OUTPUT DEVICE"); + color: hifi.colors.white; + text: qsTr("Choose output device"); } } @@ -293,7 +307,68 @@ Rectangle { } } } - PlaySampleSound { + + Item { + id: gainContainer + x: margins.paddings; + width: parent.width - margins.paddings*2 + height: gainSliderTextMetrics.height + + HifiControlsUit.Slider { + id: gainSlider + anchors.right: parent.right + height: parent.height + width: 200 + minimumValue: -60.0 + maximumValue: 20.0 + stepSize: 5 + value: Users.getAvatarGain(myAvatarUuid) + onValueChanged: { + updateMyAvatarGainFromQML(value, false); + } + onPressedChanged: { + if (!pressed) { + updateMyAvatarGainFromQML(value, false); + } + } + + MouseArea { + anchors.fill: parent + onWheel: { + // Do nothing. + } + onDoubleClicked: { + gainSlider.value = 0.0 + } + onPressed: { + // Pass through to Slider + mouse.accepted = false + } + onReleased: { + // the above mouse.accepted seems to make this + // never get called, nonetheless... + mouse.accepted = false + } + } + } + TextMetrics { + id: gainSliderTextMetrics + text: gainSliderText.text + font: gainSliderText.font + } + RalewayRegular { + // The slider for my card is special, it controls the master gain + id: gainSliderText; + text: "Avatar volume"; + size: 16; + anchors.left: parent.left; + color: hifi.colors.white; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; + } + } + + AudioControls.PlaySampleSound { x: margins.paddings visible: (bar.currentIndex === 1 && isVR) || diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml new file mode 100644 index 0000000000..8ec0ffc496 --- /dev/null +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -0,0 +1,67 @@ +// +// LoopbackAudio.qml +// qml/hifi/audio +// +// Created by Seth Alves on 2019-2-18 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit + +RowLayout { + property bool audioLoopedBack: AudioScriptingInterface.getServerEcho(); + function startAudioLoopback() { + if (!audioLoopedBack) { + audioLoopedBack = true; + AudioScriptingInterface.setServerEcho(true); + } + } + function stopAudioLoopback() { + if (audioLoopedBack) { + audioLoopedBack = false; + AudioScriptingInterface.setServerEcho(false); + } + } + + HifiConstants { id: hifi; } + + Timer { + id: loopbackTimer + interval: 8000; + running: false; + repeat: false; + onTriggered: { + stopAudioLoopback(); + } + } + + HifiControlsUit.Button { + text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE"); + color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue; + onClicked: { + if (audioLoopedBack) { + loopbackTimer.stop(); + stopAudioLoopback(); + } else { + loopbackTimer.restart(); + startAudioLoopback(); + } + } + } + + RalewayRegular { + Layout.leftMargin: 2; + size: 14; + color: "white"; + font.italic: true + text: audioLoopedBack ? qsTr("Speak in your input") : ""; + } +} diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index cfe55af9c4..b9d9727dab 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -14,7 +14,7 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import controlsUit 1.0 as HifiControlsUit RowLayout { property var sound: null; @@ -55,32 +55,9 @@ RowLayout { HifiConstants { id: hifi; } - Button { - id: control - background: Rectangle { - implicitWidth: 20; - implicitHeight: 20; - radius: hifi.buttons.radius; - gradient: Gradient { - GradientStop { - position: 0.2; - color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; - } - GradientStop { - position: 1.0; - color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; - } - } - } - contentItem: HiFiGlyphs { - // absolutely position due to asymmetry in glyph -// x: isPlaying ? 0 : 1; -// y: 1; - size: 14; - color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white"; - text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; - } - + HifiControlsUit.Button { + text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND"); + color: isPlaying ? hifi.buttons.red : hifi.buttons.blue; onClicked: isPlaying ? stopSound() : playSound(); } @@ -88,7 +65,7 @@ RowLayout { Layout.leftMargin: 2; size: 14; color: "white"; - text: isPlaying ? qsTr("Stop sample sound") : qsTr("Play sample sound"); + font.italic: true + text: isPlaying ? qsTr("Listen to your output") : ""; } - } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 8ca34af28a..16faf2feb7 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -30,6 +30,8 @@ Rectangle { property string dateAcquired: "--"; property string itemCost: "--"; property string marketplace_item_id: ""; + property bool standaloneOptimized: false; + property bool standaloneIncompatible: false; property string certTitleTextColor: hifi.colors.darkGray; property string certTextColor: hifi.colors.white; property string infoTextColor: hifi.colors.blueAccent; @@ -71,6 +73,8 @@ Rectangle { } else { root.marketplace_item_id = result.data.marketplace_item_id; root.isMyCert = result.isMyCert ? result.isMyCert : false; + root.standaloneOptimized = result.data.standalone_optimized; + root.standaloneIncompatible = result.data.standalone_incompatible; if (root.certInfoReplaceMode > 3) { root.itemName = result.data.marketplace_item_name; @@ -421,6 +425,24 @@ Rectangle { anchors.rightMargin: 24; height: root.useGoldCert ? 220 : 372; + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + top: ownedByHeader.top + rightMargin: 15 + topMargin: 28 + } + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + RalewayRegular { id: errorText; visible: !root.useGoldCert; @@ -467,6 +489,7 @@ Rectangle { color: root.infoTextColor; elide: Text.ElideRight; } + AnonymousProRegular { id: isMyCertText; visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== ""; @@ -485,14 +508,46 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + RalewayRegular { + id: standaloneHeader; + text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED" : "STAND-ALONE INCOMPATIBLE"; + // Text size + size: 16; + // Anchors + anchors.top: ownedBy.bottom; + anchors.topMargin: 15; + anchors.left: parent.left; + anchors.right: parent.right; + visible: root.standaloneOptimized || root.standaloneIncompatible; + height: visible ? paintedHeight : 0; + // Style + color: hifi.colors.darkGray; + } + + RalewayRegular { + id: standaloneText; + text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices"; + // Text size + size: 18; + // Anchors + anchors.top: standaloneHeader.bottom; + anchors.topMargin: 8; + anchors.left: standaloneHeader.left; + visible: root.standaloneOptimized || root.standaloneIncompatible; + height: visible ? paintedHeight : 0; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + } + RalewayRegular { id: dateAcquiredHeader; text: "ACQUISITION DATE"; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 28; + anchors.top: standaloneText.bottom; + anchors.topMargin: 20; anchors.left: parent.left; anchors.right: parent.horizontalCenter; anchors.rightMargin: 8; @@ -521,8 +576,8 @@ Rectangle { // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 28; + anchors.top: standaloneText.bottom; + anchors.topMargin: 20; anchors.left: parent.horizontalCenter; anchors.right: parent.right; height: paintedHeight; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 4ff935921f..07ded49956 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -91,6 +91,14 @@ Rectangle { id: -1, name: "Everything" }); + categoriesModel.append({ + id: -1, + name: "Stand-alone Optimized" + }); + categoriesModel.append({ + id: -1, + name: "Stand-alone Compatible" + }); result.data.items.forEach(function(category) { categoriesModel.append({ id: category.id, @@ -127,6 +135,8 @@ Rectangle { marketplaceItem.availability = result.data.availability; marketplaceItem.updated_item_id = result.data.updated_item_id || ""; marketplaceItem.created_at = result.data.created_at; + marketplaceItem.standaloneOptimized = result.data.standalone_optimized; + marketplaceItem.standaloneVisible = result.data.standalone_optimized || result.data.standalone_incompatible; marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; marketplaceItemView.visible = true; @@ -191,16 +201,16 @@ Rectangle { visible: true Image { - id: marketplaceHeaderImage; - source: "../common/images/marketplaceHeaderImage.png"; - anchors.top: parent.top; - anchors.topMargin: 2; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 0; - anchors.left: parent.left; - anchors.leftMargin: 8; - width: 140; - fillMode: Image.PreserveAspectFit; + id: marketplaceHeaderImage + source: "../common/images/marketplaceHeaderImage.png" + anchors.top: parent.top + anchors.topMargin: 2 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 8 + width: 140 + fillMode: Image.PreserveAspectFit MouseArea { anchors.fill: parent; @@ -546,7 +556,8 @@ Rectangle { price: model.cost availability: model.availability isLoggedIn: root.isLoggedIn; - + standaloneOptimized: model.standalone_optimized + onShowItem: { MarketplaceScriptingInterface.getMarketplaceItem(item_id); } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index fa7e311026..605a68fe73 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -15,6 +15,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.9 import QtQuick.Controls 2.2 import stylesUit 1.0 +import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon @@ -40,9 +41,11 @@ Rectangle { property string license: "" property string posted: "" property string created_at: "" - property bool isLoggedIn: false; - property int edition: -1; - property bool supports3DHTML: false; + property bool isLoggedIn: false + property int edition: -1 + property bool supports3DHTML: false + property bool standaloneVisible: false + property bool standaloneOptimized: false onCategoriesChanged: { categoriesListModel.clear(); @@ -240,10 +243,43 @@ Rectangle { right: parent.right; top: itemImage.bottom; } - height: categoriesList.y - buyButton.y + categoriesList.height + height: categoriesList.y - badges.y + categoriesList.height function evalHeight() { - height = categoriesList.y - buyButton.y + categoriesList.height; + height = categoriesList.y - badges.y + categoriesList.height; + } + + Item { + id: badges + + anchors { + right: buyButton.left + top: parent.top + rightMargin: 10 + } + height: childrenRect.height + + Image { + id: standaloneOptomizedBadge + + anchors { + topMargin: 15 + right: parent.right + top: parent.top + } + height: root.standaloneOptimized ? 50 : 0 + width: 50 + + visible: root.standaloneOptimized + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge + color: hifi.colors.blueHighlight + visible: root.standaloneOptimized + } } HifiControlsUit.Button { @@ -252,10 +288,10 @@ Rectangle { anchors { right: parent.right top: parent.top - left: parent.left topMargin: 15 } - height: 50 + height: 50 + width: 180 property bool isUpdate: root.edition >= 0 // Special case of updating from a specific older item property bool isStocking: (creator === Account.username) && (availability === "not for sale") && !updated_item_id // Note: server will say "sold out" or "invalidated" before it says NFS @@ -275,11 +311,11 @@ Rectangle { id: creatorItem anchors { - top: buyButton.bottom + top: parent.top leftMargin: 15 topMargin: 15 } - width: parent.width + width: paintedWidth height: childrenRect.height RalewaySemiBold { @@ -528,13 +564,55 @@ Rectangle { } } } + + Item { + id: standaloneItem + + anchors { + top: licenseItem.bottom + topMargin: 15 + left: parent.left + right: parent.right + } + height: root.standaloneVisible ? childrenRect.height : 0 + + visible: root.standaloneVisible + + RalewaySemiBold { + id: standaloneLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + height: 20 + + text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED:" : "STAND-ALONE INCOMPATIBLE:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: standaloneOptimizedText + + anchors.top: standaloneLabel.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: paintedWidth + + text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices" + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + } + } Item { id: descriptionItem property string text: "" anchors { - top: licenseItem.bottom + top: standaloneItem.bottom topMargin: 15 left: parent.left right: parent.right diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 439247e410..8ad6191f04 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -35,8 +35,9 @@ Rectangle { property string category: "" property int price: 0 property string availability: "unknown" - property bool isLoggedIn: false; - + property bool isLoggedIn: false + property bool standaloneOptimized: false + signal buy() signal showItem() signal categoryClicked(string category) @@ -240,16 +241,18 @@ Rectangle { id: creatorText anchors { - top: creatorLabel.top; - left: creatorLabel.right; - leftMargin: 10; + top: creatorLabel.top + left: creatorLabel.right + leftMargin: 15 + right: badges.left } - width: paintedWidth; + width: paintedWidth - text: root.creator; - size: 14; - color: hifi.colors.lightGray; - verticalAlignment: Text.AlignVCenter; + text: root.creator + size: 14 + elide: Text.ElideRight + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { @@ -260,12 +263,12 @@ Rectangle { left: parent.left leftMargin: 15 } - width: paintedWidth; + width: paintedWidth - text: "IN:"; - size: 14; - color: hifi.colors.lightGrayText; - verticalAlignment: Text.AlignVCenter; + text: "IN:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { @@ -274,23 +277,57 @@ Rectangle { anchors { top: categoryLabel.top left: categoryLabel.right - leftMargin: 10 + leftMargin: 15 + right: badges.left } width: paintedWidth text: root.category size: 14 - color: hifi.colors.blueHighlight; - verticalAlignment: Text.AlignVCenter; - + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight MouseArea { anchors.fill: parent onClicked: root.categoryClicked(root.category); } } + Item { + id: badges + + anchors { + right: buyButton.left + top: parent.top + topMargin: 10 + rightMargin: 10 + } + height: 50 + + Image { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + top: parent.top + } + height: root.standaloneOptimized ? 40 : 0 + width: 40 + + visible: root.standaloneOptimized + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge + color: hifi.colors.blueHighlight + visible: root.standaloneOptimized + } + } HifiControlsUit.Button { + id: buyButton anchors { right: parent.right top: parent.top diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 2c2fed1d8f..a7b36eae36 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -13,6 +13,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 +import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 @@ -50,6 +51,8 @@ Item { property string upgradeTitle; property bool updateAvailable: root.updateItemId && root.updateItemId !== ""; property bool valid; + property bool standaloneOptimized; + property bool standaloneIncompatible; property string originalStatusText; property string originalStatusColor; @@ -403,7 +406,9 @@ Item { id: permissionExplanationText; anchors.fill: parent; text: { - if (root.itemType === "contentSet") { + if (root.standaloneIncompatible) { + "This item is incompatible with stand-alone devices. Learn more"; + } else if (root.itemType === "contentSet") { "You do not have 'Replace Content' permissions in this domain. Learn more"; } else if (root.itemType === "entity") { "You do not have 'Rez Certified' permissions in this domain. Learn more"; @@ -417,7 +422,11 @@ Item { verticalAlignment: Text.AlignVCenter; onLinkActivated: { - sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + if (link === "#standaloneIncompatible") { + sendToPurchases({method: 'showStandaloneIncompatibleExplanation'}); + } else { + sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + } } } } @@ -699,7 +708,8 @@ Item { anchors.bottomMargin: 8; width: 160; height: 40; - enabled: root.hasPermissionToRezThis && + enabled: !root.standaloneIncompatible && + root.hasPermissionToRezThis && MyAvatar.skeletonModelURL !== root.itemHref && !root.wornEntityID && root.valid; @@ -838,6 +848,28 @@ Item { root.sendToPurchases({ method: 'flipCard' }); } } + } + Image { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + bottom: parent.bottom + rightMargin: 15 + bottomMargin:12 + } + height: root.standaloneOptimized ? 36 : 0 + width: 36 + + visible: root.standaloneOptimized + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge + color: hifi.colors.blueHighlight + visible: root.standaloneOptimized } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index dc892e6640..46bbb626c6 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -12,7 +12,7 @@ // import Hifi 1.0 as Hifi -import QtQuick 2.5 +import QtQuick 2.9 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls @@ -33,6 +33,7 @@ Rectangle { property bool purchasesReceived: false; property bool punctuationMode: false; property bool isDebuggingFirstUseTutorial: false; + property bool isStandalone: false; property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; @@ -44,6 +45,7 @@ Rectangle { purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); } + Connections { target: Commerce; @@ -110,6 +112,10 @@ Rectangle { } } + Component.onCompleted: { + isStandalone = PlatformInfo.isStandalone(); + } + HifiCommerceCommon.CommerceLightbox { id: lightboxPopup; z: 999; @@ -527,6 +533,7 @@ Rectangle { ListView { id: purchasesContentsList; visible: purchasesModel.count !== 0; + interactive: !lightboxPopup.visible; clip: true; model: purchasesModel; snapMode: ListView.NoSnap; @@ -553,6 +560,8 @@ Rectangle { upgradeTitle: model.upgrade_title; itemType: model.item_type; valid: model.valid; + standaloneOptimized: model.standalone_optimized + standaloneIncompatible: root.isStandalone && model.standalone_incompatible anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -673,6 +682,14 @@ Rectangle { lightboxPopup.visible = false; } lightboxPopup.visible = true; + } else if (msg.method === "showStandaloneIncompatibleExplanation") { + lightboxPopup.titleText = "Stand-alone Incompatible"; + lightboxPopup.bodyText = "The item is incompatible with stand-alone devices."; + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; } else if (msg.method === "flipCard") { diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index d26bf81e57..213540b334 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -97,10 +97,11 @@ Rectangle { textFormat: Text.StyledText linkColor: "#00B4EF" color: "white" - text: "Blockchain technology from Elements." + property string link: "https://eos.io/" + text: "Blockchain technology from EOS." size: 14 onLinkActivated: { - HiFiAbout.openUrl("https://elementsproject.org/elements/"); + HiFiAbout.openUrl(link); } } RalewayRegular { diff --git a/interface/resources/qml/hifi/tablet/OculusConfiguration.qml b/interface/resources/qml/hifi/tablet/OculusConfiguration.qml new file mode 100644 index 0000000000..6df4fa1b83 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/OculusConfiguration.qml @@ -0,0 +1,59 @@ +// +// Created by Dante Ruiz on 3/4/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +import QtQuick 2.5 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + + +Rectangle { + id: root + anchors.fill: parent + property string pluginName: "" + property var displayInformation: null + HifiConstants { id: hifi } + + color: hifi.colors.baseGray + + HifiControls.CheckBox { + id: box + width: 15 + height: 15 + + anchors { + left: root.left + leftMargin: 75 + } + + onClicked: { + sendConfigurationSettings( { trackControllersInOculusHome: checked }); + } + } + + RalewaySemiBold { + id: head + + text: "Track hand controllers in Oculus Home" + size: 12 + + color: "white" + anchors.left: box.right + anchors.leftMargin: 5 + } + + function displayConfiguration() { + var configurationSettings = InputConfiguration.configurationSettings(root.pluginName); + box.checked = configurationSettings.trackControllersInOculusHome; + } + + function sendConfigurationSettings(settings) { + InputConfiguration.setConfigurationSettings(settings, root.pluginName); + } +} diff --git a/interface/resources/qml/hifi/tablet/TADLightbox.qml b/interface/resources/qml/hifi/tablet/TADLightbox.qml new file mode 100644 index 0000000000..35a01aeec3 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TADLightbox.qml @@ -0,0 +1,144 @@ +// +// TADLightbox.qml +// qml/hifi/tablet +// +// TADLightbox +// +// Created by Roxanne Skelly on 2019-03-07 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//controls" as HifiControls + +// references XXX from root context + +Rectangle { + property string titleText; + property string bodyImageSource; + property string bodyText; + property string button1color: hifi.buttons.noneBorderlessGray; + property string button1text; + property var button1method; + property string button2color: hifi.buttons.blue; + property string button2text; + property var button2method; + property string buttonLayout: "leftright"; + + id: root; + visible: false; + anchors.fill: parent; + color: Qt.rgba(0, 0, 0, 0.5); + z: 999; + + HifiConstants { id: hifi; } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Rectangle { + anchors.centerIn: parent; + width: 376; + height: childrenRect.height + 30; + color: "white"; + + RalewaySemiBold { + id: titleText; + text: root.titleText; + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: paintedHeight; + color: hifi.colors.black; + size: 24; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + } + + RalewayRegular { + id: bodyText; + text: root.bodyText; + anchors.top: root.bodyImageSource ? bodyImage.bottom : (root.titleText ? titleText.bottom : parent.top); + anchors.topMargin: root.bodyImageSource ? 20 : (root.titleText ? 20 : 30); + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: paintedHeight; + color: hifi.colors.black; + size: 20; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + + } + + Item { + id: buttons; + anchors.top: bodyText.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + height: root.buttonLayout === "leftright" ? 70 : 150; + + // Button 1 + HifiControlsUit.Button { + id: button1; + color: root.button1color; + colorScheme: hifi.colorSchemes.light; + anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top; + anchors.left: parent.left; + anchors.leftMargin: root.buttonLayout === "leftright" ? 30 : 10; + anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right; + anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10; + width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) : + (undefined); + height: 40; + text: root.button1text; + onClicked: { + button1method(); + } + visible: (root.button1text !== ""); + } + + // Button 2 + HifiControlsUit.Button { + id: button2; + visible: root.button2text; + color: root.button2color; + colorScheme: hifi.colorSchemes.light; + anchors.top: root.buttonLayout === "leftright" ? parent.top : button1.bottom; + anchors.topMargin: root.buttonLayout === "leftright" ? undefined : 20; + anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left; + anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10; + anchors.right: parent.right; + anchors.rightMargin: root.buttonLayout === "leftright" ? 30 : 10; + width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined; + height: 40; + text: root.button2text; + onClicked: { + button2method(); + } + } + } + } + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index ab0a98a8c5..4edae017d1 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -73,7 +73,7 @@ StackView { function resetAfterTeleport() { //storyCardFrame.shown = root.shown = false; } - function goCard(targetString) { + function goCard(targetString, standaloneOptimized) { if (0 !== targetString.indexOf('hifi://')) { var card = tabletWebView.createObject(); card.url = addressBarDialog.metaverseServerUrl + targetString; @@ -82,7 +82,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(targetString, true); + toggleOrGo(targetString, true, standaloneOptimized); clearAddressLineTimer.start(); } @@ -230,7 +230,7 @@ StackView { updateLocationText(text.length > 0); } onAccepted: { - toggleOrGo(); + toggleOrGo(addressLine.text, false, places.isStandalone(addressLine.text)); } // unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style @@ -392,7 +392,18 @@ StackView { right: parent.right } } + } + TADLightbox { + id: unoptimizedDomain + titleText: "Unoptimized Domain" + bodyText: "You're trying to access a place that hasn't been optimized for your device. Are you sure you want to continue." + button1text: "CANCEL" + button2text: "YES CONTINUE" + visible: false + button1method: function() { + visible = false; + } } function updateLocationText(enteringAddress) { @@ -407,14 +418,30 @@ StackView { } } - function toggleOrGo(address, fromSuggestions) { - if (address !== undefined && address !== "") { - addressBarDialog.loadAddress(address, fromSuggestions); - clearAddressLineTimer.start(); - } else if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text, fromSuggestions); - clearAddressLineTimer.start(); + function toggleOrGo(address, fromSuggestions, standaloneOptimized) { + + var goTarget = function () { + if (address !== undefined && address !== "") { + addressBarDialog.loadAddress(address, fromSuggestions); + clearAddressLineTimer.start(); + } else if (addressLine.text !== "") { + addressBarDialog.loadAddress(addressLine.text, fromSuggestions); + clearAddressLineTimer.start(); + } + DialogsManager.hideAddressBar(); + } + + unoptimizedDomain.button2method = function() { + Settings.setValue("ShowUnoptimizedDomainWarning", false); + goTarget(); + } + + var showPopup = PlatformInfo.isStandalone() && !standaloneOptimized && Settings.getValue("ShowUnoptimizedDomainWarning", true); + + if(!showPopup) { + goTarget(); + } else { + unoptimizedDomain.visible = true; } - DialogsManager.hideAddressBar(); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ca8883f660..6d9a1823a1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -246,6 +246,8 @@ #include "AboutUtil.h" +#include + #if defined(Q_OS_WIN) #include @@ -289,13 +291,6 @@ static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" }; static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); #endif -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - #if !defined(Q_OS_ANDROID) static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; #else @@ -755,6 +750,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { bool isStore = cmdOptionExists(argc, const_cast(argv), OCULUS_STORE_ARG); qApp->setProperty(hifi::properties::OCULUS_STORE, isStore); + // emulate standalone device + static const auto STANDALONE_ARG = "--standalone"; + bool isStandalone = cmdOptionExists(argc, const_cast(argv), STANDALONE_ARG); + qApp->setProperty(hifi::properties::STANDALONE, isStandalone); + // Ignore any previous crashes if running from command line with a test script. bool inTestMode { false }; for (int i = 0; i < argc; ++i) { @@ -3030,6 +3030,10 @@ void Application::initializeUi() { }; OffscreenQmlSurface::addWhitelistContextHandler({ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + QUrl{ "hifi/commerce/purchases/Purchases.qml" }, + QUrl{ "hifi/commerce/wallet/Wallet.qml" }, + QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, + QUrl{ "hifi/tablet/TabletAddressDialog.qml" }, }, platformInfoCallback); QmlContextCallback ttsCallback = [](QQmlContext* context) { diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index b528441be7..04a426c3db 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -12,10 +12,52 @@ #include "AvatarDoctor.h" #include #include +#include #include +#include +#include +const int NETWORKED_JOINTS_LIMIT = 256; +const float HIPS_GROUND_MIN_Y = 0.01f; +const float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; +const QString LEFT_JOINT_PREFIX = "Left"; +const QString RIGHT_JOINT_PREFIX = "Right"; -AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) : +const QStringList LEG_MAPPING_SUFFIXES = { + "UpLeg" + "Leg", + "Foot", + "ToeBase", +}; + +static QStringList ARM_MAPPING_SUFFIXES = { + "Shoulder", + "Arm", + "ForeArm", + "Hand", +}; + +static QStringList HAND_MAPPING_SUFFIXES = { + "HandIndex3", + "HandIndex2", + "HandIndex1", + "HandPinky3", + "HandPinky2", + "HandPinky1", + "HandMiddle3", + "HandMiddle2", + "HandMiddle1", + "HandRing3", + "HandRing2", + "HandRing1", + "HandThumb3", + "HandThumb2", + "HandThumb1", +}; + +const QUrl DEFAULT_DOCS_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); + +AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { connect(this, &AvatarDoctor::complete, this, [this](QVariantList errors) { @@ -39,136 +81,215 @@ void AvatarDoctor::startDiagnosing() { const auto resource = DependencyManager::get()->getGeometryResource(_avatarFSTFileUrl); resource->refresh(); - const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); - const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) { + + const auto resourceLoaded = [this, resource](bool success) { // MODEL if (!success) { - _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened.", DEFAULT_DOCS_URL }); emit complete(getErrors()); return; } + _model = resource; const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); if (!avatarModel.originalURL.endsWith(".fbx")) { - _errors.push_back({ "Unsupported avatar model format", DEFAULT_URL }); + _errors.push_back({ "Unsupported avatar model format.", DEFAULT_DOCS_URL }); emit complete(getErrors()); return; } // RIG if (avatarModel.joints.isEmpty()) { - _errors.push_back({ "Avatar has no rig", DEFAULT_URL }); + _errors.push_back({ "Avatar has no rig.", DEFAULT_DOCS_URL }); } else { - if (avatarModel.joints.length() > 256) { - _errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL }); + auto jointNames = avatarModel.getJointNames(); + + if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { + _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_DOCS_URL }); } // Avatar does not have Hips bone mapped - if (!avatarModel.getJointNames().contains("Hips")) { - _errors.push_back({ "Hips are not mapped", DEFAULT_URL }); + if (!jointNames.contains("Hips")) { + _errors.push_back({ "Hips are not mapped.", DEFAULT_DOCS_URL }); } - if (!avatarModel.getJointNames().contains("Spine")) { - _errors.push_back({ "Spine is not mapped", DEFAULT_URL }); + if (!jointNames.contains("Spine")) { + _errors.push_back({ "Spine is not mapped.", DEFAULT_DOCS_URL }); } - if (!avatarModel.getJointNames().contains("Head")) { - _errors.push_back({ "Head is not mapped", DEFAULT_URL }); + if (!jointNames.contains("Spine1")) { + _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_DOCS_URL }); + } + if (!jointNames.contains("Neck")) { + _errors.push_back({ "Neck is not mapped.", DEFAULT_DOCS_URL }); + } + if (!jointNames.contains("Head")) { + _errors.push_back({ "Head is not mapped.", DEFAULT_DOCS_URL }); + } + + if (!jointNames.contains("LeftEye")) { + if (jointNames.contains("RightEye")) { + _errors.push_back({ "LeftEye is not mapped.", DEFAULT_DOCS_URL }); + } else { + _errors.push_back({ "Eyes are not mapped.", DEFAULT_DOCS_URL }); + } + } else if (!jointNames.contains("RightEye")) { + _errors.push_back({ "RightEye is not mapped.", DEFAULT_DOCS_URL }); + } + + const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) { + for (const QString& jointSuffix : jointMappingSuffixes) { + if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) != + jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) { + return true; + } + } + return false; + }; + + const auto isDescendantOfJointWhenJointsExist = [avatarModel, jointNames] (const QString& jointName, const QString& ancestorName) { + if (!jointNames.contains(jointName) || !jointNames.contains(ancestorName)) { + return true; + } + auto currentJoint = avatarModel.joints[avatarModel.jointIndices[jointName] - 1]; + while (currentJoint.parentIndex != -1) { + currentJoint = avatarModel.joints[currentJoint.parentIndex]; + if (currentJoint.name == ancestorName) { + return true; + } + } + return false; + }; + + if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_DOCS_URL }); + } + if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_DOCS_URL }); + } + if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_DOCS_URL }); + } + + // Multiple skeleton root joints checkup + int skeletonRootJoints = 0; + for (const HFMJoint& joint: avatarModel.joints) { + if (joint.parentIndex == -1 && joint.isSkeletonJoint) { + skeletonRootJoints++; + } + } + + if (skeletonRootJoints > 1) { + _errors.push_back({ "Multiple top-level joints found.", DEFAULT_DOCS_URL }); + } + + Rig rig; + rig.reset(avatarModel); + const float eyeHeight = rig.getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + + // SCALE + const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; + + if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_DOCS_URL }); + } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_DOCS_URL }); + } + + // HipsNotOnGround + auto hipsIndex = rig.indexOfJoint("Hips"); + if (hipsIndex >= 0) { + glm::vec3 hipsPosition; + if (rig.getJointPosition(hipsIndex, hipsPosition)) { + const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + + if (hipsPosition.y < HIPS_GROUND_MIN_Y) { + _errors.push_back({ "Hips are on ground.", DEFAULT_DOCS_URL }); + } + } + } + + // HipsSpineChestNotCoincident + auto spineIndex = rig.indexOfJoint("Spine"); + auto chestIndex = rig.indexOfJoint("Spine1"); + if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { + glm::vec3 hipsPosition; + glm::vec3 spinePosition; + glm::vec3 chestPosition; + if (rig.getJointPosition(hipsIndex, hipsPosition) && + rig.getJointPosition(spineIndex, spinePosition) && + rig.getJointPosition(chestIndex, chestPosition)) { + + const auto hipsToSpine = glm::length(hipsPosition - spinePosition); + const auto spineToChest = glm::length(spinePosition - chestPosition); + if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_DOCS_URL }); + } + } + } + + auto mapping = resource->getMapping(); + + if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { + const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); + QStringList jointValues; + for (const auto& jointVariant: jointNameMappings.values()) { + jointValues << jointVariant.toString(); + } + + const auto& uniqueJointValues = jointValues.toSet(); + for (const auto& jointName: uniqueJointValues) { + if (jointValues.count(jointName) > 1) { + _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_DOCS_URL }); + } + } + } + + if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { + _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_DOCS_URL }); + } + + if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { + _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_DOCS_URL }); + } + + if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { + _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_DOCS_URL }); } } - // SCALE - const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; - const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - - const float avatarHeight = avatarModel.bindExtents.largestDimension(); - if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too small.", DEFAULT_URL }); - } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too large.", DEFAULT_URL }); - } - - // TEXTURES - QStringList externalTextures{}; - QSet textureNames{}; - auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable { - if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) { - externalTextures << texture.name; + auto materialMappingHandled = [this]() mutable { + _materialMappingLoadedCount++; + // Continue diagnosing the textures as soon as the material mappings have tried to load. + if (_materialMappingLoadedCount == _materialMappingCount) { + // TEXTURES + diagnoseTextures(); } }; - - foreach(const HFMMaterial material, avatarModel.materials) { - addTextureToList(material.normalTexture); - addTextureToList(material.albedoTexture); - addTextureToList(material.opacityTexture); - addTextureToList(material.glossTexture); - addTextureToList(material.roughnessTexture); - addTextureToList(material.specularTexture); - addTextureToList(material.metallicTexture); - addTextureToList(material.emissiveTexture); - addTextureToList(material.occlusionTexture); - addTextureToList(material.scatteringTexture); - addTextureToList(material.lightmapTexture); - } - if (!externalTextures.empty()) { - // Check External Textures: - auto modelTexturesURLs = model->getTextures(); - _externalTextureCount = externalTextures.length(); - foreach(const QString textureKey, externalTextures) { - if (!modelTexturesURLs.contains(textureKey)) { - _missingTextureCount++; - _checkedTextureCount++; - continue; - } - - const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); - - auto textureResource = DependencyManager::get()->getTexture(textureURL); - auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable { - qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; - - if (_checkedTextureCount == _externalTextureCount) { - if (_missingTextureCount > 0) { - _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); - } - if (_unsupportedTextureCount > 0) { - _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL }); - } - emit complete(getErrors()); - } - }; - - auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable { - if (!success) { - auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); - if (normalizedURL.isLocalFile()) { - QFile textureFile(normalizedURL.toLocalFile()); - if (textureFile.exists()) { - _unsupportedTextureCount++; - } else { - _missingTextureCount++; - } - } else { - _missingTextureCount++; - } - } - _checkedTextureCount++; - checkTextureLoadingComplete(); - }; - - if (textureResource) { - textureResource->refresh(); - if (textureResource->isLoaded()) { - textureLoaded(!textureResource->isFailed()); - } else { - connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); - } + _materialMappingCount = (int)model->getMaterialMapping().size(); + _materialMappingLoadedCount = 0; + for (const auto& materialMapping : model->getMaterialMapping()) { + // refresh the texture mappings + auto materialMappingResource = materialMapping.second; + if (materialMappingResource) { + materialMappingResource->refresh(); + if (materialMappingResource->isLoaded()) { + materialMappingHandled(); } else { - _missingTextureCount++; - _checkedTextureCount++; - checkTextureLoadingComplete(); + connect(materialMappingResource.data(), &NetworkTexture::finished, this, + [materialMappingHandled](bool success) mutable { + + materialMappingHandled(); + }); } + } else { + materialMappingHandled(); } - } else { - emit complete(getErrors()); + } + if (_materialMappingCount == 0) { + // TEXTURES + diagnoseTextures(); } }; @@ -179,11 +300,117 @@ void AvatarDoctor::startDiagnosing() { connect(resource.data(), &GeometryResource::finished, this, resourceLoaded); } } else { - _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened", DEFAULT_DOCS_URL }); emit complete(getErrors()); } } +void AvatarDoctor::diagnoseTextures() { + const auto model = _model.data(); + const auto avatarModel = _model.data()->getHFMModel(); + QVector externalTextures{}; + QVector textureNames{}; + int texturesFound = 0; + auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable { + if (texture.filename.isEmpty()) { + return; + } + if (texture.content.isEmpty() && !externalTextures.contains(texture.name)) { + externalTextures << texture.name; + } + texturesFound++; + }; + + for (const auto& material : avatarModel.materials) { + addTextureToList(material.normalTexture); + addTextureToList(material.albedoTexture); + addTextureToList(material.opacityTexture); + addTextureToList(material.glossTexture); + addTextureToList(material.roughnessTexture); + addTextureToList(material.specularTexture); + addTextureToList(material.metallicTexture); + addTextureToList(material.emissiveTexture); + addTextureToList(material.occlusionTexture); + addTextureToList(material.scatteringTexture); + addTextureToList(material.lightmapTexture); + } + + for (const auto& materialMapping : model->getMaterialMapping()) { + for (const auto& networkMaterial : materialMapping.second.data()->parsedMaterials.networkMaterials) { + texturesFound += (int)networkMaterial.second->getTextureMaps().size(); + } + } + + auto normalizedURL = DependencyManager::get()->normalizeURL( + QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); + + if (texturesFound == 0) { + _errors.push_back({ tr("No textures assigned."), DEFAULT_DOCS_URL }); + } + + if (!externalTextures.empty()) { + // Check External Textures: + auto modelTexturesURLs = model->getTextures(); + _externalTextureCount = externalTextures.length(); + + auto checkTextureLoadingComplete = [this]() mutable { + if (_checkedTextureCount == _externalTextureCount) { + if (_missingTextureCount > 0) { + _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_DOCS_URL }); + } + if (_unsupportedTextureCount > 0) { + _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), + DEFAULT_DOCS_URL }); + } + + emit complete(getErrors()); + } + }; + + for (const QString& textureKey : externalTextures) { + if (!modelTexturesURLs.contains(textureKey)) { + _missingTextureCount++; + _checkedTextureCount++; + continue; + } + const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); + auto textureResource = DependencyManager::get()->getTexture(textureURL); + auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable { + if (!success) { + auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); + if (normalizedURL.isLocalFile()) { + QFile textureFile(normalizedURL.toLocalFile()); + if (textureFile.exists()) { + _unsupportedTextureCount++; + } else { + _missingTextureCount++; + } + } else { + _missingTextureCount++; + } + } + _checkedTextureCount++; + checkTextureLoadingComplete(); + }; + + if (textureResource) { + textureResource->refresh(); + if (textureResource->isLoaded()) { + textureLoaded(!textureResource->isFailed()); + } else { + connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); + } + } else { + _missingTextureCount++; + _checkedTextureCount++; + } + } + checkTextureLoadingComplete(); + } else { + emit complete(getErrors()); + } +} + QVariantList AvatarDoctor::getErrors() const { QVariantList result; for (const auto& error : _errors) { diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index bebec32542..780f600bed 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -16,6 +16,7 @@ #include #include #include +#include "GeometryCache.h" struct AvatarDiagnosticResult { QString message; @@ -27,7 +28,7 @@ Q_DECLARE_METATYPE(QVector) class AvatarDoctor : public QObject { Q_OBJECT public: - AvatarDoctor(QUrl avatarFSTFileUrl); + AvatarDoctor(const QUrl& avatarFSTFileUrl); Q_INVOKABLE void startDiagnosing(); @@ -37,6 +38,8 @@ signals: void complete(QVariantList errors); private: + void diagnoseTextures(); + QUrl _avatarFSTFileUrl; QVector _errors; @@ -45,6 +48,11 @@ private: int _missingTextureCount = 0; int _unsupportedTextureCount = 0; + int _materialMappingCount = 0; + int _materialMappingLoadedCount = 0; + + GeometryResource::Pointer _model; + bool _isDiagnosing = false; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 709d11764f..9211be3b4f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5333,10 +5333,22 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) } void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "useFlow", + Q_ARG(bool, isActive), + Q_ARG(bool, isCollidable), + Q_ARG(const QVariantMap&, physicsConfig), + Q_ARG(const QVariantMap&, collisionsConfig)); + return; + } if (_skeletonModel->isLoaded()) { - _skeletonModel->getRig().initFlow(isActive); auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); + if (!flow.isInitialized() && isActive) { + _skeletonModel->getRig().initFlow(true); + } else { + flow.setActive(isActive); + } collisionSystem.setActive(isCollidable); auto physicsGroups = physicsConfig.keys(); if (physicsGroups.size() > 0) { @@ -5387,6 +5399,93 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } } +QVariantMap MyAvatar::getFlowData() { + QVariantMap result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "getFlowData", + Q_RETURN_ARG(QVariantMap, result)); + return result; + } + if (_skeletonModel->isLoaded()) { + auto jointNames = getJointNames(); + auto &flow = _skeletonModel->getRig().getFlow(); + auto &collisionSystem = flow.getCollisionSystem(); + bool initialized = flow.isInitialized(); + result.insert("initialized", initialized); + result.insert("active", flow.getActive()); + result.insert("colliding", collisionSystem.getActive()); + QVariantMap physicsData; + QVariantMap collisionsData; + QVariantMap threadData; + std::map groupJointsMap; + QVariantList jointCollisionData; + auto &groups = flow.getGroupSettings(); + for (auto &joint : flow.getJoints()) { + auto &groupName = joint.second.getGroup(); + if (groups.find(groupName) != groups.end()) { + if (groupJointsMap.find(groupName) == groupJointsMap.end()) { + groupJointsMap.insert(std::pair(groupName, QVariantList())); + } + groupJointsMap[groupName].push_back(joint.second.getIndex()); + } + } + for (auto &group : groups) { + QVariantMap settingsObject; + QString groupName = group.first; + FlowPhysicsSettings groupSettings = group.second; + settingsObject.insert("active", groupSettings._active); + settingsObject.insert("damping", groupSettings._damping); + settingsObject.insert("delta", groupSettings._delta); + settingsObject.insert("gravity", groupSettings._gravity); + settingsObject.insert("inertia", groupSettings._inertia); + settingsObject.insert("radius", groupSettings._radius); + settingsObject.insert("stiffness", groupSettings._stiffness); + settingsObject.insert("jointIndices", groupJointsMap[groupName]); + physicsData.insert(groupName, settingsObject); + } + + auto &collisions = collisionSystem.getCollisions(); + for (auto &collision : collisions) { + QVariantMap collisionObject; + collisionObject.insert("offset", vec3toVariant(collision._offset)); + collisionObject.insert("radius", collision._radius); + collisionObject.insert("jointIndex", collision._jointIndex); + QString jointName = jointNames.size() > collision._jointIndex ? jointNames[collision._jointIndex] : "unknown"; + collisionsData.insert(jointName, collisionObject); + } + for (auto &thread : flow.getThreads()) { + QVariantList indices; + for (int index : thread._joints) { + indices.append(index); + } + threadData.insert(thread._jointsPointer->at(thread._joints[0]).getName(), indices); + } + result.insert("physics", physicsData); + result.insert("collisions", collisionsData); + result.insert("threads", threadData); + } + return result; +} + +QVariantList MyAvatar::getCollidingFlowJoints() { + QVariantList result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "getCollidingFlowJoints", + Q_RETURN_ARG(QVariantList, result)); + return result; + } + + if (_skeletonModel->isLoaded()) { + auto& flow = _skeletonModel->getRig().getFlow(); + for (auto &joint : flow.getJoints()) { + if (joint.second.isColliding()) { + result.append(joint.second.getIndex()); + } + } + } + return result; +} + void MyAvatar::initFlowFromFST() { if (_skeletonModel->isLoaded()) { auto &flowData = _skeletonModel->getHFMModel().flowData; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 46189c4a11..e516364f61 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1198,6 +1198,19 @@ public: */ Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + /**jsdoc + * @function MyAvatar.getFlowData + * @returns {object} + */ + Q_INVOKABLE QVariantMap getFlowData(); + + /**jsdoc + * returns the indices of every colliding flow joint + * @function MyAvatar.getCollidingFlowJoints + * @returns {int[]} + */ + Q_INVOKABLE QVariantList getCollidingFlowJoints(); + public slots: /**jsdoc diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..fcf3c181da 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -192,7 +192,7 @@ signals: * }); */ void mutedChanged(bool isMuted); - + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index b390ab7119..89d609810c 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -7,7 +7,7 @@ // #include "PlatformInfoScriptingInterface.h" #include "Application.h" - +#include #include #ifdef Q_OS_WIN @@ -138,6 +138,14 @@ bool PlatformInfoScriptingInterface::has3DHTML() { #if defined(Q_OS_ANDROID) return false; #else - return true; + return !qApp->property(hifi::properties::STANDALONE).toBool(); +#endif +} + +bool PlatformInfoScriptingInterface::isStandalone() { +#if defined(Q_OS_ANDROID) + return false; +#else + return qApp->property(hifi::properties::STANDALONE).toBool(); #endif } diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index aece09b008..31f0058585 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -68,9 +68,15 @@ public slots: /**jsdoc * Returns true if device supports 3d HTML - * @function Window.hasRift + * @function Window.has3DHTML * @returns {boolean} true if device supports 3d HTML, otherwise false.*/ bool has3DHTML(); + + /**jsdoc + * Returns true if device is standalone + * @function Window.hasRift + * @returns {boolean} true if device is a standalone device, otherwise false.*/ + bool isStandalone(); }; #endif // hifi_PlatformInfoScriptingInterface_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 3d7971cf57..dbd24573ee 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "Application.h" diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 3bb80b9375..5bc2021e5e 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -463,6 +463,7 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); auto simPrefix = SIM_JOINT_PREFIX.toUpper(); std::vector handsIndices; + _groupSettings.clear(); for (int i = 0; i < skeleton->getNumJoints(); i++) { auto name = skeleton->getJointName(i); @@ -509,6 +510,7 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, jointSettings); _flowJointData.insert(std::pair(i, flowJoint)); } + updateGroupSettings(group, jointSettings); } } else { if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { @@ -727,6 +729,7 @@ void Flow::setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSet joint.second.setSettings(settings); } } + updateGroupSettings(group, settings); } bool Flow::getJointPositionInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { @@ -780,4 +783,12 @@ Flow& Flow::operator=(const Flow& otherFlow) { } } return *this; +} + +void Flow::updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings) { + if (_groupSettings.find(group) == _groupSettings.end()) { + _groupSettings.insert(std::pair(group, settings)); + } else { + _groupSettings[group] = settings; + } } \ No newline at end of file diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index 35464e9420..ad81c2be77 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -149,6 +149,7 @@ public: void setCollisionSettingsByJoint(int jointIndex, const FlowCollisionSettings& settings); void setActive(bool active) { _active = active; } bool getActive() const { return _active; } + const std::vector& getCollisions() const { return _selfCollisions; } protected: std::vector _selfCollisions; std::vector _othersCollisions; @@ -221,6 +222,7 @@ public: const glm::quat& getCurrentRotation() const { return _currentRotation; } const glm::vec3& getCurrentTranslation() const { return _initialTranslation; } const glm::vec3& getInitialPosition() const { return _initialPosition; } + bool isColliding() const { return _colliding; } protected: @@ -293,6 +295,7 @@ public: void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); + const std::map& getGroupSettings() const { return _groupSettings; } void cleanUp(); signals: @@ -309,6 +312,7 @@ private: void setJoints(AnimPoseVec& relativePoses, const std::vector& overrideFlags); void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex); + void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings); void setScale(float scale); float _scale { 1.0f }; @@ -316,6 +320,7 @@ private: glm::vec3 _entityPosition; glm::quat _entityRotation; std::map _flowJointData; + std::map _groupSettings; std::vector _jointThreads; std::vector _flowJointKeywords; FlowCollisionSystem _collisionSystem; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0fe03c7074..07bdfde189 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2179,3 +2179,52 @@ void Rig::initFlow(bool isActive) { _networkFlow.cleanUp(); } } + +float Rig::getUnscaledEyeHeight() const { + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + + int headTopJoint = indexOfJoint("HeadTop_End"); + int headJoint = indexOfJoint("Head"); + int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); + int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = getAnimSkeleton(); + if (eyeJoint >= 0 && toeJoint >= 0) { + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; + } else if (eyeJoint >= 0) { + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; + } else if (headTopJoint >= 0 && toeJoint >= 0) { + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. + const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); + } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. + const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); + } else if (headJoint >= 0) { + // Measure Head joint to the ground, then add in distance from neck to eye. + const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); + } else { + return DEFAULT_AVATAR_EYE_HEIGHT; + } +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2f0e2ad65b..df13ff5c2b 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -26,6 +26,7 @@ #include "SimpleMovingAverage.h" #include "AnimUtil.h" #include "Flow.h" +#include "AvatarConstants.h" class Rig; class AnimInverseKinematics; @@ -237,6 +238,8 @@ public: void initFlow(bool isActive); Flow& getFlow() { return _internalFlow; } + float getUnscaledEyeHeight() const; + signals: void onLoadComplete(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 8c50a195ee..b2e6167ffa 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1531,7 +1531,6 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) { } } - bool AudioClient::setIsStereoInput(bool isStereoInput) { bool stereoInputChanged = false; if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 29036b7c71..87e0f68e72 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -210,13 +210,13 @@ public slots: void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } - bool getLocalEcho() { return _shouldEchoLocally; } - void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } - void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } + virtual bool getLocalEcho() override { return _shouldEchoLocally; } + virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; } + virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; } - bool getServerEcho() { return _shouldEchoToServer; } - void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; } - void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } + virtual bool getServerEcho() override { return _shouldEchoToServer; } + virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; } + virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; } void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); void sendMuteEnvironmentPacket(); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 0f075ab224..e9e40e95f9 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -45,9 +45,16 @@ public slots: virtual bool shouldLoopbackInjectors() { return false; } virtual bool setIsStereoInput(bool stereo) = 0; - virtual bool isStereoInput() = 0; + virtual bool getLocalEcho() = 0; + virtual void setLocalEcho(bool localEcho) = 0; + virtual void toggleLocalEcho() = 0; + + virtual bool getServerEcho() = 0; + virtual void setServerEcho(bool serverEcho) = 0; + virtual void toggleServerEcho() = 0; + signals: void isStereoInputChanged(bool isStereo); }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d3ae030296..f3e671143b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -2040,54 +2040,7 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. if (_skeletonModel) { - auto& rig = _skeletonModel->getRig(); - - // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. - AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); - AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); - - // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. - // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. - - int headTopJoint = rig.indexOfJoint("HeadTop_End"); - int headJoint = rig.indexOfJoint("Head"); - int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); - int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); - - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - const float GROUND_Y = 0.0f; - - // Values from the skeleton are in the geometry coordinate frame. - auto skeleton = rig.getAnimSkeleton(); - if (eyeJoint >= 0 && toeJoint >= 0) { - // Measure from eyes to toes. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; - return scaleFactor * eyeHeight; - } else if (eyeJoint >= 0) { - // Measure Eye joint to y = 0 plane. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; - return scaleFactor * eyeHeight; - } else if (headTopJoint >= 0 && toeJoint >= 0) { - // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. - const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; - return scaleFactor * (height - height * ratio); - } else if (headTopJoint >= 0) { - // Measure from HeadTop_End joint to the ground, then remove forehead distance. - const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; - return scaleFactor * (headHeight - headHeight * ratio); - } else if (headJoint >= 0) { - // Measure Head joint to the ground, then add in distance from neck to eye. - const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; - const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; - return scaleFactor * (neckHeight + neckHeight * ratio); - } else { - return DEFAULT_AVATAR_EYE_HEIGHT; - } + return _skeletonModel->getRig().getUnscaledEyeHeight(); } else { return DEFAULT_AVATAR_EYE_HEIGHT; } diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 454e8b136a..7050393221 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include "paintStroke_Shared.slh" using namespace render; @@ -29,13 +31,6 @@ gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr; static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _texture = DependencyManager::get()->getTexture(DEFAULT_POLYLINE_TEXTURE); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index b61bb2cbda..20837070d8 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -19,6 +19,8 @@ #include "RenderPipelines.h" +#include + //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT #include @@ -30,13 +32,6 @@ using namespace render::entities; // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - static_assert(shader::render_utils::program::simple != 0, "Validate simple program exists"); static_assert(shader::render_utils::program::simple_transparent != 0, "Validate simple transparent program exists"); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index dfc9277bf0..107847826c 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -19,6 +19,8 @@ #include "GLMHelpers.h" +#include + using namespace render; using namespace render::entities; @@ -160,6 +162,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { glm::vec4 backgroundColor; Transform modelTransform; glm::vec3 dimensions; + bool forwardRendered; withReadLock([&] { modelTransform = _renderTransform; dimensions = _dimensions; @@ -169,6 +172,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created); backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created); + forwardRendered = _renderLayer != RenderLayer::WORLD || DISABLE_DEFERRED; }); // Render background @@ -188,7 +192,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { if (backgroundColor.a > 0.0f) { batch.setModelTransform(transformToTopLeft); auto geometryCache = DependencyManager::get(); - geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false); + geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forwardRendered); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); } @@ -199,7 +203,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { batch.setModelTransform(transformToTopLeft); glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin)); - _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale); + _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forwardRendered); } } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 40bba5a0df..b6b36704c8 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -147,6 +147,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_EDITION_NUMBER; requestedProperties += PROP_ENTITY_INSTANCE_NUMBER; requestedProperties += PROP_CERTIFICATE_ID; + requestedProperties += PROP_CERTIFICATE_TYPE; requestedProperties += PROP_STATIC_CERTIFICATE_VERSION; return requestedProperties; @@ -337,6 +338,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, getCertificateType()); APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, @@ -942,6 +944,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, QString, setCertificateType); READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, @@ -1381,6 +1384,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateType, getCertificateType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion); // Script local data @@ -1529,6 +1533,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateType, setCertificateType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion); if (updateQueryAACube()) { @@ -3150,6 +3155,7 @@ DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID) DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber) DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber) DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID) +DEFINE_PROPERTY_ACCESSOR(QString, CertificateType, certificateType) DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVersion) uint32_t EntityItem::getDirtyFlags() const { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index f8c9c3b6f7..a9a8baa413 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -369,6 +369,8 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); + QString getCertificateType() const; + void setCertificateType(const QString& value); quint32 getStaticCertificateVersion() const; void setStaticCertificateVersion(const quint32&); @@ -653,6 +655,7 @@ protected: QString _itemLicense { ENTITY_ITEM_DEFAULT_ITEM_LICENSE }; quint32 _limitedRun { ENTITY_ITEM_DEFAULT_LIMITED_RUN }; QString _certificateID { ENTITY_ITEM_DEFAULT_CERTIFICATE_ID }; + QString _certificateType { ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE }; quint32 _editionNumber { ENTITY_ITEM_DEFAULT_EDITION_NUMBER }; quint32 _entityInstanceNumber { ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER }; QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 80a6a90841..3efedf02ec 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -545,6 +545,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber); CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID); + CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_TYPE, certificateType); CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); // Location data for scripts @@ -1644,6 +1645,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EDITION_NUMBER, editionNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_TYPE, certificateType); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); // Local props for scripts @@ -2054,6 +2056,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateType, QString, setCertificateType); COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion); // Script location data @@ -2335,6 +2338,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(editionNumber); COPY_PROPERTY_IF_CHANGED(entityInstanceNumber); COPY_PROPERTY_IF_CHANGED(certificateID); + COPY_PROPERTY_IF_CHANGED(certificateType); COPY_PROPERTY_IF_CHANGED(staticCertificateVersion); // Local props for scripts @@ -2649,6 +2653,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString); + ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_TYPE, CertificateType, certificateType, QString); ADD_PROPERTY_TO_MAP(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32); // Local script props @@ -3094,6 +3099,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, properties.getCertificateType()); APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion()); if (properties.getType() == EntityTypes::ParticleEffect) { @@ -3573,6 +3579,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_TYPE, QString, setCertificateType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); if (properties.getType() == EntityTypes::ParticleEffect) { @@ -3982,6 +3989,7 @@ void EntityItemProperties::markAllChanged() { _editionNumberChanged = true; _entityInstanceNumberChanged = true; _certificateIDChanged = true; + _certificateTypeChanged = true; _staticCertificateVersionChanged = true; // Common @@ -4443,6 +4451,9 @@ QList EntityItemProperties::listChangedProperties() { if (certificateIDChanged()) { out += "certificateID"; } + if (certificateTypeChanged()) { + out += "certificateType"; + } if (staticCertificateVersionChanged()) { out += "staticCertificateVersion"; } @@ -4879,6 +4890,9 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { if (!getAnimation().getURL().isEmpty()) { json["animationURL"] = getAnimation().getURL(); } + if (staticCertificateVersion >= 3) { + ADD_STRING_PROPERTY(certificateType, CertificateType); + } ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); ADD_INT_PROPERTY(editionNumber, EditionNumber); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index bc1784c93b..d030f4f2e4 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -226,6 +226,7 @@ public: DEFINE_PROPERTY_REF(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32, ENTITY_ITEM_DEFAULT_EDITION_NUMBER); DEFINE_PROPERTY_REF(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32, ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER); DEFINE_PROPERTY_REF(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_ID); + DEFINE_PROPERTY_REF(PROP_CERTIFICATE_TYPE, CertificateType, certificateType, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE); DEFINE_PROPERTY_REF(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32, ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION); // these are used when bouncing location data into and out of scripts @@ -630,6 +631,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, EditionNumber, editionNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityInstanceNumber, entityInstanceNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateType, certificateType, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalPosition, localPosition, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 4d7a34bea5..be3c566724 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -41,6 +41,7 @@ const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString(""); const quint32 ENTITY_ITEM_DEFAULT_EDITION_NUMBER = 0; const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0; const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString(""); +const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE = QString(""); const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const glm::u8vec3 ENTITY_ITEM_DEFAULT_COLOR = { 255, 255, 255 }; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 6d1c3a1df8..969fd007f1 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -98,6 +98,7 @@ enum EntityPropertyList { PROP_EDITION_NUMBER, PROP_ENTITY_INSTANCE_NUMBER, PROP_CERTIFICATE_ID, + PROP_CERTIFICATE_TYPE, PROP_STATIC_CERTIFICATE_VERSION, // Used to convert values to and from scripts diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index fffcd943c3..d64c8870eb 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -42,6 +42,7 @@ static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour +static const QString DOMAIN_UNLIMITED = "domainUnlimited"; EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) @@ -300,7 +301,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { // Delete an already-existing entity from the tree if it has the same // CertificateID as the entity we're trying to add. - if (!existingEntityItemID.isNull()) { + if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID" << existingEntityItemID << ". Deleting existing entity."; deleteEntity(existingEntityItemID, true); @@ -1870,7 +1871,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c failedAdd = true; qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID() << "] attempted to add a certified entity with ID:" << entityItemID; - } else if (isClone && isCertified) { + } else if (isClone && isCertified && !properties.getCertificateType().contains(DOMAIN_UNLIMITED)) { failedAdd = true; qCDebug(entities) << "User attempted to clone certified entity from entity ID:" << entityIDToClone; } else if (isClone && !isCloneable) { diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index a2161caec9..6aa09c4d0f 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -25,6 +25,8 @@ #include #include +#include "TGAReader.h" + #include "ImageLogging.h" using namespace gpu; @@ -203,6 +205,16 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + content.open(QIODevice::ReadOnly); + + if (filenameExtension == "tga") { + QImage image = image::readTGA(content); + if (!image.isNull()) { + return image; + } + content.reset(); + } + QImageReader imageReader(&content, filenameExtension.c_str()); if (imageReader.canRead()) { diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp new file mode 100644 index 0000000000..897d565eba --- /dev/null +++ b/libraries/image/src/image/TGAReader.cpp @@ -0,0 +1,202 @@ +// +// TGAReader.cpp +// image/src/image +// +// Created by Ryan Huffman +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TGAReader.h" + +#include "ImageLogging.h" + +#include +#include + +QImage image::readTGA(QIODevice& content) { + enum class TGAImageType : uint8_t { + NoImageData = 0, + UncompressedColorMapped = 1, + UncompressedTrueColor = 2, + UncompressedBlackWhite = 3, + RunLengthEncodedColorMapped = 9, + RunLengthEncodedTrueColor = 10, + RunLengthEncodedBlackWhite = 11, + }; + + struct TGAHeader { + uint8_t idLength; + uint8_t colorMapType; + TGAImageType imageType; + struct { + uint64_t firstEntryIndex : 16; + uint64_t length : 16; + uint64_t entrySize : 8; + } colorMap; + uint16_t xOrigin; + uint16_t yOrigin; + uint16_t width; + uint16_t height; + uint8_t pixelDepth; + struct { + uint8_t attributeBitsPerPixel : 4; + uint8_t reserved : 1; + uint8_t orientation : 1; + uint8_t padding : 2; + } imageDescriptor; + }; + + constexpr bool WANT_DEBUG { false }; + constexpr qint64 TGA_HEADER_SIZE_BYTES { 18 }; + + // BottomLeft: 0, TopLeft: 1 + constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 }; + + TGAHeader header; + + if (content.isSequential()) { + qWarning(imagelogging) << "TGA - Sequential devices are not supported for reading"; + return QImage(); + } + + if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) { + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + content.read((char*)&header.idLength, 1); + content.read((char*)&header.colorMapType, 1); + content.read((char*)&header.imageType, 1); + content.read((char*)&header.colorMap, 5); + content.read((char*)&header.xOrigin, 2); + content.read((char*)&header.yOrigin, 2); + content.read((char*)&header.width, 2); + content.read((char*)&header.height, 2); + content.read((char*)&header.pixelDepth, 1); + content.read((char*)&header.imageDescriptor, 1); + + if (WANT_DEBUG) { + qDebug(imagelogging) << "Id Length: " << (int)header.idLength; + qDebug(imagelogging) << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; + qDebug(imagelogging) << "Color map type: " << (int)header.colorMapType; + qDebug(imagelogging) << "Image type: " << (int)header.imageType; + qDebug(imagelogging) << "Origin: " << header.xOrigin << header.yOrigin; + qDebug(imagelogging) << "Size: " << header.width << header.height; + qDebug(imagelogging) << "Depth: " << header.pixelDepth; + qDebug(imagelogging) << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; + } + + if (header.xOrigin != 0 || header.yOrigin != 0) { + qWarning(imagelogging) << "TGA - origin not supporter"; + return QImage(); + } + + if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) { + qWarning(imagelogging) << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; + return QImage(); + } + + if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) { + qWarning(imagelogging) << "TGA - Only 0 or 8 bits for the alpha channel is supported"; + return QImage(); + } + + char alphaMask = header.imageDescriptor.attributeBitsPerPixel == 8 ? 0x00 : 0xFF; + int bytesPerPixel = header.pixelDepth / 8; + + content.skip(header.idLength); + if (header.imageType == TGAImageType::UncompressedTrueColor) { + qint64 minimumSize = header.width * header.height * bytesPerPixel; + if (content.bytesAvailable() < minimumSize) { + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + char* line; + for (int y = 0; y < header.height; ++y) { + if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) { + line = (char*)image.scanLine(header.height - y - 1); + } else { + line = (char*)image.scanLine(y); + } + for (int x = 0; x < header.width; ++x) { + content.read(line, bytesPerPixel); + *(line + 3) |= alphaMask; + + line += 4; + } + } + return image; + } else if (header.imageType == TGAImageType::RunLengthEncodedTrueColor) { + QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + + for (int y = 0; y < header.height; ++y) { + char* line; + if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) { + line = (char*)image.scanLine(header.height - y - 1); + } else { + line = (char*)image.scanLine(y); + } + int col = 0; + while (col < header.width) { + constexpr char IS_REPETITION_MASK{ (char)0x80 }; + constexpr char LENGTH_MASK{ (char)0x7f }; + char repetition; + if (content.read(&repetition, 1) != 1) { + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + bool isRepetition = repetition & IS_REPETITION_MASK; + + // The length in `repetition` is always 1 less than the number of following pixels, + // so we need to increment it by 1. Because of this, the length is never 0. + int length = (repetition & LENGTH_MASK) + 1; + + if (isRepetition) { + // Read into temporary buffer + char color[4]; + if (content.read(color, bytesPerPixel) != bytesPerPixel) { + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + color[3] |= alphaMask; + + // Copy `length` number of times + col += length; + while (length-- > 0) { + *line = color[0]; + *(line + 1) = color[1]; + *(line + 2) = color[2]; + *(line + 3) = color[3]; + + line += 4; + } + } else { + qint64 minimumSize = length * bytesPerPixel; + if (content.bytesAvailable() < minimumSize) { + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + // Read in `length` number of pixels + col += length; + while (length-- > 0) { + content.read(line, bytesPerPixel); + *(line + 3) |= alphaMask; + + line += 4; + } + } + } + } + return image; + } else { + qWarning(imagelogging) << "TGA - Unsupported image type: " << (int)header.imageType; + } + + return QImage(); +} diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h new file mode 100644 index 0000000000..8019be7f0b --- /dev/null +++ b/libraries/image/src/image/TGAReader.h @@ -0,0 +1,24 @@ +// +// TGAReader.h +// image/src/image +// +// Created by Ryan Huffman +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_image_TGAReader_h +#define hifi_image_TGAReader_h + +#include + +namespace image { + +// TODO Move this into a plugin that QImageReader can use +QImage readTGA(QIODevice& contents); + +} + +#endif // hifi_image_TGAReader_h diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index a896766058..b8bcdb386e 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -29,8 +29,14 @@ QMap getJointNameMapping(const QVariantHash& mapping) { QMap getJointRotationOffsets(const QVariantHash& mapping) { QMap jointRotationOffsets; static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; - if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { - auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; + if (!mapping.isEmpty() && ((mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) || (mapping.contains(JOINT_ROTATION_OFFSET2_FIELD) && mapping[JOINT_ROTATION_OFFSET2_FIELD].type() == QVariant::Hash))) { + QHash offsets; + if (mapping.contains(JOINT_ROTATION_OFFSET_FIELD)) { + offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + } else { + offsets = mapping[JOINT_ROTATION_OFFSET2_FIELD].toHash(); + } for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); QString line = itr.value().toString(); @@ -57,6 +63,15 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointRotationOffsets = output.edit1(); auto& jointIndices = output.edit2(); + bool newJointRot = false; + static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; + QVariantHash fstHashMap = mapping.second; + if (fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD)) { + newJointRot = true; + } else { + newJointRot = false; + } + // Get joint renames auto jointNameMapping = getJointNameMapping(mapping.second); // Apply joint metadata from FST file mappings @@ -64,11 +79,12 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu jointsOut.push_back(jointIn); auto& jointOut = jointsOut.back(); - auto jointNameMapKey = jointNameMapping.key(jointIn.name); - if (jointNameMapping.contains(jointNameMapKey)) { - jointOut.name = jointNameMapKey; + if (!newJointRot) { + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + jointOut.name = jointNameMapKey; + } } - jointIndices.insert(jointOut.name, (int)jointsOut.size()); } @@ -77,10 +93,33 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); int jointIndex = jointIndices.value(jointName) - 1; - if (jointIndex != -1) { + if (jointIndex >= 0) { glm::quat rotationOffset = itr.value(); jointRotationOffsets.insert(jointIndex, rotationOffset); qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; } } + + if (newJointRot) { + for (const auto& jointIn : jointsIn) { + + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + int mappedIndex = jointIndices.value(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + + // delete and replace with hifi name + jointIndices.remove(jointIn.name); + jointIndices.insert(jointNameMapKey, mappedIndex); + } else { + + // nothing mapped to this fbx joint name + if (jointNameMapping.contains(jointIn.name)) { + // but the name is in the list of hifi names is mapped to a different joint + int extraIndex = jointIndices.value(jointIn.name); + jointIndices.remove(jointIn.name); + jointIndices.insert("", extraIndex); + } + } + } + } } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 1489f8e16c..0ec7c40ca4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -265,6 +265,7 @@ enum class EntityVersion : PacketVersion { WebBillboardMode, ModelScale, ReOrderParentIDProperties, + CertificateTypeProperty, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 7d29b9e792..e322dc9d2b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -35,10 +35,10 @@ #include "StencilMaskPass.h" #include "FadeEffect.h" - - #include "DeferredLightingEffect.h" +#include + namespace gr { using graphics::slot::texture::Texture; using graphics::slot::buffer::Buffer; @@ -49,13 +49,6 @@ namespace ru { using render_utils::slot::buffer::Buffer; } -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - //#define WANT_DEBUG // @note: Originally size entity::NUM_SHAPES diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 2d507ba21b..93edc4217d 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -67,11 +67,11 @@ float TextRenderer3D::getFontSize() const { } void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds) { + const glm::vec2& bounds, bool forwardRendered) { // The font does all the OpenGL work if (_font) { _color = color; - _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, forwardRendered); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 97b74fcabd..b6475ab0ed 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -39,7 +39,7 @@ public: float getFontSize() const; // Pixel size void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + const glm::vec2& bounds = glm::vec2(-1.0f), bool forwardRendered = false); private: TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false, diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index 04ee44510a..5f4df86d56 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -19,6 +19,7 @@ // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; void main() { _texCoord01.xy = inTexCoord0.xy; @@ -26,7 +27,7 @@ void main() { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> const vec3 normal = vec3(0, 0, 1); <$transformModelToWorldDir(cam, obj, normal, _normalWS)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 2fbfb2ca43..311c849915 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -10,7 +10,14 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -<@include DeferredBufferWrite.slh@> +<@include DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlended()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> LAYOUT(binding=0) uniform sampler2D Font; @@ -24,12 +31,14 @@ LAYOUT(binding=0) uniform textParamsBuffer { TextParams params; }; -// the interpolated normal +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord0 _texCoord01.xy #define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor0; + #define TAA_TEXTURE_LOD_BIAS -3.0 const float interiorCutoff = 0.8; @@ -57,9 +66,24 @@ void main() { a += evalSDF(_texCoord0 + dxTexCoord + dyTexCoord); a *= 0.25; - packDeferredFragmentTranslucent( + float alpha = a * params.color.a; + if (alpha <= 0.0) { + discard; + } + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _positionES.xyz; + + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, normalize(_normalWS), - a * params.color.a, params.color.rgb, - DEFAULT_ROUGHNESS); + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_ROUGHNESS, alpha), + alpha); } \ No newline at end of file diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index cc451eeedc..e0e99da020 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -343,7 +343,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm } void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds) { + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool forwardRendered) { if (str == "") { return; } @@ -370,7 +370,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString } // need the gamma corrected color here - batch.setPipeline((color.a < 1.0f) ? _transparentPipeline : _pipeline); + batch.setPipeline(forwardRendered || (color.a < 1.0f) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 85f692f5d8..26cc4e46c3 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -46,7 +46,7 @@ public: // Render string to batch void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, EffectType effectType, - const glm::vec2& origin, const glm::vec2& bound); + const glm::vec2& origin, const glm::vec2& bound, bool forwardRendered); static Pointer load(const QString& family); diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 8e54d2d5de..65d71e46e6 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -88,3 +88,43 @@ bool AudioScriptingInterface::isStereoInput() { } return stereoEnabled; } + +bool AudioScriptingInterface::getServerEcho() { + bool serverEchoEnabled = false; + if (_localAudioInterface) { + serverEchoEnabled = _localAudioInterface->getServerEcho(); + } + return serverEchoEnabled; +} + +void AudioScriptingInterface::setServerEcho(bool serverEcho) { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho)); + } +} + +void AudioScriptingInterface::toggleServerEcho() { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho"); + } +} + +bool AudioScriptingInterface::getLocalEcho() { + bool localEchoEnabled = false; + if (_localAudioInterface) { + localEchoEnabled = _localAudioInterface->getLocalEcho(); + } + return localEchoEnabled; +} + +void AudioScriptingInterface::setLocalEcho(bool localEcho) { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho)); + } +} + +void AudioScriptingInterface::toggleLocalEcho() { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho"); + } +} diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index d2f886d2dd..a6801dcdcb 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -66,6 +66,39 @@ public: _localAudioInterface->getAudioSolo().reset(); } + /**jsdoc + * @function Audio.getServerEcho + */ + Q_INVOKABLE bool getServerEcho(); + + /**jsdoc + * @function Audio.setServerEcho + * @parm {boolean} serverEcho + */ + Q_INVOKABLE void setServerEcho(bool serverEcho); + + /**jsdoc + * @function Audio.toggleServerEcho + */ + Q_INVOKABLE void toggleServerEcho(); + + /**jsdoc + * @function Audio.getLocalEcho + */ + Q_INVOKABLE bool getLocalEcho(); + + /**jsdoc + * @function Audio.setLocalEcho + * @parm {boolean} localEcho + */ + Q_INVOKABLE void setLocalEcho(bool localEcho); + + /**jsdoc + * @function Audio.toggleLocalEcho + */ + Q_INVOKABLE void toggleLocalEcho(); + + protected: AudioScriptingInterface() = default; diff --git a/libraries/shared/src/DisableDeferred.h b/libraries/shared/src/DisableDeferred.h new file mode 100644 index 0000000000..9a1f9be117 --- /dev/null +++ b/libraries/shared/src/DisableDeferred.h @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman on 3/7/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DisableDeferred_h +#define hifi_DisableDeferred_h + +#include +#include + +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif + + +#endif // hifi_DisableDeferred_h + diff --git a/libraries/shared/src/shared/GlobalAppProperties.cpp b/libraries/shared/src/shared/GlobalAppProperties.cpp index 54e50da3ea..1fd6c191b2 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.cpp +++ b/libraries/shared/src/shared/GlobalAppProperties.cpp @@ -14,6 +14,7 @@ namespace hifi { namespace properties { const char* STEAM = "com.highfidelity.launchedFromSteam"; const char* LOGGER = "com.highfidelity.logger"; const char* OCULUS_STORE = "com.highfidelity.oculusStore"; + const char* STANDALONE = "com.highfidelity.standalone"; const char* TEST = "com.highfidelity.test"; const char* TRACING = "com.highfidelity.tracing"; const char* HMD = "com.highfidelity.hmd"; diff --git a/libraries/shared/src/shared/GlobalAppProperties.h b/libraries/shared/src/shared/GlobalAppProperties.h index 174be61939..6809d5530a 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.h +++ b/libraries/shared/src/shared/GlobalAppProperties.h @@ -16,6 +16,7 @@ namespace hifi { namespace properties { extern const char* STEAM; extern const char* LOGGER; extern const char* OCULUS_STORE; + extern const char* STANDALONE; extern const char* TEST; extern const char* TRACING; extern const char* HMD; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index c2b9145f3c..8d97ff78af 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -30,6 +30,7 @@ using namespace hifi; const char* OculusControllerManager::NAME = "Oculus"; +const QString OCULUS_LAYOUT = "OculusConfiguration.qml"; const quint64 LOST_TRACKING_DELAY = 3000000; @@ -43,6 +44,22 @@ bool OculusControllerManager::activate() { return true; } +QString OculusControllerManager::configurationLayout() { + return OCULUS_LAYOUT; +} + +void OculusControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) { + if (configurationSettings.contains("trackControllersInOculusHome")) { + _touch->_trackControllersInOculusHome.set(configurationSettings["trackControllersInOculusHome"].toBool()); + } +} + +QJsonObject OculusControllerManager::configurationSettings() { + QJsonObject configurationSettings; + configurationSettings["trackControllersInOculusHome"] = _touch->_trackControllersInOculusHome.get(); + return configurationSettings; +} + void OculusControllerManager::checkForConnectedDevices() { if (_touch && _remote) { return; @@ -215,13 +232,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, quint64 currentTime = usecTimestampNow(); static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked; bool hasInputFocus = ovr::hasInputFocus(); + bool trackControllersInOculusHome = _trackControllersInOculusHome.get(); auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false); ovr::for_each_hand([&](ovrHandType hand) { ++numTrackedControllers; int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) - if (!hasInputFocus) { + if (!hasInputFocus && !trackControllersInOculusHome) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index ee06115b26..ea32eace61 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -15,6 +15,7 @@ #include +#include #include #include @@ -28,7 +29,11 @@ public: const QString getName() const override { return NAME; } bool isHandController() const override { return _touch != nullptr; } bool isHeadController() const override { return true; } + bool configurable() override { return true; } + QString configurationLayout() override; QStringList getSubdeviceNames() override; + void setConfigurationSettings(const QJsonObject configurationSetting) override; + QJsonObject configurationSettings() override; bool activate() override; void deactivate() override; @@ -93,6 +98,7 @@ private: float _leftHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; + Setting::Handle _trackControllersInOculusHome { "trackControllersInOculusHome", false }; mutable std::recursive_mutex _lock; std::map _lostTracking; std::map _regainTrackingDeadline; diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 731d62017d..c597f75bca 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -14,7 +14,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE var utilsPath = Script.resolvePath('../developer/libraries/utils.js'); Script.include(utilsPath); @@ -67,7 +67,7 @@ } function hasOverlay() { - return Overlays.getProperty(overlayID, "position") !== undefined; + return Overlays.getProperty(overlayID, "position") !== undefined; } function updateOverlay() { @@ -101,4 +101,4 @@ Audio.muted.disconnect(onMuteToggled); Script.update.disconnect(update); } -}()); // END LOCAL_SCOPE +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index 5381d39116..e0f4aba84a 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -48,10 +48,11 @@ propsAreCloneDynamic = function(props) { cloneEntity = function(props) { var entityToClone = props.id; - var certificateID = Entities.getEntityProperties(entityToClone, ['certificateID']).certificateID; + var props = Entities.getEntityProperties(entityToClone, ['certificateID', 'certificateType']) + var certificateID = props.certificateID; // ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits // will now be handled by the server where the entity add will fail if limit reached - if (entityIsCloneable(props) && (certificateID === undefined || certificateID.length === 0)) { + if (entityIsCloneable(props) && (!!certificateID || props.certificateType.indexOf('domainUnlimited') >= 0)) { var cloneID = Entities.cloneEntity(entityToClone); return cloneID; } diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 8160ad8aa0..c25a962824 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -14,7 +14,7 @@ using System.Collections.Generic; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.3.1"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.3"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; @@ -364,7 +364,14 @@ class AvatarExporter : MonoBehaviour { " the Rig section of it's Inspector window.", "Ok"); return; } - + + // if the rig is optimized we should de-optimize it during the export process + bool shouldDeoptimizeGameObjects = modelImporter.optimizeGameObjects; + if (shouldDeoptimizeGameObjects) { + modelImporter.optimizeGameObjects = false; + modelImporter.SaveAndReimport(); + } + humanDescription = modelImporter.humanDescription; string textureWarnings = SetTextureDependencies(); SetBoneAndMaterialInformation(); @@ -375,6 +382,15 @@ class AvatarExporter : MonoBehaviour { // format resulting avatar rule failure strings // consider export-blocking avatar rules to be errors and show them in an error dialog, // and also include any other avatar rule failures plus texture warnings as warnings in the dialog + if (shouldDeoptimizeGameObjects) { + // switch back to optimized game object in case it was originally optimized + modelImporter.optimizeGameObjects = true; + modelImporter.SaveAndReimport(); + } + + // format resulting bone rule failure strings + // consider export-blocking bone rules to be errors and show them in an error dialog, + // and also include any other bone rule failures plus texture warnings as warnings in the dialog string boneErrors = ""; string warnings = ""; foreach (var failedAvatarRule in failedAvatarRules) { @@ -621,13 +637,10 @@ class AvatarExporter : MonoBehaviour { // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones Quaternion jointOffset = new Quaternion(); - string outputJointName = ""; if (userBoneInfo.HasHumanMapping()) { - outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.humanName]; Quaternion rotation = REFERENCE_ROTATIONS[userBoneInfo.humanName]; jointOffset = Quaternion.Inverse(userBoneInfo.rotation) * rotation; } else { - outputJointName = userBoneName; jointOffset = Quaternion.Inverse(userBoneInfo.rotation); string lastRequiredParent = FindLastRequiredAncestorBone(userBoneName); if (lastRequiredParent != "root") { @@ -639,11 +652,9 @@ class AvatarExporter : MonoBehaviour { } // swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst - if (!string.IsNullOrEmpty(outputJointName)) { - jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); - File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + - jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); - } + jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); + File.AppendAllText(exportFstPath, "jointRotationOffset2 = " + userBoneName + " = (" + jointOffset.x + ", " + + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); } // if there is any material data to save then write out all materials in JSON material format to the materialMap field diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index b1e8a5c537..402719b497 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.3.1 +Version 0.3.3 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 281cc80ddb..f7385e3831 100644 Binary files a/tools/unity-avatar-exporter/avatarExporter.unitypackage and b/tools/unity-avatar-exporter/avatarExporter.unitypackage differ