diff --git a/examples/dialTone.js b/examples/dialTone.js new file mode 100644 index 0000000000..0748d0ba94 --- /dev/null +++ b/examples/dialTone.js @@ -0,0 +1,23 @@ +// +// dialTone.js +// examples +// +// Created by Stephen Birarda on 06/08/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// setup the local sound we're going to use +var connectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/short1.wav"); + +// setup the options needed for that sound +var connectSoundOptions = { + localOnly: true +} + +// play the sound locally once we get the first audio packet from a mixer +Audio.receivedFirstPacket.connect(function(){ + Audio.playSound(connectSound, connectSoundOptions); +}); diff --git a/examples/example/dynamicLandscape.js b/examples/example/dynamicLandscape.js new file mode 100644 index 0000000000..ad247963fd --- /dev/null +++ b/examples/example/dynamicLandscape.js @@ -0,0 +1,89 @@ + +// dynamicLandscape.js +// examples +// +// Created by Eric Levin on June 8 +// Copyright 2015 High Fidelity, Inc. +// +// Meditative ocean landscape +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +Script.include(HIFI_PUBLIC_BUCKET + 'scripts/utilities.js') + +var NUM_ROWS = 10; +var CUBE_SIZE = 1; +var cubes = []; +var cubesSettings = []; +var time = 0; + +var OMEGA = 2.0 * Math.PI/8; +var RANGE = CUBE_SIZE/2; + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(CUBE_SIZE* 10, Quat.getFront(Camera.getOrientation()))); + + +for (var x = 0, rowIndex = 0; x < NUM_ROWS * CUBE_SIZE; x += CUBE_SIZE, rowIndex++) { + for (var z = 0, columnIndex = 0; z < NUM_ROWS * CUBE_SIZE; z += CUBE_SIZE, columnIndex++) { + + var baseHeight = map( columnIndex + 1, 1, NUM_ROWS, -CUBE_SIZE * 2, -CUBE_SIZE); + var relativePosition = { + x: x, + y: baseHeight, + z: z + }; + var position = Vec3.sum(center, relativePosition); + cubes.push(Entities.addEntity({ + type: 'Box', + position: position, + dimensions: { + x: CUBE_SIZE, + y: CUBE_SIZE, + z: CUBE_SIZE + } + })); + + var phase = map( (columnIndex + 1) * (rowIndex + 1), 2, NUM_ROWS * NUM_ROWS, Math.PI * 2, Math.PI * 4); + cubesSettings.push({ + baseHeight: center.y + baseHeight, + phase: phase + }) + } +} + +function update(deleteTime) { + time += deleteTime; + for (var i = 0; i < cubes.length; i++) { + var phase = cubesSettings[i].phase; + var props = Entities.getEntityProperties(cubes[i]); + var newHeight = Math.sin(time * OMEGA + phase) / 2.0; + var hue = map(newHeight, -.5, .5, 0.5, 0.7); + var light = map(newHeight, -.5, .5, 0.4, 0.6) + newHeight = cubesSettings[i].baseHeight + (newHeight * RANGE); + var newVelocityY = Math.cos(time * OMEGA + phase) / 2.0 * RANGE * OMEGA; + + var newPosition = props.position; + var newVelocity = props.velocity; + + newPosition.y = newHeight; + newVelocity = newVelocityY; + Entities.editEntity( cubes[i], { + position: newPosition, + velocity: props.velocity, + color: hslToRgb({hue: hue, sat: 0.7, light: light}) + }); + } +} + +function cleanup() { + cubes.forEach(function(cube) { + Entities.deleteEntity(cube); + }) +} + +Script.update.connect(update); +Script.scriptEnding.connect(cleanup) + + diff --git a/examples/utilities.js b/examples/utilities.js new file mode 100644 index 0000000000..3844e23e14 --- /dev/null +++ b/examples/utilities.js @@ -0,0 +1,56 @@ +// utilities.js +// examples +// +// Created by Eric Levin on June 8 +// Copyright 2015 High Fidelity, Inc. +// +// Common utilities +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +function hslToRgb(hslColor) { + var h = hslColor.hue; + var s = hslColor.sat; + var l = hslColor.light; + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + var hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return { + red: Math.round(r * 255), + green: Math.round(g * 255), + blue: Math.round(b * 255) + }; + +} + +function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} + +function randFloat(low, high) { + return low + Math.random() * (high - low); +} + + +function randInt(low, high) { + return Math.floor(randFloat(low, high)); +} \ No newline at end of file diff --git a/interface/resources/images/hifi-logo-blackish.svg b/interface/resources/images/hifi-logo-blackish.svg new file mode 100644 index 0000000000..60bfb3d418 --- /dev/null +++ b/interface/resources/images/hifi-logo-blackish.svg @@ -0,0 +1,123 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/images/login-close.svg b/interface/resources/images/login-close.svg new file mode 100644 index 0000000000..2fb10c241b --- /dev/null +++ b/interface/resources/images/login-close.svg @@ -0,0 +1,55 @@ + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/images/login-password.svg b/interface/resources/images/login-password.svg new file mode 100644 index 0000000000..98d86f6d8a --- /dev/null +++ b/interface/resources/images/login-password.svg @@ -0,0 +1,58 @@ + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/images/login-username.svg b/interface/resources/images/login-username.svg new file mode 100644 index 0000000000..a40dd91cf7 --- /dev/null +++ b/interface/resources/images/login-username.svg @@ -0,0 +1,51 @@ + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 91e05d020d..3377b20d87 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -9,18 +9,16 @@ // import Hifi 1.0 -import QtQuick 2.3 -import QtQuick.Controls 1.2 +import QtQuick 2.4 import "controls" import "styles" -Item { +DialogContainer { id: root HifiConstants { id: hifi } objectName: "AddressBarDialog" - property int animationDuration: hifi.effects.fadeInDuration property bool destroyOnInvisible: false property real scale: 1.25 // Make this dialog a little larger than normal @@ -101,47 +99,16 @@ Item { } } - // The UI enables an object, rather than manipulating its visibility, so that we can do animations in both directions. - // Because visibility and enabled are booleans, they cannot be animated. So when enabled is changed, we modify a property - // that can be animated, like scale or opacity, and then when the target animation value is reached, we can modify the - // visibility. - enabled: false - opacity: 0.0 - onEnabledChanged: { - opacity = enabled ? 1.0 : 0.0 if (enabled) { - addressLine.forceActiveFocus(); + addressLine.forceActiveFocus() } } - Behavior on opacity { - // Animate opacity. - NumberAnimation { - duration: animationDuration - easing.type: Easing.OutCubic - } - } - - onOpacityChanged: { - // Once we're transparent, disable the dialog's visibility. - visible = (opacity != 0.0) - } - onVisibleChanged: { if (!visible) { - reset() - - // Some dialogs should be destroyed when they become invisible. - if (destroyOnInvisible) { - destroy() - } + addressLine.text = "" } - - } - - function reset() { - addressLine.text = "" } function toggleOrGo() { @@ -152,21 +119,18 @@ Item { } } - Keys.onEscapePressed: { - enabled = false - } - Keys.onPressed: { - switch(event.key) { - case Qt.Key_W: - if (event.modifiers == Qt.ControlModifier) { - event.accepted = true - enabled = false - } + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + enabled = false + event.accepted = true + break + case Qt.Key_Enter: + case Qt.Key_Return: + toggleOrGo() + event.accepted = true break } } - - Keys.onReturnPressed: toggleOrGo() - Keys.onEnterPressed: toggleOrGo() } diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 55a0a6a461..947bf739fc 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -4,7 +4,7 @@ import QtWebKit 3.0 import "controls" import "styles" -Dialog { +VrDialog { id: root HifiConstants { id: hifi } title: "Browser" diff --git a/interface/resources/qml/ErrorDialog.qml b/interface/resources/qml/ErrorDialog.qml index c0f8132f14..76d9111d97 100644 --- a/interface/resources/qml/ErrorDialog.qml +++ b/interface/resources/qml/ErrorDialog.qml @@ -8,27 +8,22 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import Hifi 1.0 as Hifi -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Dialogs 1.2 +import Hifi 1.0 +import QtQuick 2.4 import "controls" import "styles" -Item { +DialogContainer { id: root HifiConstants { id: hifi } - property int animationDuration: hifi.effects.fadeInDuration - property bool destroyOnInvisible: true - Component.onCompleted: { enabled = true } onParentChanged: { if (visible && enabled) { - forceActiveFocus(); + forceActiveFocus() } } @@ -38,7 +33,7 @@ Item { x: parent ? parent.width / 2 - width / 2 : 0 y: parent ? parent.height / 2 - height / 2 : 0 - Hifi.ErrorDialog { + ErrorDialog { id: content implicitWidth: box.width @@ -69,7 +64,7 @@ Item { Text { id: messageText - font.pointSize: 10 + font.pixelSize: hifi.fonts.pixelSize * 0.6 font.weight: Font.Bold anchors { @@ -91,55 +86,17 @@ Item { } MouseArea { anchors.fill: parent + cursorShape: "PointingHandCursor" onClicked: { - content.accept(); + content.accept() } } } } } - // The UI enables an object, rather than manipulating its visibility, so that we can do animations in both directions. - // Because visibility and enabled are booleans, they cannot be animated. So when enabled is changed, we modify a property - // that can be animated, like scale or opacity, and then when the target animation value is reached, we can modify the - // visibility. - enabled: false - opacity: 0.0 - - onEnabledChanged: { - opacity = enabled ? 1.0 : 0.0 - } - - Behavior on opacity { - // Animate opacity. - NumberAnimation { - duration: animationDuration - easing.type: Easing.OutCubic - } - } - - onOpacityChanged: { - // Once we're transparent, disable the dialog's visibility. - visible = (opacity != 0.0) - } - - onVisibleChanged: { - if (!visible) { - // Some dialogs should be destroyed when they become invisible. - if (destroyOnInvisible) { - destroy() - } - } - } - Keys.onPressed: { - if (event.modifiers === Qt.ControlModifier) - switch (event.key) { - case Qt.Key_W: - event.accepted = true - content.accept() - break - } else switch (event.key) { + switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: case Qt.Key_Enter: diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index 6b49e6f0c7..012f04f1fd 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -5,7 +5,7 @@ import QtQuick.Controls.Styles 1.3 import QtWebKit 3.0 import "controls" -Dialog { +VrDialog { id: root width: 800 height: 800 diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 5653dfc7a1..8d5267f7f8 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -1,78 +1,173 @@ +// +// LoginDialog.qml +// +// Created by David Rowe on 3 Jun 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import Hifi 1.0 -import QtQuick 2.3 -import QtQuick.Controls.Styles 1.3 +import QtQuick 2.4 import "controls" import "styles" -Dialog { +DialogContainer { + id: root HifiConstants { id: hifi } - title: "Login" + objectName: "LoginDialog" - height: 512 - width: 384 - onVisibleChanged: { - if (!visible) { - reset() - } - } + property bool destroyOnInvisible: false - onEnabledChanged: { - if (enabled) { - username.forceActiveFocus(); - } - } + implicitWidth: loginDialog.implicitWidth + implicitHeight: loginDialog.implicitHeight - function reset() { - username.text = "" - password.text = "" - loginDialog.statusText = "" - } + x: parent ? parent.width / 2 - width / 2 : 0 + y: parent ? parent.height / 2 - height / 2 : 0 + property int maximumX: parent ? parent.width - width : 0 + property int maximumY: parent ? parent.height - height : 0 LoginDialog { id: loginDialog - anchors.fill: parent - anchors.margins: parent.margins - anchors.topMargin: parent.topMargin - Column { - anchors.topMargin: 8 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.top: parent.top - spacing: 8 - Image { - height: 64 - anchors.horizontalCenter: parent.horizontalCenter - width: 64 - source: "../images/hifi-logo.svg" + implicitWidth: backgroundRectangle.width + implicitHeight: backgroundRectangle.height + + readonly property int inputWidth: 500 + readonly property int inputHeight: 60 + readonly property int borderWidth: 30 + readonly property int closeMargin: 16 + readonly property real tan30: 0.577 // tan(30°) + readonly property int inputSpacing: 16 + property int maximumX: parent ? parent.width - width : 0 + property int maximumY: parent ? parent.height - height : 0 + + Rectangle { + id: backgroundRectangle + width: loginDialog.inputWidth + loginDialog.borderWidth * 2 + height: loginDialog.inputHeight * 6 + loginDialog.closeMargin * 2 + radius: loginDialog.closeMargin * 2 + + color: "#2c86b1" + opacity: 0.85 + + MouseArea { + width: parent.width + height: parent.height + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + drag { + target: root + minimumX: 0 + minimumY: 0 + maximumX: root.parent ? root.maximumX : 0 + maximumY: root.parent ? root.maximumY : 0 + } + } + } + + Image { + id: closeIcon + source: "../images/login-close.svg" + width: 20 + height: 20 + anchors { + top: backgroundRectangle.top + right: backgroundRectangle.right + topMargin: loginDialog.closeMargin + rightMargin: loginDialog.closeMargin } - Border { - width: 304 - height: 64 - anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + cursorShape: "PointingHandCursor" + onClicked: { + root.enabled = false + } + } + } + + Column { + id: mainContent + width: loginDialog.inputWidth + spacing: loginDialog.inputSpacing + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + + Item { + // Offset content down a little + width: loginDialog.inputWidth + height: loginDialog.closeMargin + } + + Rectangle { + width: loginDialog.inputWidth + height: loginDialog.inputHeight + radius: height / 2 + color: "#ebebeb" + + Image { + source: "../images/login-username.svg" + width: loginDialog.inputHeight * 0.65 + height: width + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: loginDialog.inputHeight / 4 + } + } + TextInput { id: username - anchors.fill: parent - helperText: "Username or Email" - anchors.margins: 8 + anchors { + fill: parent + leftMargin: loginDialog.inputHeight + rightMargin: loginDialog.inputHeight / 2 + } + + helperText: "username or email" + color: hifi.colors.text + KeyNavigation.tab: password KeyNavigation.backtab: password } } - Border { - width: 304 - height: 64 - anchors.horizontalCenter: parent.horizontalCenter + Rectangle { + width: loginDialog.inputWidth + height: loginDialog.inputHeight + radius: height / 2 + color: "#ebebeb" + + Image { + source: "../images/login-password.svg" + width: loginDialog.inputHeight * 0.65 + height: width + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: loginDialog.inputHeight / 4 + } + } + TextInput { id: password - anchors.fill: parent + anchors { + fill: parent + leftMargin: loginDialog.inputHeight + rightMargin: loginDialog.inputHeight / 2 + } + + helperText: "password" echoMode: TextInput.Password - helperText: "Password" - anchors.margins: 8 + color: hifi.colors.text + KeyNavigation.tab: username KeyNavigation.backtab: username onFocusChanged: { @@ -83,102 +178,176 @@ Dialog { } } - Text { - anchors.horizontalCenter: parent.horizontalCenter - textFormat: Text.StyledText - width: parent.width - height: 96 - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: loginDialog.statusText - } - } + Item { + width: loginDialog.inputWidth + height: loginDialog.inputHeight / 2 - Column { - anchors.bottomMargin: 5 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.bottom: parent.bottom + Text { + id: messageText + + visible: loginDialog.statusText != "" && loginDialog.statusText != "Logging in..." + + width: loginDialog.inputWidth + height: loginDialog.inputHeight / 2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + text: loginDialog.statusText + color: "white" + } + + Row { + id: messageSpinner + + visible: loginDialog.statusText == "Logging in..." + onVisibleChanged: visible ? messageSpinnerAnimation.restart() : messageSpinnerAnimation.stop() + + spacing: 24 + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + + Rectangle { + id: spinner1 + width: 10 + height: 10 + color: "#ebebeb" + opacity: 0.05 + } + + Rectangle { + id: spinner2 + width: 10 + height: 10 + color: "#ebebeb" + opacity: 0.05 + } + + Rectangle { + id: spinner3 + width: 10 + height: 10 + color: "#ebebeb" + opacity: 0.05 + } + + SequentialAnimation { + id: messageSpinnerAnimation + running: messageSpinner.visible + loops: Animation.Infinite + NumberAnimation { target: spinner1; property: "opacity"; to: 1.0; duration: 1000 } + NumberAnimation { target: spinner2; property: "opacity"; to: 1.0; duration: 1000 } + NumberAnimation { target: spinner3; property: "opacity"; to: 1.0; duration: 1000 } + NumberAnimation { target: spinner1; property: "opacity"; to: 0.05; duration: 0 } + NumberAnimation { target: spinner2; property: "opacity"; to: 0.05; duration: 0 } + NumberAnimation { target: spinner3; property: "opacity"; to: 0.05; duration: 0 } + } + } + } Rectangle { - width: 192 - height: 64 - anchors.horizontalCenter: parent.horizontalCenter - color: hifi.colors.hifiBlue - border.width: 0 - radius: 10 + width: loginDialog.inputWidth + height: loginDialog.inputHeight + radius: height / 2 + color: "#353535" + + TextInput { + anchors.fill: parent + text: "Login" + color: "white" + horizontalAlignment: Text.AlignHCenter + } MouseArea { - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.left + anchors.fill: parent + cursorShape: "PointingHandCursor" onClicked: { loginDialog.login(username.text, password.text) } } + } - Row { - anchors.centerIn: parent + Row { + anchors.horizontalCenter: parent.horizontalCenter + + Text { + text: "Password?" + font.pixelSize: hifi.fonts.pixelSize * 0.8 + font.underline: true + color: "#e0e0e0" + width: loginDialog.inputHeight * 4 + horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter - spacing: 8 + + MouseArea { + anchors.fill: parent + cursorShape: "PointingHandCursor" + onClicked: { + loginDialog.openUrl(loginDialog.rootUrl + "/users/password/new") + } + } + } + + Item { + width: loginDialog.inputHeight + loginDialog.inputSpacing * 2 + height: loginDialog.inputHeight + Image { - id: loginIcon - height: 32 - width: 32 - source: "../images/login.svg" - } - Text { - text: "Login" - color: "white" - width: 64 - height: parent.height + id: hifiIcon + source: "../images/hifi-logo-blackish.svg" + width: loginDialog.inputHeight + height: width + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } } } - } + Text { + text: "Register" + font.pixelSize: hifi.fonts.pixelSize * 0.8 + font.underline: true + color: "#e0e0e0" + width: loginDialog.inputHeight * 4 + horizontalAlignment: Text.AlignLeft + anchors.verticalCenter: parent.verticalCenter - Text { - width: parent.width - height: 24 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text:"Create Account" - font.pointSize: 12 - font.bold: true - color: hifi.colors.hifiBlue - - MouseArea { - anchors.fill: parent - onClicked: { - loginDialog.openUrl(loginDialog.rootUrl + "/signup") - } - } - } - - Text { - width: parent.width - height: 24 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pointSize: 12 - text: "Recover Password" - color: hifi.colors.hifiBlue - - MouseArea { - anchors.fill: parent - onClicked: { - loginDialog.openUrl(loginDialog.rootUrl + "/users/password/new") + MouseArea { + anchors.fill: parent + cursorShape: "PointingHandCursor" + onClicked: { + loginDialog.openUrl(loginDialog.rootUrl + "/signup") + } } } } } } + + onOpacityChanged: { + // Set focus once animation is completed so that focus is set at start-up when not logged in + if (opacity == 1.0) { + username.forceActiveFocus() + } + } + + onVisibleChanged: { + if (!visible) { + username.text = "" + password.text = "" + loginDialog.statusText = "" + } + } + Keys.onPressed: { - switch(event.key) { + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + enabled = false + event.accepted = true + break case Qt.Key_Enter: case Qt.Key_Return: if (username.activeFocus) { @@ -192,7 +361,7 @@ Dialog { loginDialog.login(username.text, password.text) } } - break; + break } } } diff --git a/interface/resources/qml/MarketplaceDialog.qml b/interface/resources/qml/MarketplaceDialog.qml index 58bb3e6183..946f32e84a 100644 --- a/interface/resources/qml/MarketplaceDialog.qml +++ b/interface/resources/qml/MarketplaceDialog.qml @@ -5,7 +5,7 @@ import QtQuick.Controls.Styles 1.3 import QtWebKit 3.0 import "controls" -Dialog { +VrDialog { title: "Test Dlg" id: testDialog objectName: "Browser" diff --git a/interface/resources/qml/MessageDialog.qml b/interface/resources/qml/MessageDialog.qml index dd410b8070..e8b01df9d0 100644 --- a/interface/resources/qml/MessageDialog.qml +++ b/interface/resources/qml/MessageDialog.qml @@ -5,7 +5,7 @@ import QtQuick.Dialogs 1.2 import "controls" import "styles" -Dialog { +VrDialog { id: root HifiConstants { id: hifi } property real spacing: hifi.layout.spacing diff --git a/interface/resources/qml/TestDialog.qml b/interface/resources/qml/TestDialog.qml index 15bd790c22..e6675b7282 100644 --- a/interface/resources/qml/TestDialog.qml +++ b/interface/resources/qml/TestDialog.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.3 import "controls" -Dialog { +VrDialog { title: "Test Dialog" id: testDialog objectName: "TestDialog" diff --git a/interface/resources/qml/controls/DialogContainer.qml b/interface/resources/qml/controls/DialogContainer.qml new file mode 100644 index 0000000000..4aa4e45d67 --- /dev/null +++ b/interface/resources/qml/controls/DialogContainer.qml @@ -0,0 +1,65 @@ +// +// DialogCommon.qml +// +// Created by David Rowe on 3 Jun 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.4 +import "../styles" + +Item { + id: root + + property bool destroyOnInvisible: true + + + // The UI enables an object, rather than manipulating its visibility, so that we can do animations in both directions. + // Because visibility and enabled are booleans, they cannot be animated. So when enabled is changed, we modify a property + // that can be animated, like scale or opacity, and then when the target animation value is reached, we can modify the + // visibility. + enabled: false + opacity: 0.0 + + onEnabledChanged: { + opacity = enabled ? 1.0 : 0.0 + } + + Behavior on opacity { + // Animate opacity. + NumberAnimation { + duration: hifi.effects.fadeInDuration + easing.type: Easing.OutCubic + } + } + + onOpacityChanged: { + // Once we're transparent, disable the dialog's visibility. + visible = (opacity != 0.0) + } + + onVisibleChanged: { + if (!visible) { + // Some dialogs should be destroyed when they become invisible. + if (destroyOnInvisible) { + destroy() + } + } + } + + + Keys.onPressed: { + switch(event.key) { + case Qt.Key_W: + if (event.modifiers == Qt.ControlModifier) { + enabled = false + event.accepted = true + } + break + } + } +} diff --git a/interface/resources/qml/controls/Dialog.qml b/interface/resources/qml/controls/VrDialog.qml similarity index 100% rename from interface/resources/qml/controls/Dialog.qml rename to interface/resources/qml/controls/VrDialog.qml diff --git a/interface/resources/qml/styles/HifiConstants.qml b/interface/resources/qml/styles/HifiConstants.qml index fa556f2083..c232b993d1 100644 --- a/interface/resources/qml/styles/HifiConstants.qml +++ b/interface/resources/qml/styles/HifiConstants.qml @@ -13,10 +13,9 @@ Item { readonly property color hifiBlue: "#0e7077" readonly property color window: sysPalette.window readonly property color dialogBackground: sysPalette.window - //readonly property color dialogBackground: "#00000000" readonly property color inputBackground: "white" readonly property color background: sysPalette.dark - readonly property color text: sysPalette.text + readonly property color text: "#202020" readonly property color disabledText: "gray" readonly property color hintText: "gray" // A bit darker than sysPalette.dark so that it is visible on the DK2 readonly property color light: sysPalette.light diff --git a/interface/resources/sounds/short1.wav b/interface/resources/sounds/short1.wav new file mode 100644 index 0000000000..fb03f5dd49 Binary files /dev/null and b/interface/resources/sounds/short1.wav differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 614522d618..64136627d7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -250,12 +250,12 @@ bool setupEssentials(int& argc, char** argv) { } // Set build version QCoreApplication::setApplicationVersion(BUILD_VERSION); - + DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); Setting::init(); - + // Set dependencies auto addressManager = DependencyManager::set(); auto nodeList = DependencyManager::set(NodeType::Agent, listenPort); @@ -289,6 +289,7 @@ bool setupEssentials(int& argc, char** argv) { auto discoverabilityManager = DependencyManager::set(); auto sceneScriptingInterface = DependencyManager::set(); auto offscreenUi = DependencyManager::set(); + auto pathUtils = DependencyManager::set(); return true; } @@ -355,9 +356,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); - + Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us - + auto nodeList = DependencyManager::get(); _myAvatar = DependencyManager::get()->getMyAvatar(); @@ -369,7 +370,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _bookmarks = new Bookmarks(); // Before setting up the menu _runningScriptsWidget = new RunningScriptsWidget(_window); - + // start the nodeThread so its event loop is running QThread* nodeThread = new QThread(this); nodeThread->setObjectName("Datagram Processor Thread"); @@ -377,14 +378,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // make sure the node thread is given highest priority nodeThread->setPriority(QThread::TimeCriticalPriority); - + _datagramProcessor = new DatagramProcessor(nodeList.data()); - + // have the NodeList use deleteLater from DM customDeleter nodeList->setCustomDeleter([](Dependency* dependency) { static_cast(dependency)->deleteLater(); }); - + // put the NodeList and datagram processing on the node thread nodeList->moveToThread(nodeThread); @@ -401,20 +402,22 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // put the audio processing on a separate thread QThread* audioThread = new QThread(); audioThread->setObjectName("Audio Thread"); - + auto audioIO = DependencyManager::get(); - + audioIO->setPositionGetter(getPositionForAudio); audioIO->setOrientationGetter(getOrientationForAudio); - + audioIO->moveToThread(audioThread); connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start); connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit); connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled); + connect(audioIO.data(), &AudioClient::receivedFirstPacket, + &AudioScriptingInterface::getInstance(), &AudioScriptingInterface::receivedFirstPacket); audioThread->start(); - + const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); @@ -433,7 +436,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : auto discoverabilityManager = DependencyManager::get(); connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); - + // if we get a domain change, immediately attempt update location in metaverse server connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); @@ -467,14 +470,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); - + auto addressManager = DependencyManager::get(); - + // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter(getPositionForPath); addressManager->setOrientationGetter(getOrientationForPath); - - connect(addressManager.data(), &AddressManager::rootPlaceNameChanged, this, &Application::updateWindowTitle); + + connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); #ifdef _WIN32 @@ -505,7 +508,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); networkAccessManager.setCache(cache); - + ResourceCache::setRequestLimit(3); _window->setCentralWidget(_glWidget); @@ -556,11 +559,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList.data(), SIGNAL(dataReceived(const quint8, const int)), bandwidthRecorder.data(), SLOT(updateInboundData(const quint8, const int))); - connect(&_myAvatar->getSkeletonModel(), &SkeletonModel::skeletonLoaded, + connect(&_myAvatar->getSkeletonModel(), &SkeletonModel::skeletonLoaded, this, &Application::checkSkeleton, Qt::QueuedConnection); // Setup the userInputMapper with the actions - // Setup the keyboardMouseDevice and the user input mapper with the default bindings + // Setup the keyboardMouseDevice and the user input mapper with the default bindings _keyboardMouseDevice.registerToUserInputMapper(_userInputMapper); _keyboardMouseDevice.assignDefaultInputMapping(_userInputMapper); @@ -576,7 +579,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // do this as late as possible so that all required subsystems are initialized loadScripts(); } - + loadSettings(); int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now connect(&_settingsTimer, &QTimer::timeout, this, &Application::saveSettings); @@ -586,12 +589,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _settingsTimer.setSingleShot(false); _settingsTimer.setInterval(SAVE_SETTINGS_INTERVAL); _settingsThread.start(); - + _trayIcon->show(); - + // set the local loopback interface for local sounds from audio scripts AudioScriptingInterface::getInstance().setLocalAudioInterface(audioIO.data()); - + #ifdef HAVE_RTMIDI // setup the MIDIManager MIDIManager& midiManagerInstance = MIDIManager::getInstance(); @@ -599,7 +602,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : #endif this->installEventFilter(this); - // The offscreen UI needs to intercept the mouse and keyboard + // The offscreen UI needs to intercept the mouse and keyboard // events coming from the onscreen window _glWidget->installEventFilter(DependencyManager::get().data()); @@ -617,7 +620,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : void Application::aboutToQuit() { emit beforeAboutToQuit(); - + _aboutToQuit = true; cleanupBeforeQuit(); } @@ -629,7 +632,7 @@ void Application::cleanupBeforeQuit() { _datagramProcessor->shutdown(); // tell the datagram processor we're shutting down, so it can short circuit _entities.shutdown(); // tell the entities system we're shutting down, so it will stop running scripts ScriptEngine::stopAllScripts(this); // stop all currently running global scripts - + // first stop all timers directly or by invokeMethod // depending on what thread they run in locationUpdateTimer->stop(); @@ -656,11 +659,11 @@ void Application::cleanupBeforeQuit() { // let the avatar mixer know we're out MyAvatar::sendKillAvatar(); - + // stop the AudioClient QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); - + // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy(); @@ -669,12 +672,12 @@ void Application::cleanupBeforeQuit() { #endif } -Application::~Application() { +Application::~Application() { EntityTree* tree = _entities.getTree(); tree->lockForWrite(); _entities.getTree()->setSimulation(NULL); tree->unlock(); - + _octreeProcessor.terminate(); _entityEditSender.terminate(); @@ -684,7 +687,7 @@ Application::~Application() { _myAvatar = NULL; ModelEntityItem::cleanupLoadedAnimations(); - + // stop the glWidget frame timer so it doesn't call paintGL _glWidget->stopFrameTimer(); @@ -699,10 +702,10 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - + QThread* nodeThread = DependencyManager::get()->thread(); DependencyManager::destroy(); - + // ask the node thread to quit and wait until it is done nodeThread->quit(); nodeThread->wait(); @@ -756,8 +759,8 @@ void Application::initializeGL() { initDisplay(); qCDebug(interfaceapp, "Initialized Display."); - // The UI can't be created until the primary OpenGL - // context is created, because it needs to share + // The UI can't be created until the primary OpenGL + // context is created, because it needs to share // texture resources initializeUi(); qCDebug(interfaceapp, "Initialized Offscreen UI."); @@ -909,7 +912,7 @@ void Application::paintGL() { OculusManager::display(_glWidget, _myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), _myCamera); } } else if (TV3DManager::isConnected()) { - + TV3DManager::display(_myCamera); } else { @@ -928,7 +931,7 @@ void Application::paintGL() { if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { _rearMirrorTools->render(true, _glWidget->mapFromGlobal(QCursor::pos())); } else if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - renderRearViewMirror(_mirrorViewRect); + renderRearViewMirror(_mirrorViewRect); } auto finalFbo = DependencyManager::get()->render(); @@ -945,7 +948,7 @@ void Application::paintGL() { if (!OculusManager::isConnected() || OculusManager::allowSwap()) { _glWidget->swapBuffers(); - } + } if (OculusManager::isConnected()) { OculusManager::endFrameTiming(); @@ -1035,7 +1038,7 @@ void Application::updateProjectionMatrix(Camera& camera, bool updateViewFrustum) // Tell our viewFrustum about this change, using the application camera if (updateViewFrustum) { loadViewFrustum(camera, _viewFrustum); - } + } glMatrixMode(GL_MODELVIEW); } @@ -1103,11 +1106,11 @@ bool Application::event(QEvent* event) { // handle custom URL if (event->type() == QEvent::FileOpen) { - + QFileOpenEvent* fileEvent = static_cast(event); QUrl url = fileEvent->url(); - + if (!url.isEmpty()) { QString urlString = url.toString(); if (canAcceptURL(urlString)) { @@ -1116,7 +1119,7 @@ bool Application::event(QEvent* event) { } return false; } - + if (HFActionEvent::types().contains(event->type())) { _controllerScriptingInterface.handleMetaEvent(static_cast(event)); } @@ -1182,7 +1185,7 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::AddressBar); } else if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::LodTools); - } + } break; case Qt::Key_F: { @@ -1223,7 +1226,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Backslash: Menu::getInstance()->triggerOption(MenuOption::Chat); break; - + case Qt::Key_Up: if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { if (!isShifted) { @@ -1352,18 +1355,18 @@ void Application::keyPressEvent(QKeyEvent* event) { computePickRay(getTrueMouseX(), getTrueMouseY())); sendEvent(this, &startActionEvent); } - + break; } case Qt::Key_Escape: { OculusManager::abandonCalibration(); - + if (!event->isAutoRepeat()) { // this starts the HFCancelEvent HFBackEvent startBackEvent(HFBackEvent::startType()); sendEvent(this, &startBackEvent); } - + break; } @@ -1425,7 +1428,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { void Application::focusOutEvent(QFocusEvent* event) { _keyboardMouseDevice.focusOutEvent(event); - + // synthesize events for keys currently pressed, since we may not get their release events foreach (int key, _keysPressed) { QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); @@ -1440,12 +1443,12 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { if (!_lastMouseMoveWasSimulated) { _lastMouseMove = usecTimestampNow(); } - + if (_aboutToQuit) { return; } - - if (Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen) + + if (Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen) && !Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) { // Show/hide menu bar in fullscreen if (event->globalY() > _menuBarHeight) { @@ -1458,7 +1461,7 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { } _entities.mouseMoveEvent(event, deviceID); - + _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isMouseCaptured()) { @@ -1466,7 +1469,7 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { } _keyboardMouseDevice.mouseMoveEvent(event, deviceID); - + } void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { @@ -1492,7 +1495,7 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { _mouseDragStartedX = getTrueMouseX(); _mouseDragStartedY = getTrueMouseY(); _mousePressed = true; - + if (mouseOnScreen()) { if (DependencyManager::get()->mousePressEvent(getMouseX(), getMouseY())) { // stop propagation @@ -1509,7 +1512,7 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { return; } } - + // nobody handled this - make it an action event on the _window object HFActionEvent actionEvent(HFActionEvent::startType(), computePickRay(event->x(), event->y())); @@ -1557,14 +1560,14 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { if (event->button() == Qt::LeftButton) { _mousePressed = false; - + if (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && mouseOnScreen()) { // let's set horizontal offset to give stats some margin to mirror int horizontalOffset = MIRROR_VIEW_WIDTH; Stats::getInstance()->checkClick(getMouseX(), getMouseY(), getMouseDragStartedX(), getMouseDragStartedY(), horizontalOffset); } - + // fire an action end event HFActionEvent actionEvent(HFActionEvent::endType(), computePickRay(event->x(), event->y())); @@ -1673,7 +1676,7 @@ void Application::dropEvent(QDropEvent *event) { } } } - + if (atLeastOneFileAccepted) { event->acceptProposedAction(); } @@ -1693,7 +1696,7 @@ void Application::dragEnterEvent(QDragEnterEvent* event) { bool Application::acceptSnapshot(const QString& urlString) { QUrl url(urlString); QString snapshotPath = url.toLocalFile(); - + SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); if (snapshotData) { if (!snapshotData->getURL().toString().isEmpty()) { @@ -1703,7 +1706,7 @@ bool Application::acceptSnapshot(const QString& urlString) { QMessageBox msgBox; msgBox.setText("No location details were found in the file " + snapshotPath + ", try dragging in an authentic Hifi snapshot."); - + msgBox.setStandardButtons(QMessageBox::Ok); msgBox.exec(); } @@ -1736,7 +1739,7 @@ void Application::checkFPS() { void Application::idle() { PerformanceTimer perfTimer("idle"); - + if (_aboutToQuit) { return; // bail early, nothing to do here. } @@ -1865,15 +1868,15 @@ void Application::setEnableVRMode(bool enableVRMode) { OculusManager::recalibrate(); } else { OculusManager::abandonCalibration(); - + _mirrorCamera.setHmdPosition(glm::vec3()); _mirrorCamera.setHmdRotation(glm::quat()); _myCamera.setHmdPosition(glm::vec3()); _myCamera.setHmdRotation(glm::quat()); } - + resizeGL(); - + updateCursorVisibility(); } @@ -1926,7 +1929,7 @@ int Application::getMouseDragStartedY() const { FaceTracker* Application::getActiveFaceTracker() { auto faceshift = DependencyManager::get(); auto dde = DependencyManager::get(); - + return (dde->isActive() ? static_cast(dde.data()) : (faceshift->isActive() ? static_cast(faceshift.data()) : NULL)); } @@ -2059,14 +2062,14 @@ void Application::saveSettings() { bool Application::importEntities(const QString& urlOrFilename) { _entityClipboard.eraseAllOctreeElements(); - + QUrl url(urlOrFilename); - + // if the URL appears to be invalid or relative, then it is probably a local file if (!url.isValid() || url.isRelative()) { url = QUrl::fromLocalFile(urlOrFilename); } - + bool success = _entityClipboard.readFromURL(url.toString()); if (success) { _entityClipboard.reaverageOctreeElements(); @@ -2090,7 +2093,7 @@ void Application::initDisplay() { void Application::init() { // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); - + _environment.init(); DependencyManager::get()->init(this); @@ -2118,7 +2121,7 @@ void Application::init() { _timerStart.start(); _lastTimeUpdated.start(); - + // when --url in command line, teleport to location const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); @@ -2126,11 +2129,11 @@ void Application::init() { if (urlIndex != -1) { addressLookupString = arguments().value(urlIndex + 1); } - + DependencyManager::get()->loadSettings(addressLookupString); - + qCDebug(interfaceapp) << "Loaded settings"; - + #ifdef __APPLE__ if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) { // on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash @@ -2238,7 +2241,7 @@ void Application::updateMouseRay() { PickRay pickRay = computePickRay(getTrueMouseX(), getTrueMouseY()); _mouseRayOrigin = pickRay.origin; _mouseRayDirection = pickRay.direction; - + // adjust for mirroring if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { glm::vec3 mouseRayOffset = _mouseRayOrigin - _viewFrustum.getPosition(); @@ -2270,11 +2273,11 @@ void Application::updateMyAvatarLookAtPosition() { lookAtSpot = OculusManager::getRightEyePosition(); } } - + } else { AvatarSharedPointer lookingAt = _myAvatar->getLookAtTargetAvatar().toStrongRef(); if (lookingAt && _myAvatar != lookingAt.data()) { - + isLookingAtSomeone = true; // If I am looking at someone else, look directly at one of their eyes if (tracker && !tracker->isMuted()) { @@ -2361,7 +2364,7 @@ void Application::updateDialogs(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateDialogs()"); auto dialogsManager = DependencyManager::get(); - + // Update bandwidth dialog, if any BandwidthDialog* bandwidthDialog = dialogsManager->getBandwidthDialog(); if (bandwidthDialog) { @@ -2567,7 +2570,7 @@ void Application::update(float deltaTime) { } } - // send packet containing downstream audio stats to the AudioMixer + // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { @@ -2589,64 +2592,64 @@ int Application::sendNackPackets() { // iterates thru all nodes in NodeList auto nodeList = DependencyManager::get(); - + nodeList->eachNode([&](const SharedNodePointer& node){ - + if (node->getActiveSocket() && node->getType() == NodeType::EntityServer) { - + QUuid nodeUUID = node->getUUID(); - + // if there are octree packets from this node that are waiting to be processed, // don't send a NACK since the missing packets may be among those waiting packets. if (_octreeProcessor.hasPacketsToProcessFrom(nodeUUID)) { return; } - + _octreeSceneStatsLock.lockForRead(); - + // retreive octree scene stats of this node if (_octreeServerSceneStats.find(nodeUUID) == _octreeServerSceneStats.end()) { _octreeSceneStatsLock.unlock(); return; } - + // get sequence number stats of node, prune its missing set, and make a copy of the missing set SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats(); sequenceNumberStats.pruneMissingSet(); const QSet missingSequenceNumbers = sequenceNumberStats.getMissingSet(); - + _octreeSceneStatsLock.unlock(); - + // construct nack packet(s) for this node int numSequenceNumbersAvailable = missingSequenceNumbers.size(); QSet::const_iterator missingSequenceNumbersIterator = missingSequenceNumbers.constBegin(); while (numSequenceNumbersAvailable > 0) { - + char* dataAt = packet; int bytesRemaining = MAX_PACKET_SIZE; - + // pack header int numBytesPacketHeader = nodeList->populatePacketHeader(packet, PacketTypeOctreeDataNack); dataAt += numBytesPacketHeader; bytesRemaining -= numBytesPacketHeader; - + // calculate and pack the number of sequence numbers int numSequenceNumbersRoomFor = (bytesRemaining - sizeof(uint16_t)) / sizeof(OCTREE_PACKET_SEQUENCE); uint16_t numSequenceNumbers = min(numSequenceNumbersAvailable, numSequenceNumbersRoomFor); uint16_t* numSequenceNumbersAt = (uint16_t*)dataAt; *numSequenceNumbersAt = numSequenceNumbers; dataAt += sizeof(uint16_t); - + // pack sequence numbers for (int i = 0; i < numSequenceNumbers; i++) { OCTREE_PACKET_SEQUENCE* sequenceNumberAt = (OCTREE_PACKET_SEQUENCE*)dataAt; *sequenceNumberAt = *missingSequenceNumbersIterator; dataAt += sizeof(OCTREE_PACKET_SEQUENCE); - + missingSequenceNumbersIterator++; } numSequenceNumbersAvailable -= numSequenceNumbers; - + // send it nodeList->writeUnverifiedDatagram(packet, dataAt - packet, node); packetsSent++; @@ -2688,7 +2691,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node int unknownJurisdictionServers = 0; auto nodeList = DependencyManager::get(); - + nodeList->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { @@ -2748,17 +2751,17 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node if (wantExtraDebugging) { qCDebug(interfaceapp, "perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer); } - + nodeList->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { - + // get the server bounds for this server QUuid nodeUUID = node->getUUID(); - + bool inView = false; bool unknownView = false; - + // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { @@ -2768,9 +2771,9 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node } } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - + unsigned char* rootCode = map.getRootOctalCode(); - + if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); @@ -2780,7 +2783,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node rootDetails.s * TREE_SCALE); - + ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); if (serverFrustumLocation != ViewFrustum::OUTSIDE) { inView = true; @@ -2793,7 +2796,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node } } } - + if (inView) { _octreeQuery.setMaxQueryPacketsPerSecond(perServerPPS); } else if (unknownView) { @@ -2801,7 +2804,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", give it budget of " << perUnknownServer << " to send us jurisdiction."; } - + // set the query's position/orientation to be degenerate in a manner that will get the scene quickly // If there's only one server, then don't do this, and just let the normal voxel query pass through // as expected... this way, we will actually get a valid scene if there is one to be seen @@ -2828,12 +2831,12 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node // insert packet type/version and node UUID endOfQueryPacket += nodeList->populatePacketHeader(reinterpret_cast(endOfQueryPacket), packetType); - + // encode the query data... endOfQueryPacket += _octreeQuery.getBroadcastData(endOfQueryPacket); - + int packetLength = endOfQueryPacket - queryPacket; - + // make sure we still have an active socket nodeList->writeUnverifiedDatagram(reinterpret_cast(queryPacket), packetLength, node); } @@ -2850,7 +2853,7 @@ bool Application::isHMDMode() const { QRect Application::getDesirableApplicationGeometry() { QRect applicationGeometry = getWindow()->geometry(); - + // If our parent window is on the HMD, then don't use its geometry, instead use // the "main screen" geometry. HMDToolsDialog* hmdTools = DependencyManager::get()->getHMDToolsDialog(); @@ -2911,14 +2914,14 @@ void Application::updateShadowMap() { glm::vec3 lightDirection = getSunDirection(); glm::quat rotation = rotationBetween(IDENTITY_FRONT, lightDirection); glm::quat inverseRotation = glm::inverse(rotation); - + const float SHADOW_MATRIX_DISTANCES[] = { 0.0f, 2.0f, 6.0f, 14.0f, 30.0f }; const glm::vec2 MAP_COORDS[] = { glm::vec2(0.0f, 0.0f), glm::vec2(0.5f, 0.0f), glm::vec2(0.0f, 0.5f), glm::vec2(0.5f, 0.5f) }; - + float frustumScale = 1.0f / (_viewFrustum.getFarClip() - _viewFrustum.getNearClip()); loadViewFrustum(_myCamera, _viewFrustum); - + int matrixCount = 1; //int targetSize = fbo->width(); int sourceSize = shadowFramebuffer->getWidth(); @@ -2960,12 +2963,12 @@ void Application::updateShadowMap() { _shadowDistances[i] = -glm::distance(_viewFrustum.getPosition(), center) - radius * RADIUS_SCALE; } center = inverseRotation * center; - + // to reduce texture "shimmer," move in texel increments float texelSize = (2.0f * radius) / targetSize; center = glm::vec3(roundf(center.x / texelSize) * texelSize, roundf(center.y / texelSize) * texelSize, roundf(center.z / texelSize) * texelSize); - + glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius); glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); @@ -3004,7 +3007,7 @@ void Application::updateShadowMap() { // store view matrix without translation, which we'll use for precision-sensitive objects updateUntranslatedViewMatrix(); - + // Equivalent to what is happening with _untranslatedViewMatrix and the _viewMatrixTranslation // the viewTransofmr object is updatded with the correct values and saved, // this is what is used for rendering the Entities and avatars @@ -3047,9 +3050,9 @@ void Application::updateShadowMap() { glMatrixMode(GL_MODELVIEW); } - + // fbo->release(); - + glViewport(0, 0, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); activeRenderingThread = nullptr; } @@ -3081,11 +3084,11 @@ bool Application::shouldRenderMesh(float largestDimension, float distanceToCamer return DependencyManager::get()->shouldRenderMesh(largestDimension, distanceToCamera); } -float Application::getSizeScale() const { +float Application::getSizeScale() const { return DependencyManager::get()->getOctreeSizeScale(); } -int Application::getBoundaryLevelAdjust() const { +int Application::getBoundaryLevelAdjust() const { return DependencyManager::get()->getBoundaryLevelAdjust(); } @@ -3109,27 +3112,27 @@ PickRay Application::computePickRay(float x, float y) const { QImage Application::renderAvatarBillboard() { auto primaryFramebuffer = DependencyManager::get()->getPrimaryFramebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFramebuffer)); - + // clear the alpha channel so the background is transparent glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - + // the "glow" here causes an alpha of one Glower glower; - + const int BILLBOARD_SIZE = 64; renderRearViewMirror(QRect(0, _glWidget->getDeviceHeight() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true); - + QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - + glBindFramebuffer(GL_FRAMEBUFFER, 0); - + return image; } @@ -3214,11 +3217,11 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb } if (renderSide != RenderArgs::MONO) { glm::mat4 invView = glm::inverse(_untranslatedViewMatrix); - + viewTransform.evalFromRawMatrix(invView); viewTransform.preTranslate(_viewMatrixTranslation); } - + setViewTransform(viewTransform); glTranslatef(_viewMatrixTranslation.x, _viewMatrixTranslation.y, _viewMatrixTranslation.z); @@ -3269,7 +3272,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f; const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f; - glm::vec3 sunDirection = (getAvatarPosition() - closestData.getSunLocation()) + glm::vec3 sunDirection = (getAvatarPosition() - closestData.getSunLocation()) / closestData.getAtmosphereOuterRadius(); float height = glm::distance(theCamera.getPosition(), closestData.getAtmosphereCenter()); if (height < closestData.getAtmosphereInnerRadius()) { @@ -3277,20 +3280,20 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb alpha = 0.0f; if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { - float directionY = glm::clamp(sunDirection.y, - -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + float directionY = glm::clamp(sunDirection.y, + -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + APPROXIMATE_DISTANCE_FROM_HORIZON; alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); } - + } else if (height < closestData.getAtmosphereOuterRadius()) { alpha = (height - closestData.getAtmosphereInnerRadius()) / (closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius()); if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { - float directionY = glm::clamp(sunDirection.y, - -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + float directionY = glm::clamp(sunDirection.y, + -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + APPROXIMATE_DISTANCE_FROM_HORIZON; alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); } @@ -3331,14 +3334,14 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); - + DependencyManager::get()->prepare(); if (!selfAvatarOnly) { // draw a red sphere float originSphereRadius = 0.05f; DependencyManager::get()->renderSphere(originSphereRadius, 15, 15, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f)); - + // render models... if (DependencyManager::get()->shouldRenderEntities()) { PerformanceTimer perfTimer("entities"); @@ -3359,7 +3362,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb renderMode = RenderArgs::MIRROR_RENDER_MODE; } _entities.render(renderMode, renderSide, renderDebugFlags); - + if (!Menu::getInstance()->isOptionChecked(MenuOption::Wireframe)) { // Restaure polygon mode glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); @@ -3380,13 +3383,13 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb DependencyManager::get()->render(); } } - + bool mirrorMode = (theCamera.getMode() == CAMERA_MODE_MIRROR); - + { PerformanceTimer perfTimer("avatars"); DependencyManager::get()->renderAvatars(mirrorMode ? RenderArgs::MIRROR_RENDER_MODE : RenderArgs::NORMAL_RENDER_MODE, - false, selfAvatarOnly); + false, selfAvatarOnly); } if (!billboard) { @@ -3396,7 +3399,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb DependencyManager::get()->setGlobalAtmosphere(skyStage->getAtmosphere()); DependencyManager::get()->setGlobalSkybox(skybox); - PROFILE_RANGE("DeferredLighting"); + PROFILE_RANGE("DeferredLighting"); PerformanceTimer perfTimer("lighting"); DependencyManager::get()->render(); } @@ -3404,9 +3407,9 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb { PerformanceTimer perfTimer("avatarsPostLighting"); DependencyManager::get()->renderAvatars(mirrorMode ? RenderArgs::MIRROR_RENDER_MODE : RenderArgs::NORMAL_RENDER_MODE, - true, selfAvatarOnly); + true, selfAvatarOnly); } - + //Render the sixense lasers if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseLasers)) { _myAvatar->renderLaserPointers(); @@ -3414,7 +3417,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, bool billb if (!selfAvatarOnly) { _nodeBoundsDisplay.draw(); - + // Render the world box if (theCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { PerformanceTimer perfTimer("worldBox"); @@ -3491,9 +3494,9 @@ void Application::computeOffAxisFrustum(float& left, float& right, float& bottom _displayViewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); if (OculusManager::isConnected()) { OculusManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - + } else if (TV3DManager::isConnected()) { - TV3DManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); + TV3DManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); } } @@ -3503,8 +3506,8 @@ bool Application::getShadowsEnabled() { menubar->isOptionChecked(MenuOption::CascadedShadows); } -bool Application::getCascadeShadowsEnabled() { - return Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows); +bool Application::getCascadeShadowsEnabled() { + return Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows); } glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) { @@ -3551,22 +3554,22 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); } else { // HEAD zoom level - // FIXME note that the positioing of the camera relative to the avatar can suffer limited - // precision as the user's position moves further away from the origin. Thus at - // /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways - // wildly as you rotate your avatar because the floating point values are becoming - // larger, squeezing out the available digits of precision you have available at the - // human scale for camera positioning. + // FIXME note that the positioing of the camera relative to the avatar can suffer limited + // precision as the user's position moves further away from the origin. Thus at + // /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways + // wildly as you rotate your avatar because the floating point values are becoming + // larger, squeezing out the available digits of precision you have available at the + // human scale for camera positioning. - // Previously there was a hack to correct this using the mechanism of repositioning - // the avatar at the origin of the world for the purposes of rendering the mirror, - // but it resulted in failing to render the avatar's head model in the mirror view - // when in first person mode. Presumably this was because of some missed culling logic - // that was not accounted for in the hack. + // Previously there was a hack to correct this using the mechanism of repositioning + // the avatar at the origin of the world for the purposes of rendering the mirror, + // but it resulted in failing to render the avatar's head model in the mirror view + // when in first person mode. Presumably this was because of some missed culling logic + // that was not accounted for in the hack. - // This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further + // This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further // investigated in order to adapt the technique while fixing the head rendering issue, - // but the complexity of the hack suggests that a better approach + // but the complexity of the hack suggests that a better approach _mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() + _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); } @@ -3577,7 +3580,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { if (billboard) { QSize size = DependencyManager::get()->getFrameBufferSize(); glViewport(region.x(), size.height() - region.y() - region.height(), region.width(), region.height()); - glScissor(region.x(), size.height() - region.y() - region.height(), region.width(), region.height()); + glScissor(region.x(), size.height() - region.y() - region.height(), region.width(), region.height()); } else { // if not rendering the billboard, the region is in device independent coordinates; must convert to device QSize size = DependencyManager::get()->getFrameBufferSize(); @@ -3618,7 +3621,7 @@ void Application::resetSensors() { QWindow* mainWindow = _window->windowHandle(); QPoint windowCenter = mainWindow->geometry().center(); _glWidget->cursor().setPos(currentScreen, windowCenter); - + _myAvatar->reset(); QMetaObject::invokeMethod(DependencyManager::get().data(), "reset", Qt::QueuedConnection); @@ -3650,12 +3653,12 @@ void Application::updateWindowTitle(){ QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; QString username = AccountManager::getInstance().getAccountInfo().getUsername(); - QString currentPlaceName = DependencyManager::get()->getRootPlaceName(); - + QString currentPlaceName = DependencyManager::get()->getHost(); + if (currentPlaceName.isEmpty()) { currentPlaceName = nodeList->getDomainHandler().getHostname(); } - + QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + currentPlaceName + connectionStatus + buildVersion; @@ -3701,7 +3704,7 @@ void Application::domainConnectionDenied(const QString& reason) { void Application::connectedToDomain(const QString& hostname) { AccountManager& accountManager = AccountManager::getInstance(); const QUuid& domainID = DependencyManager::get()->getDomainHandler().getUUID(); - + if (accountManager.isLoggedIn() && !domainID.isNull()) { _notifiedPacketVersionMismatchThisDomain = false; } @@ -3885,7 +3888,7 @@ void Application::saveScripts() { Settings settings; settings.beginWriteArray(SETTINGS_KEY); settings.remove(""); - + QStringList runningScripts = getRunningScripts(); int i = 0; for (auto it = runningScripts.begin(); it != runningScripts.end(); ++it) { @@ -3936,7 +3939,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Overlays", &_overlays); qScriptRegisterMetaType(scriptEngine, OverlayPropertyResultToScriptValue, OverlayPropertyResultFromScriptValue); - qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, + qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); @@ -3947,7 +3950,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri LocationScriptingInterface::locationSetter); scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); - + scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); @@ -3959,7 +3962,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get().data()); - + scriptEngine->registerGlobalObject("Joysticks", &JoystickScriptingInterface::getInstance()); qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue); @@ -3967,6 +3970,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("LODManager", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Paths", DependencyManager::get().data()); + QScriptValue hmdInterface = scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance()); scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0); scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0); @@ -4014,7 +4019,7 @@ void Application::initializeAcceptedFiles() { bool Application::canAcceptURL(const QString& urlString) { initializeAcceptedFiles(); - + QUrl url(urlString); if (urlString.startsWith(HIFI_URL_SCHEME)) { return true; @@ -4032,7 +4037,7 @@ bool Application::canAcceptURL(const QString& urlString) { bool Application::acceptURL(const QString& urlString) { initializeAcceptedFiles(); - + if (urlString.startsWith(HIFI_URL_SCHEME)) { // this is a hifi URL - have the AddressManager handle it QMetaObject::invokeMethod(DependencyManager::get().data(), "handleLookupString", @@ -4070,19 +4075,19 @@ bool Application::askToSetAvatarUrl(const QString& url) { msgBox.exec(); return false; } - + // Download the FST file, to attempt to determine its model type QVariantHash fstMapping = FSTReader::downloadMapping(url); - + FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping); - + QMessageBox msgBox; msgBox.setIcon(QMessageBox::Question); msgBox.setWindowTitle("Set Avatar"); QPushButton* headButton = NULL; QPushButton* bodyButton = NULL; QPushButton* bodyAndHeadButton = NULL; - + QString modelName = fstMapping["name"].toString(); QString message; QString typeInfo; @@ -4101,7 +4106,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { message = QString("Would you like to use '") + modelName + QString("' for your avatar?"); bodyAndHeadButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); break; - + default: message = QString("Would you like to use '") + modelName + QString("' for some part of your avatar head?"); headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole); @@ -4127,7 +4132,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { } else { qCDebug(interfaceapp) << "Declined to use the avatar: " << url; } - + return true; } @@ -4152,7 +4157,7 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser if (isAboutToQuit()) { return NULL; } - + QUrl scriptUrl(scriptFilename); const QString& scriptURLString = scriptUrl.toString(); if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor @@ -4163,18 +4168,18 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser ScriptEngine* scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); scriptEngine->setUserLoaded(isUserLoaded); - + if (scriptFilename.isNull()) { // this had better be the script editor (we should de-couple so somebody who thinks they are loading a script // doesn't just get an empty script engine) - + // we can complete setup now since there isn't a script we have to load registerScriptEngineWithApplicationServices(scriptEngine); } else { // connect to the appropriate signals of this script engine connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded); connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &Application::handleScriptLoadError); - + // get the script engine object to load the script at the designated script URL scriptEngine->loadURL(scriptUrl); } @@ -4189,11 +4194,11 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser void Application::handleScriptEngineLoaded(const QString& scriptFilename) { ScriptEngine* scriptEngine = qobject_cast(sender()); - + _scriptEnginesHash.insertMulti(scriptFilename, scriptEngine); _runningScriptsWidget->setRunningScripts(getRunningScripts()); UserActivityLogger::getInstance().loadedScript(scriptFilename); - + // register our application services and set it off on its own thread registerScriptEngineWithApplicationServices(scriptEngine); } @@ -4303,7 +4308,7 @@ void Application::updateMyAvatarTransform() { glm::vec3 newOriginOffset = avatarPosition; int halfExtent = (int)HALF_SIMULATION_EXTENT; for (int i = 0; i < 3; ++i) { - newOriginOffset[i] = (float)(glm::max(halfExtent, + newOriginOffset[i] = (float)(glm::max(halfExtent, ((int)(avatarPosition[i] / SIMULATION_OFFSET_QUANTIZATION)) * (int)SIMULATION_OFFSET_QUANTIZATION)); } // TODO: Andrew to replace this with method that actually moves existing object positions in PhysicsEngine @@ -4317,23 +4322,23 @@ void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject const QString PER_VOXEL_COST_KEY = "per-voxel-credits"; const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits"; const QString VOXEL_WALLET_UUID = "voxel-wallet"; - + const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject(); - + qint64 satoshisPerVoxel = 0; qint64 satoshisPerMeterCubed = 0; QUuid voxelWalletUUID; - + if (!domainSettingsObject.isEmpty()) { float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble(); float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble(); - + satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT); satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT); - + voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString()); } - + qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed"; qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID; } @@ -4536,7 +4541,7 @@ bool Application::isVSyncOn() const { } else { return true; } - */ + */ #endif return true; } @@ -4634,14 +4639,14 @@ void Application::notifyPacketVersionMismatch() { void Application::checkSkeleton() { if (_myAvatar->getSkeletonModel().isActive() && !_myAvatar->getSkeletonModel().hasSkeleton()) { qCDebug(interfaceapp) << "MyAvatar model has no skeleton"; - + QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded..."; QMessageBox msgBox; msgBox.setText(message); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Warning); msgBox.exec(); - + _myAvatar->useBodyURL(DEFAULT_BODY_MODEL_URL, "Default"); } else { _physicsEngine.setCharacterController(_myAvatar->getCharacterController()); @@ -4654,7 +4659,7 @@ void Application::showFriendsWindow() { const int FRIENDS_WINDOW_WIDTH = 290; const int FRIENDS_WINDOW_HEIGHT = 500; if (!_friendsWindow) { - _friendsWindow = new WebWindowClass(FRIENDS_WINDOW_TITLE, FRIENDS_WINDOW_URL, FRIENDS_WINDOW_WIDTH, + _friendsWindow = new WebWindowClass(FRIENDS_WINDOW_TITLE, FRIENDS_WINDOW_URL, FRIENDS_WINDOW_WIDTH, FRIENDS_WINDOW_HEIGHT, false); connect(_friendsWindow, &WebWindowClass::closed, this, &Application::friendsWindowClosed); } @@ -4695,16 +4700,16 @@ QSize Application::getDeviceSize() const { return _glWidget->getDeviceSize(); } -int Application::getTrueMouseX() const { - return _glWidget->mapFromGlobal(QCursor::pos()).x(); +int Application::getTrueMouseX() const { + return _glWidget->mapFromGlobal(QCursor::pos()).x(); } -int Application::getTrueMouseY() const { - return _glWidget->mapFromGlobal(QCursor::pos()).y(); +int Application::getTrueMouseY() const { + return _glWidget->mapFromGlobal(QCursor::pos()).y(); } -bool Application::isThrottleRendering() const { - return _glWidget->isThrottleRendering(); +bool Application::isThrottleRendering() const { + return _glWidget->isThrottleRendering(); } PickRay Application::computePickRay() const { diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index b452f153f0..196ba5d29e 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -1,6 +1,6 @@ // -// // LoginDialog.cpp +// interface/src/ui // // Created by Bradley Austin Davis on 2015/04/14 // Copyright 2015 High Fidelity, Inc. @@ -8,16 +8,22 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + #include "LoginDialog.h" -#include "DependencyManager.h" -#include "AccountManager.h" -#include "Menu.h" +#include + #include +#include "AccountManager.h" +#include "DependencyManager.h" +#include "Menu.h" + HIFI_QML_DEF(LoginDialog) -LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent), _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString()) { +LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent), + _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString()) +{ connect(&AccountManager::getInstance(), &AccountManager::loginComplete, this, &LoginDialog::handleLoginCompleted); connect(&AccountManager::getInstance(), &AccountManager::loginFailed, @@ -48,7 +54,7 @@ void LoginDialog::handleLoginCompleted(const QUrl&) { } void LoginDialog::handleLoginFailed() { - setStatusText("Invalid username or password.< / font>"); + setStatusText("Invalid username or password"); } void LoginDialog::setStatusText(const QString& statusText) { @@ -68,10 +74,11 @@ QString LoginDialog::rootUrl() const { void LoginDialog::login(const QString& username, const QString& password) { qDebug() << "Attempting to login " << username; - setStatusText("Authenticating..."); + setStatusText("Logging in..."); AccountManager::getInstance().requestAccessToken(username, password); } void LoginDialog::openUrl(const QString& url) { qDebug() << url; + QDesktopServices::openUrl(url); } diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index e9ae0a1c16..50c820aa07 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -1,5 +1,6 @@ // // LoginDialog.h +// interface/src/ui // // Created by Bradley Austin Davis on 2015/04/14 // Copyright 2015 High Fidelity, Inc. @@ -9,6 +10,7 @@ // #pragma once + #ifndef hifi_LoginDialog_h #define hifi_LoginDialog_h diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 41c8cb9537..340ca9374d 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -153,6 +153,7 @@ void AudioClient::reset() { } void AudioClient::audioMixerKilled() { + _hasReceivedFirstPacket = false; _outgoingAvatarAudioSequenceNumber = 0; _stats.reset(); } @@ -481,6 +482,7 @@ void AudioClient::start() { qCDebug(audioclient) << "Unable to set up audio input because of a problem with input format."; qCDebug(audioclient) << "The closest format available is" << inputDeviceInfo.nearestFormat(_desiredInputFormat); } + if (!outputFormatSupported) { qCDebug(audioclient) << "Unable to set up audio output because of a problem with output format."; qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.nearestFormat(_desiredOutputFormat); @@ -489,6 +491,7 @@ void AudioClient::start() { if (_audioInput) { _inputFrameBuffer.initialize( _inputFormat.channelCount(), _audioInput->bufferSize() * 8 ); } + _inputGain.initialize(); _sourceGain.initialize(); _noiseSource.initialize(); @@ -926,6 +929,14 @@ void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveFirstAudioPacket); if (_audioOutput) { + + if (!_hasReceivedFirstPacket) { + _hasReceivedFirstPacket = true; + + // have the audio scripting interface emit a signal to say we just connected to mixer + emit receivedFirstPacket(); + } + // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(audioByteArray); } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index f9392c6a10..3b2c1c1ae6 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include "AudioIOStats.h" @@ -57,7 +58,7 @@ static const int NUM_AUDIO_CHANNELS = 2; static const int DEFAULT_AUDIO_OUTPUT_BUFFER_SIZE_FRAMES = 3; static const int MIN_AUDIO_OUTPUT_BUFFER_SIZE_FRAMES = 1; static const int MAX_AUDIO_OUTPUT_BUFFER_SIZE_FRAMES = 20; -#if defined(Q_OS_ANDROID) || defined(Q_OS_WIN) +#if defined(Q_OS_ANDROID) || defined(Q_OS_WIN) static const int DEFAULT_AUDIO_OUTPUT_STARVE_DETECTION_ENABLED = false; #else static const int DEFAULT_AUDIO_OUTPUT_STARVE_DETECTION_ENABLED = true; @@ -88,14 +89,14 @@ public: void stop() { close(); } qint64 readData(char * data, qint64 maxSize); qint64 writeData(const char * data, qint64 maxSize) { return 0; } - + int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; } private: MixedProcessedAudioStream& _receivedAudioStream; AudioClient* _audio; int _unfulfilledReads; }; - + const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; } MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; } @@ -105,30 +106,30 @@ public: float getAudioAverageInputLoudness() const { return _lastInputLoudness; } int getDesiredJitterBufferFrames() const { return _receivedAudioStream.getDesiredJitterBufferFrames(); } - + bool isMuted() { return _muted; } - + const AudioIOStats& getStats() const { return _stats; } float getInputRingBufferMsecsAvailable() const; float getAudioOutputMsecsUnplayed() const; int getOutputBufferSize() { return _outputBufferSizeFrames.get(); } - + bool getOutputStarveDetectionEnabled() { return _outputStarveDetectionEnabled.get(); } void setOutputStarveDetectionEnabled(bool enabled) { _outputStarveDetectionEnabled.set(enabled); } int getOutputStarveDetectionPeriod() { return _outputStarveDetectionPeriodMsec.get(); } void setOutputStarveDetectionPeriod(int msecs) { _outputStarveDetectionPeriodMsec.set(msecs); } - + int getOutputStarveDetectionThreshold() { return _outputStarveDetectionThreshold.get(); } void setOutputStarveDetectionThreshold(int threshold) { _outputStarveDetectionThreshold.set(threshold); } - + void setPositionGetter(AudioPositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } static const float CALLBACK_ACCELERATOR_RATIO; - + public slots: void start(); void stop(); @@ -140,7 +141,7 @@ public slots: void reset(); void audioMixerKilled(); void toggleMute(); - + virtual void enableAudioSourceInject(bool enable); virtual void selectAudioSourcePinkNoise(); virtual void selectAudioSourceSine440(); @@ -148,10 +149,10 @@ public slots: virtual void setIsStereoInput(bool stereo); void toggleAudioNoiseReduction() { _isNoiseGateEnabled = !_isNoiseGateEnabled; } - + void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } - + void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); void sendMuteEnvironmentPacket(); @@ -172,10 +173,10 @@ public slots: void setReverbOptions(const AudioEffectOptions* options); void outputNotify(); - + void loadSettings(); void saveSettings(); - + signals: bool muteToggled(); void inputReceived(const QByteArray& inputSamples); @@ -184,14 +185,16 @@ signals: void deviceChanged(); + void receivedFirstPacket(); + protected: AudioClient(); ~AudioClient(); - + virtual void customDeleter() { deleteLater(); } - + private: void outputFormatChanged(); @@ -216,35 +219,35 @@ private: QString _inputAudioDeviceName; QString _outputAudioDeviceName; - + quint64 _outputStarveDetectionStartTimeMsec; int _outputStarveDetectionCount; - + Setting::Handle _outputBufferSizeFrames; Setting::Handle _outputStarveDetectionEnabled; Setting::Handle _outputStarveDetectionPeriodMsec; // Maximum number of starves per _outputStarveDetectionPeriod before increasing buffer size Setting::Handle _outputStarveDetectionThreshold; - + StDev _stdev; QElapsedTimer _timeSinceLastReceived; float _averagedLatency; float _lastInputLoudness; float _timeSinceLastClip; int _totalInputAudioSamples; - + bool _muted; bool _shouldEchoLocally; bool _shouldEchoToServer; bool _isNoiseGateEnabled; bool _audioSourceInjectEnabled; - + bool _reverb; AudioEffectOptions _scriptReverbOptions; AudioEffectOptions _zoneReverbOptions; AudioEffectOptions* _reverbOptions; ty_gverb* _gverb; - + // possible soxr streams needed for resample soxr* _inputToNetworkResampler; soxr* _networkToOutputResampler; @@ -268,17 +271,17 @@ private: // Input framebuffer AudioBufferFloat32 _inputFrameBuffer; - + // Input gain AudioGain _inputGain; - + // Post tone/pink noise generator gain AudioGain _sourceGain; // Pink noise source bool _noiseSourceEnabled; AudioSourcePinkNoise _noiseSource; - + // Tone source bool _toneSourceEnabled; AudioSourceTone _toneSource; @@ -286,17 +289,19 @@ private: quint16 _outgoingAvatarAudioSequenceNumber; AudioOutputIODevice _audioOutputIODevice; - + AudioIOStats _stats; - + AudioNoiseGate _inputGate; - + AudioPositionGetter _positionGetter; AudioOrientationGetter _orientationGetter; - + QVector _inputDevices; QVector _outputDevices; void checkDevices(); + + bool _hasReceivedFirstPacket = false; }; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index ae397ba97e..912351e21a 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -26,7 +26,7 @@ AudioInjector::AudioInjector(QObject* parent) : QObject(parent) { - + } AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) : @@ -39,24 +39,25 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt _audioData(audioData), _options(injectorOptions) { - + } void AudioInjector::setIsFinished(bool isFinished) { _isFinished = isFinished; - + // In all paths, regardless of isFinished argument. restart() passes false to prepare for new play, and injectToMixer() needs _shouldStop reset. + _shouldStop = false; + if (_isFinished) { emit finished(); - + if (_localBuffer) { _localBuffer->stop(); _localBuffer->deleteLater(); _localBuffer = NULL; } - + _isStarted = false; - _shouldStop = false; - + if (_shouldDeleteAfterFinish) { // we've been asked to delete after finishing, trigger a queued deleteLater here qCDebug(audio) << "AudioInjector triggering delete from setIsFinished"; @@ -67,18 +68,19 @@ void AudioInjector::setIsFinished(bool isFinished) { void AudioInjector::injectAudio() { if (!_isStarted) { + _isStarted = true; // check if we need to offset the sound by some number of seconds if (_options.secondOffset > 0.0f) { - + // convert the offset into a number of bytes int byteOffset = (int) floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f)); byteOffset *= sizeof(int16_t); - + _currentSendPosition = byteOffset; } else { _currentSendPosition = 0; } - + if (_options.localOnly) { injectLocally(); } else { @@ -86,12 +88,20 @@ void AudioInjector::injectAudio() { } } else { qCDebug(audio) << "AudioInjector::injectAudio called but already started."; - } + } } void AudioInjector::restart() { qCDebug(audio) << "Restarting an AudioInjector by stopping and starting over."; - stop(); + connect(this, &AudioInjector::finished, this, &AudioInjector::restartPortionAfterFinished); + if (!_isStarted || _isFinished) { + emit finished(); + } else { + stop(); + } +} +void AudioInjector::restartPortionAfterFinished() { + disconnect(this, &AudioInjector::finished, this, &AudioInjector::restartPortionAfterFinished); setIsFinished(false); QMetaObject::invokeMethod(this, "injectAudio", Qt::QueuedConnection); } @@ -100,37 +110,37 @@ void AudioInjector::injectLocally() { bool success = false; if (_localAudioInterface) { if (_audioData.size() > 0) { - + _localBuffer = new AudioInjectorLocalBuffer(_audioData, this); - + _localBuffer->open(QIODevice::ReadOnly); _localBuffer->setShouldLoop(_options.loop); _localBuffer->setVolume(_options.volume); - + // give our current send position to the local buffer _localBuffer->setCurrentOffset(_currentSendPosition); - + success = _localAudioInterface->outputLocalInjector(_options.stereo, this); - + // if we're not looping and the buffer tells us it is empty then emit finished connect(_localBuffer, &AudioInjectorLocalBuffer::bufferEmpty, this, &AudioInjector::stop); - + if (!success) { qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; } } else { qCDebug(audio) << "AudioInjector::injectLocally called without any data in Sound QByteArray"; } - + } else { qCDebug(audio) << "AudioInjector::injectLocally cannot inject locally with no local audio interface present."; } - + if (!success) { // we never started so we are finished, call our stop method stop(); } - + } const uchar MAX_INJECTOR_VOLUME = 0xFF; @@ -140,65 +150,65 @@ void AudioInjector::injectToMixer() { _currentSendPosition >= _audioData.size()) { _currentSendPosition = 0; } - + auto nodeList = DependencyManager::get(); - + // make sure we actually have samples downloaded to inject if (_audioData.size()) { - + // setup the packet for injected audio QByteArray injectAudioPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeInjectAudio); QDataStream packetStream(&injectAudioPacket, QIODevice::Append); - + // pack some placeholder sequence number for now int numPreSequenceNumberBytes = injectAudioPacket.size(); packetStream << (quint16)0; - + // pack stream identifier (a generated UUID) packetStream << QUuid::createUuid(); - + // pack the stereo/mono type of the stream packetStream << _options.stereo; - + // pack the flag for loopback uchar loopbackFlag = (uchar) true; packetStream << loopbackFlag; - + // pack the position for injected audio int positionOptionOffset = injectAudioPacket.size(); packetStream.writeRawData(reinterpret_cast(&_options.position), sizeof(_options.position)); - + // pack our orientation for injected audio int orientationOptionOffset = injectAudioPacket.size(); packetStream.writeRawData(reinterpret_cast(&_options.orientation), sizeof(_options.orientation)); - + // pack zero for radius float radius = 0; packetStream << radius; - + // pack 255 for attenuation byte int volumeOptionOffset = injectAudioPacket.size(); quint8 volume = MAX_INJECTOR_VOLUME * _options.volume; packetStream << volume; - + packetStream << _options.ignorePenumbra; - + QElapsedTimer timer; timer.start(); int nextFrame = 0; - + int numPreAudioDataBytes = injectAudioPacket.size(); bool shouldLoop = _options.loop; - + // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks quint16 outgoingInjectedAudioSequenceNumber = 0; while (_currentSendPosition < _audioData.size() && !_shouldStop) { - + int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, _audioData.size() - _currentSendPosition); - + // Measure the loudness of this frame _loudness = 0.0f; for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) { @@ -215,45 +225,45 @@ void AudioInjector::injectToMixer() { sizeof(_options.orientation)); volume = MAX_INJECTOR_VOLUME * _options.volume; memcpy(injectAudioPacket.data() + volumeOptionOffset, &volume, sizeof(volume)); - + // resize the QByteArray to the right size injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy); // pack the sequence number memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes, &outgoingInjectedAudioSequenceNumber, sizeof(quint16)); - + // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet memcpy(injectAudioPacket.data() + numPreAudioDataBytes, _audioData.data() + _currentSendPosition, bytesToCopy); - + // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - + // send off this audio packet nodeList->writeDatagram(injectAudioPacket, audioMixer); outgoingInjectedAudioSequenceNumber++; - + _currentSendPosition += bytesToCopy; - + // send two packets before the first sleep so the mixer can start playback right away - + if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) { - + // process events in case we have been told to stop and be deleted QCoreApplication::processEvents(); - + if (_shouldStop) { break; } - + // not the first packet and not done // sleep for the appropriate time int usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; - + if (usecToSleep > 0) { usleep(usecToSleep); - } + } } if (shouldLoop && _currentSendPosition >= _audioData.size()) { @@ -261,13 +271,13 @@ void AudioInjector::injectToMixer() { } } } - + setIsFinished(true); } void AudioInjector::stop() { _shouldStop = true; - + if (_options.localOnly) { // we're only a local injector, so we can say we are finished right away too setIsFinished(true); diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index ca39dcbdc4..0513b70bd8 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -59,6 +59,7 @@ public slots: void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; } float getLoudness() const { return _loudness; } bool isPlaying() const { return !_isFinished; } + void restartPortionAfterFinished(); signals: void finished(); diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 577328ee18..7dc6010f8f 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -53,21 +53,25 @@ Sound::Sound(const QUrl& url, bool isStereo) : _isStereo(isStereo), _isReady(false) { - + } void Sound::downloadFinished(QNetworkReply* reply) { // replace our byte array with the downloaded data QByteArray rawAudioByteArray = reply->readAll(); + QString fileName = reply->url().fileName(); - if (reply->hasRawHeader("Content-Type")) { + const QString WAV_EXTENSION = ".wav"; + + if (reply->hasRawHeader("Content-Type") || fileName.endsWith(WAV_EXTENSION)) { QByteArray headerContentType = reply->rawHeader("Content-Type"); // WAV audio file encountered if (headerContentType == "audio/x-wav" || headerContentType == "audio/wav" - || headerContentType == "audio/wave") { + || headerContentType == "audio/wave" + || fileName.endsWith(WAV_EXTENSION)) { QByteArray outputAudioByteArray; @@ -80,7 +84,7 @@ void Sound::downloadFinished(QNetworkReply* reply) { _isStereo = true; qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << reply->url() << "as stereo audio file."; } - + // Process as RAW file downSample(rawAudioByteArray); } @@ -88,7 +92,7 @@ void Sound::downloadFinished(QNetworkReply* reply) { } else { qCDebug(audio) << "Network reply without 'Content-Type'."; } - + _isReady = true; reply->deleteLater(); } @@ -99,16 +103,16 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) { // we want to convert it to the format that the audio-mixer wants // which is signed, 16-bit, 24Khz - + int numSourceSamples = rawAudioByteArray.size() / sizeof(AudioConstants::AudioSample); - + int numDestinationBytes = rawAudioByteArray.size() / sizeof(AudioConstants::AudioSample); if (_isStereo && numSourceSamples % 2 != 0) { numDestinationBytes += sizeof(AudioConstants::AudioSample); } - + _byteArray.resize(numDestinationBytes); - + int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data(); int16_t* destinationSamples = (int16_t*) _byteArray.data(); @@ -134,22 +138,22 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) { } void Sound::trimFrames() { - + const uint32_t inputFrameCount = _byteArray.size() / sizeof(int16_t); const uint32_t trimCount = 1024; // number of leading and trailing frames to trim - + if (inputFrameCount <= (2 * trimCount)) { return; } - + int16_t* inputFrameData = (int16_t*)_byteArray.data(); AudioEditBufferFloat32 editBuffer(1, inputFrameCount); editBuffer.copyFrames(1, inputFrameCount, inputFrameData, false /*copy in*/); - + editBuffer.linearFade(0, trimCount, true); editBuffer.linearFade(inputFrameCount - trimCount, inputFrameCount, false); - + editBuffer.copyFrames(1, inputFrameCount, inputFrameData, true /*copy out*/); } @@ -238,7 +242,7 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou } else if (qFromLittleEndian(fileHeader.wave.numChannels) > 2) { qCDebug(audio) << "Currently not support audio files with more than 2 channels."; } - + if (qFromLittleEndian(fileHeader.wave.bitsPerSample) != 16) { qCDebug(audio) << "Currently not supporting non 16bit audio files."; return; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 3c6b7bd3f5..a86ce78655 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -30,7 +30,7 @@ const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; Setting::Handle currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address", DEFAULT_HIFI_ADDRESS); AddressManager::AddressManager() : - _rootPlaceName(), + _host(), _rootPlaceID(), _positionGetter(NULL), _orientationGetter(NULL) @@ -45,7 +45,7 @@ const QUrl AddressManager::currentAddress() const { QUrl hifiURL; hifiURL.setScheme(HIFI_URL_SCHEME); - hifiURL.setHost(_rootPlaceName); + hifiURL.setHost(_host); hifiURL.setPath(currentPath()); return hifiURL; @@ -123,6 +123,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())))) { // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(lookupUrl.path()); + } else if (handleDomainID(lookupUrl.host())){ + // no place name - this is probably a domain ID + // try to look up the domain ID on the metaverse API + attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path()); } else { // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after @@ -161,11 +165,18 @@ void AddressManager::handleLookupString(const QString& lookupString) { } } +const QString DATA_OBJECT_DOMAIN_KEY = "domain"; + + void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); - goToAddressFromObject(dataObject.toVariantMap(), requestReply); + if (!dataObject.isEmpty()) { + goToAddressFromObject(dataObject.toVariantMap(), requestReply); + } else if (responseObject.contains(DATA_OBJECT_DOMAIN_KEY)) { + goToAddressFromObject(responseObject.toVariantMap(), requestReply); + } emit lookupResultsFinished(); } @@ -180,6 +191,8 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QVariantMap locationMap; if (dataObject.contains(DATA_OBJECT_PLACE_KEY)) { locationMap = dataObject[DATA_OBJECT_PLACE_KEY].toMap(); + } else if (dataObject.contains(DATA_OBJECT_DOMAIN_KEY)) { + locationMap = dataObject; } else { locationMap = dataObject[DATA_OBJECT_USER_LOCATION_KEY].toMap(); } @@ -206,6 +219,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); + const QString DOMAIN_ID_KEY = "id"; + QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); + QUuid domainID(domainIDString); + if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); @@ -219,10 +236,6 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); - const QString DOMAIN_ID_KEY = "id"; - QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); - QUuid domainID(domainIDString); - qCDebug(networking) << "Possible domain change required to connect to domain with ID" << domainID << "via ice-server at" << iceServerAddress; @@ -235,8 +248,12 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const // set our current root place name to the name that came back const QString PLACE_NAME_KEY = "name"; - QString newRootPlaceName = rootMap[PLACE_NAME_KEY].toString(); - setRootPlaceName(newRootPlaceName); + QString placeName = rootMap[PLACE_NAME_KEY].toString(); + if (!placeName.isEmpty()) { + setHost(placeName); + } else { + setHost(domainIDString); + } // check if we had a path to override the path returned QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); @@ -304,6 +321,24 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q QByteArray(), NULL, requestParams); } +const QString GET_DOMAIN_ID = "/api/v1/domains/%1"; + +void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QString& overridePath) { + // assume this is a domain ID and see if we can get any info on it + QString domainID = QUrl::toPercentEncoding(lookupString); + + QVariantMap requestParams; + if (!overridePath.isEmpty()) { + requestParams.insert(OVERRIDE_PATH_KEY, overridePath); + } + + AccountManager::getInstance().sendRequest(GET_DOMAIN_ID.arg(domainID), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, + apiCallbackParameters(), + QByteArray(), NULL, requestParams); +} + bool AddressManager::handleNetworkAddress(const QString& lookupString) { const QString IP_ADDRESS_REGEX_STRING = "^((?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?::(\\d{1,5}))?$"; @@ -335,7 +370,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; if (!hostnameRegex.cap(2).isEmpty()) { - domainPort = (qint16) hostnameRegex.cap(2).toInt(); + domainPort = (qint16)hostnameRegex.cap(2).toInt(); } emit lookupResultsFinished(); @@ -347,6 +382,14 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { return false; } +bool AddressManager::handleDomainID(const QString& host) { + const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; + + QRegExp domainIDRegex(UUID_REGEX_STRING, Qt::CaseInsensitive); + + return (domainIDRegex.indexIn(host) != -1); +} + void AddressManager::handlePath(const QString& path) { if (!handleViewpoint(path)) { qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path << @@ -422,16 +465,16 @@ bool AddressManager::handleUsername(const QString& lookupString) { return false; } -void AddressManager::setRootPlaceName(const QString& rootPlaceName) { - if (rootPlaceName != _rootPlaceName) { - _rootPlaceName = rootPlaceName; - emit rootPlaceNameChanged(_rootPlaceName); +void AddressManager::setHost(const QString& host) { + if (host != _host) { + _host = host; + emit hostChanged(_host); } } void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { - _rootPlaceName = hostname; + _host = hostname; _rootPlaceID = QUuid(); qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 5831d62603..2b587a9bd7 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -35,7 +35,7 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(bool isConnected READ isConnected) Q_PROPERTY(QUrl href READ currentAddress) Q_PROPERTY(QString protocol READ getProtocol) - Q_PROPERTY(QString hostname READ getRootPlaceName) + Q_PROPERTY(QString hostname READ getHost) Q_PROPERTY(QString pathname READ currentPath) public: bool isConnected(); @@ -46,10 +46,11 @@ public: const QUuid& getRootPlaceID() const { return _rootPlaceID; } - const QString& getRootPlaceName() const { return _rootPlaceName; } - void setRootPlaceName(const QString& rootPlaceName); + const QString& getHost() const { return _host; } + void setHost(const QString& host); void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath = QString()); + void attemptDomainIDLookup(const QString& lookupString, const QString& overridePath = QString()); void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } @@ -78,7 +79,7 @@ signals: bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); void pathChangeRequired(const QString& newPath); - void rootPlaceNameChanged(const QString& newRootPlaceName); + void hostChanged(const QString& newHost); protected: AddressManager(); private slots: @@ -95,8 +96,9 @@ private: void handlePath(const QString& path); bool handleViewpoint(const QString& viewpointString, bool shouldFace = false); bool handleUsername(const QString& lookupString); + bool handleDomainID(const QString& host); - QString _rootPlaceName; + QString _host; QUuid _rootPlaceID; PositionGetter _positionGetter; OrientationGetter _orientationGetter; diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index e210ee6f6e..9e3e924933 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -28,19 +28,19 @@ AudioScriptingInterface& AudioScriptingInterface::getInstance() { AudioScriptingInterface::AudioScriptingInterface() : _localAudioInterface(NULL) { - + } ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions& injectorOptions) { if (QThread::currentThread() != thread()) { ScriptAudioInjector* injector = NULL; - + QMetaObject::invokeMethod(this, "playSound", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ScriptAudioInjector*, injector), Q_ARG(Sound*, sound), Q_ARG(const AudioInjectorOptions&, injectorOptions)); return injector; } - + if (sound) { // stereo option isn't set from script, this comes from sound metadata or filename AudioInjectorOptions optionsCopy = injectorOptions; diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index bbc9a57db8..d74e1ed1e0 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -22,22 +22,23 @@ class AudioScriptingInterface : public QObject { Q_OBJECT public: static AudioScriptingInterface& getInstance(); - + void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } - + protected: // this method is protected to stop C++ callers from calling, but invokable from script Q_INVOKABLE ScriptAudioInjector* playSound(Sound* sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); - + Q_INVOKABLE void injectGeneratedNoise(bool inject); Q_INVOKABLE void selectPinkNoise(); Q_INVOKABLE void selectSine440(); Q_INVOKABLE void setStereoInput(bool stereo); - + signals: void mutedByMixer(); void environmentMuted(); + void receivedFirstPacket(); private: AudioScriptingInterface(); diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 84c8ae4939..79e83e9b40 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -18,7 +18,7 @@ #include "PathUtils.h" -QString& PathUtils::resourcesPath() { +const QString& PathUtils::resourcesPath() { #ifdef Q_OS_MAC static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; #else diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 6b6893574b..12b1d57641 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -12,12 +12,17 @@ #ifndef hifi_PathUtils_h #define hifi_PathUtils_h +#include -#include +#include "DependencyManager.h" -namespace PathUtils { - QString& resourcesPath(); -} +class PathUtils : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + Q_PROPERTY(QString resources READ resourcesPath) +public: + static const QString& resourcesPath(); +}; QString fileNameWithoutExtension(const QString& fileName, const QVector possibleExtensions); QString findMostRecentFileExtension(const QString& originalFileName, QVector possibleExtensions);