diff --git a/examples/grab.js b/examples/grab.js index 7ed69e9664..593c671506 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -13,6 +13,7 @@ var isGrabbing = false; var grabbedEntity = null; +var actionID = null; var prevMouse = {}; var deltaMouse = { z: 0 @@ -87,7 +88,6 @@ function mousePressEvent(event) { Entities.editEntity(grabbedEntity, { gravity: {x: 0, y: 0, z: 0} }); - } } @@ -110,7 +110,10 @@ function updateDropLine(position) { function mouseReleaseEvent() { if (isGrabbing) { + // Controller.mouseMoveEvent.disconnect(mouseMoveEvent); isGrabbing = false; + Entities.deleteAction(grabbedEntity, actionID); + actionID = null; // only restore the original gravity if it's not zero. This is to avoid... // 1. interface A grabs an entity and locally saves off its gravity @@ -228,23 +231,28 @@ function update(deltaTime) { } if (shouldRotate) { angularVelocity = Vec3.subtract(angularVelocity, Vec3.multiply(angularVelocity, ANGULAR_DAMPING_RATE)); + Entities.editEntity(grabbedEntity, { + rotation: currentRotation, + angularVelocity: angularVelocity + }); } else { angularVelocity = entityProps.angularVelocity; } - Entities.editEntity(grabbedEntity, { - position: currentPosition, - rotation: currentRotation, - velocity: newVelocity, - angularVelocity: angularVelocity - }); + var newSpeed = Vec3.length(newVelocity); + if (!actionID) { + actionID = Entities.addAction("pull-to-point", grabbedEntity, {target: targetPosition, speed: newSpeed}); + } else { + Entities.updateAction(grabbedEntity, actionID, {target: targetPosition, speed: newSpeed}); + } + updateDropLine(targetPosition); } } -Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update); 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/src/Application.cpp b/interface/src/Application.cpp index c58b10880c..ec521ea7d8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -482,7 +482,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : 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 @@ -2477,8 +2477,9 @@ void Application::update(float deltaTime) { updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... - - DependencyManager::get()->updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... + + //loop through all the other avatars and simulate them... + DependencyManager::get()->updateOtherAvatars(deltaTime); updateCamera(deltaTime); // handle various camera tweaks like off axis projection updateDialogs(deltaTime); // update various stats dialogs if present @@ -2492,6 +2493,7 @@ void Application::update(float deltaTime) { _physicsEngine.deleteObjects(_entitySimulation.getObjectsToDelete()); _physicsEngine.addObjects(_entitySimulation.getObjectsToAdd()); _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange()); + _entitySimulation.applyActionChanges(); _entitySimulation.unlock(); AvatarManager* avatarManager = DependencyManager::get().data(); @@ -2514,7 +2516,8 @@ void Application::update(float deltaTime) { if (!_aboutToQuit) { PerformanceTimer perfTimer("entities"); - // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk deadlock.) + // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk + // deadlock.) _entitySimulation.handleCollisionEvents(collisionEvents); // NOTE: the _entities.update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). @@ -2527,11 +2530,12 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("overlays"); _overlays.update(deltaTime); } - + { PerformanceTimer perfTimer("myAvatar"); updateMyAvatarLookAtPosition(); - DependencyManager::get()->updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes + // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes + DependencyManager::get()->updateMyAvatar(deltaTime); } { @@ -3760,7 +3764,7 @@ 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(); 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/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp new file mode 100644 index 0000000000..f26dd006ff --- /dev/null +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -0,0 +1,101 @@ +// +// EntityActionInterface.cpp +// libraries/entities/src +// +// Created by Seth Alves on 2015-6-4 +// 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 +// + +#include "EntityItem.h" + +#include "EntityActionInterface.h" + + +EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeString) { + QString normalizedActionTypeString = actionTypeString.toLower().remove('-').remove('_'); + if (normalizedActionTypeString == "none") { + return ACTION_TYPE_NONE; + } + if (normalizedActionTypeString == "pulltopoint") { + return ACTION_TYPE_PULL_TO_POINT; + } + + qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString; + return ACTION_TYPE_NONE; +} + +QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { + switch(actionType) { + case ACTION_TYPE_NONE: + return "none"; + case ACTION_TYPE_PULL_TO_POINT: + return "pullToPoint"; + } + assert(false); + return "none"; +} + +glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVariantMap arguments, + QString argumentName, bool& ok) { + if (!arguments.contains(argumentName)) { + qDebug() << objectName << "requires argument:" << argumentName; + ok = false; + return vec3(); + } + + QVariant resultV = arguments[argumentName]; + if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) { + qDebug() << objectName << "argument" << argumentName << "must be a map"; + ok = false; + return vec3(); + } + + QVariantMap resultVM = resultV.toMap(); + if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) { + qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z"; + ok = false; + return vec3(); + } + + QVariant xV = resultVM["x"]; + QVariant yV = resultVM["y"]; + QVariant zV = resultVM["z"]; + + bool xOk = true; + bool yOk = true; + bool zOk = true; + float x = xV.toFloat(&xOk); + float y = yV.toFloat(&yOk); + float z = zV.toFloat(&zOk); + if (!xOk || !yOk || !zOk) { + qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z and values of type float."; + ok = false; + return vec3(); + } + + return vec3(x, y, z); +} + + +float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments, + QString argumentName, bool& ok) { + if (!arguments.contains(argumentName)) { + qDebug() << objectName << "requires argument:" << argumentName; + ok = false; + return 0.0f; + } + + QVariant vV = arguments[argumentName]; + bool vOk = true; + float v = vV.toFloat(&vOk); + + if (!vOk) { + ok = false; + return 0.0f; + } + + return v; +} diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h new file mode 100644 index 0000000000..74efae3239 --- /dev/null +++ b/libraries/entities/src/EntityActionInterface.h @@ -0,0 +1,49 @@ +// +// EntityActionInterface.h +// libraries/entities/src +// +// Created by Seth Alves on 2015-6-2 +// 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 +// + +#ifndef hifi_EntityActionInterface_h +#define hifi_EntityActionInterface_h + +#include + +class EntitySimulation; + +enum EntityActionType { + // keep these synchronized with actionTypeFromString and actionTypeToString + ACTION_TYPE_NONE, + ACTION_TYPE_PULL_TO_POINT +}; + + +class EntityActionInterface { +public: + EntityActionInterface() { } + virtual ~EntityActionInterface() { } + virtual const QUuid& getID() const = 0; + virtual void removeFromSimulation(EntitySimulation* simulation) const = 0; + virtual const EntityItemPointer& getOwnerEntity() const = 0; + virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0; + virtual bool updateArguments(QVariantMap arguments) = 0; + // virtual QByteArray serialize() = 0; + // static EntityActionPointer deserialize(EntityItemPointer ownerEntity, QByteArray data); + + static EntityActionType actionTypeFromString(QString actionTypeString); + static QString actionTypeToString(EntityActionType actionType); + +protected: + + static glm::vec3 extractVec3Argument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); + static float extractFloatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); +}; + +typedef std::shared_ptr EntityActionPointer; + +#endif // hifi_EntityActionInterface_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6b8e1833d7..4a10aad95d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -25,6 +25,7 @@ #include "EntityItem.h" #include "EntitiesLogging.h" #include "EntityTree.h" +#include "EntitySimulation.h" bool EntityItem::_sendPhysicsUpdates = true; @@ -1334,3 +1335,46 @@ void EntityItem::updateSimulatorID(const QUuid& value) { _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } + +bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) { + assert(action); + const QUuid& actionID = action->getID(); + assert(!_objectActions.contains(actionID) || _objectActions[actionID] == action); + _objectActions[actionID] = action; + + assert(action->getOwnerEntity().get() == this); + + simulation->addAction(action); + + return false; +} + +bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) { + if (!_objectActions.contains(actionID)) { + return false; + } + EntityActionPointer action = _objectActions[actionID]; + return action->updateArguments(arguments); +} + +bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) { + if (_objectActions.contains(actionID)) { + EntityActionPointer action = _objectActions[actionID]; + _objectActions.remove(actionID); + action->setOwnerEntity(nullptr); + action->removeFromSimulation(simulation); + return true; + } + return false; +} + +void EntityItem::clearActions(EntitySimulation* simulation) { + QHash::iterator i = _objectActions.begin(); + while (i != _objectActions.end()) { + const QUuid id = i.key(); + EntityActionPointer action = _objectActions[id]; + i = _objectActions.erase(i); + action->setOwnerEntity(nullptr); + action->removeFromSimulation(simulation); + } +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 638d9bf1d7..77a6627853 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -25,9 +25,10 @@ #include #include -#include "EntityItemID.h" -#include "EntityItemProperties.h" -#include "EntityItemPropertiesDefaults.h" +#include "EntityItemID.h" +#include "EntityItemProperties.h" +#include "EntityItemPropertiesDefaults.h" +#include "EntityActionInterface.h" #include "EntityTypes.h" class EntitySimulation; @@ -95,7 +96,7 @@ public: }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly - + EntityItem(const EntityItemID& entityItemID); EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); virtual ~EntityItem(); @@ -107,7 +108,7 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties() const; - + /// returns true if something changed virtual bool setProperties(const EntityItemProperties& properties); @@ -121,7 +122,7 @@ public: /// Last edited time of this entity universal usecs quint64 getLastEdited() const { return _lastEdited; } - void setLastEdited(quint64 lastEdited) + void setLastEdited(quint64 lastEdited) { _lastEdited = _lastUpdated = lastEdited; _changedOnServer = glm::max(lastEdited, _changedOnServer); } float getEditedAgo() const /// Elapsed seconds since this entity was last edited { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } @@ -135,26 +136,26 @@ public: // TODO: eventually only include properties changed since the params.lastViewFrustumSent time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - + virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const; - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, + int& propertyCount, OctreeElement::AppendState& appendState) const { /* do nothing*/ }; - static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead, + static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); virtual int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData) + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { return 0; } virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, @@ -170,7 +171,7 @@ public: // perform update virtual void update(const quint64& now) { _lastUpdated = now; } quint64 getLastUpdated() const { return _lastUpdated; } - + // perform linear extrapolation for SimpleEntitySimulation void simulate(const quint64& now); void simulateKinematicMotion(float timeElapsed, bool setFlags=true); @@ -178,10 +179,10 @@ public: virtual bool needsToCallUpdate() const { return false; } virtual void debugDump() const; - + virtual bool supportsDetailedRayIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const { return true; } // attributes applicable to all entity types @@ -207,7 +208,6 @@ public: inline const glm::vec3& getDimensions() const { return _transform.getScale(); } virtual void setDimensions(const glm::vec3& value); - float getGlowLevel() const { return _glowLevel; } void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; } @@ -231,7 +231,7 @@ public: const glm::vec3& getAcceleration() const { return _acceleration; } /// get acceleration in meters/second/second void setAcceleration(const glm::vec3& value) { _acceleration = value; } /// acceleration in meters/second/second bool hasAcceleration() const { return _acceleration != ENTITY_ITEM_ZERO_VEC3; } - + float getDamping() const { return _damping; } void setDamping(float value) { _damping = value; } @@ -253,7 +253,7 @@ public: /// is this entity mortal, in that it has a lifetime set, and will automatically be deleted when that lifetime expires bool isMortal() const { return _lifetime != ENTITY_ITEM_IMMORTAL_LIFETIME; } - + /// age of this entity in seconds float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; } bool lifetimeHasExpired() const; @@ -272,7 +272,7 @@ public: const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity /// registration point as ratio of entity - void setRegistrationPoint(const glm::vec3& value) + void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = glm::clamp(value, 0.0f, 1.0f); } const glm::vec3& getAngularVelocity() const { return _angularVelocity; } @@ -300,7 +300,7 @@ public: bool getLocked() const { return _locked; } void setLocked(bool value) { _locked = value; } - + const QString& getUserData() const { return _userData; } void setUserData(const QString& value) { _userData = value; } @@ -308,13 +308,13 @@ public: void setSimulatorID(const QUuid& value); void updateSimulatorID(const QUuid& value); quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } - + const QString& getMarketplaceID() const { return _marketplaceID; } void setMarketplaceID(const QString& value) { _marketplaceID = value; } - - // TODO: get rid of users of getRadius()... + + // TODO: get rid of users of getRadius()... float getRadius() const; - + virtual bool contains(const glm::vec3& point) const; virtual bool isReadyToComputeShape() { return true; } @@ -345,11 +345,11 @@ public: uint32_t getDirtyFlags() const { return _dirtyFlags; } void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } - + bool isMoving() const; void* getPhysicsInfo() const { return _physicsInfo; } - + void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElement* getElement() const { return _element; } @@ -365,12 +365,18 @@ public: void getAllTerseUpdateProperties(EntityItemProperties& properties) const; + bool addAction(EntitySimulation* simulation, EntityActionPointer action); + bool updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments); + bool removeAction(EntitySimulation* simulation, const QUuid& actionID); + void clearActions(EntitySimulation* simulation); + protected: static bool _sendPhysicsUpdates; EntityTypes::EntityType _type; QUuid _id; - quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, and physics changes + quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, + // and physics changes quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes quint64 _lastEdited; // last official local or remote edit time quint64 _lastBroadcast; // the last time we sent an edit packet about this entity @@ -419,12 +425,12 @@ protected: // // damping = 1 - exp(-1 / timescale) // - - // NOTE: Radius support is obsolete, but these private helper functions are available for this class to + + // NOTE: Radius support is obsolete, but these private helper functions are available for this class to // parse old data streams - + /// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis - void setRadius(float value); + void setRadius(float value); // DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about. uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation @@ -433,6 +439,8 @@ protected: EntityTreeElement* _element = nullptr; // set by EntityTreeElement void* _physicsInfo = nullptr; // set by EntitySimulation bool _simulated; // set by EntitySimulation + + QHash _objectActions; }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 5a897f7fe7..836e7f5225 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -11,13 +11,14 @@ #include -#include "EntityScriptingInterface.h" #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" #include "ZoneEntityItem.h" #include "EntitiesLogging.h" +#include "EntitySimulation.h" +#include "EntityScriptingInterface.h" EntityScriptingInterface::EntityScriptingInterface() : _entityTree(NULL) @@ -83,7 +84,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties entity->setLastBroadcast(usecTimestampNow()); // This Node is creating a new object. If it's in motion, set this Node as the simulator. bidForSimulationOwnership(propertiesWithSimID); - entity->setSimulatorID(propertiesWithSimID.getSimulatorID()); // and make note of it now, so we can act on it right away. + // and make note of it now, so we can act on it right away. + entity->setSimulatorID(propertiesWithSimID.getSimulatorID()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -105,7 +107,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit _entityTree->lockForRead(); EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity)); - + if (entity) { results = entity->getProperties(); @@ -124,7 +126,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit } _entityTree->unlock(); } - + return results; } @@ -228,7 +230,7 @@ QVector EntityScriptingInterface::findEntities(const glm::vec3& center, f QVector entities; _entityTree->findEntities(center, radius, entities); _entityTree->unlock(); - + foreach (EntityItemPointer entity, entities) { result << entity->getEntityItemID(); } @@ -244,7 +246,7 @@ QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn QVector entities; _entityTree->findEntities(box, entities); _entityTree->unlock(); - + foreach (EntityItemPointer entity, entities) { result << entity->getEntityItemID(); } @@ -401,7 +403,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra } } - bool EntityScriptingInterface::setVoxels(QUuid entityID, std::function actor) { if (!_entityTree) { @@ -439,23 +440,83 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID, return true; } - bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) { return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) { polyVoxEntity.setSphere(center, radius, value); }); } - bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) { return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) { polyVoxEntity.setVoxelInVolume(position, value); }); } - bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) { return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) { polyVoxEntity.setAll(value); }); } + + +bool EntityScriptingInterface::actionWorker(const QUuid& entityID, + std::function actor) { + if (!_entityTree) { + return false; + } + + _entityTree->lockForWrite(); + + EntitySimulation* simulation = _entityTree->getSimulation(); + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + qDebug() << "actionWorker -- unknown entity" << entityID; + _entityTree->unlock(); + return false; + } + + if (!simulation) { + qDebug() << "actionWorker -- no simulation" << entityID; + _entityTree->unlock(); + return false; + } + + bool success = actor(simulation, entity); + _entityTree->unlock(); + return success; +} + + +QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, + const QUuid& entityID, + const QVariantMap& arguments) { + QUuid actionID = QUuid::createUuid(); + bool success = actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString); + if (actionType == ACTION_TYPE_NONE) { + return false; + } + if (simulation->actionFactory(actionType, actionID, entity, arguments)) { + return true; + } + return false; + }); + if (success) { + return actionID; + } + return QUuid(); +} + + +bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) { + return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + return entity->updateAction(simulation, actionID, arguments); + }); +} + + +bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) { + return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + return entity->removeAction(simulation, actionID); + }); +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 738a011b15..c6bc43c8c6 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -54,14 +54,14 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende Q_OBJECT public: EntityScriptingInterface(); - + EntityEditPacketSender* getEntityPacketSender() const { return (EntityEditPacketSender*)getPacketSender(); } virtual NodeType_t getServerNodeType() const { return NodeType::EntityServer; } virtual OctreeEditPacketSender* createPacketSender() { return new EntityEditPacketSender(); } void setEntityTree(EntityTree* modelTree); EntityTree* getEntityTree(EntityTree*) { return _entityTree; } - + public slots: // returns true if the DomainServer will allow this Node/Avatar to make changes @@ -88,11 +88,11 @@ public slots: /// will return a EntityItemID.isKnownID = false if no models are in the radius /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QUuid findClosestEntity(const glm::vec3& center, float radius) const; - + /// finds models within the search sphere specified by the center point and radius /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; - + /// finds models within the search sphere specified by the center point and radius /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; @@ -118,13 +118,16 @@ public slots: Q_INVOKABLE void setSendPhysicsUpdates(bool value); Q_INVOKABLE bool getSendPhysicsUpdates() const; - bool setVoxels(QUuid entityID, std::function actor); Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value); Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); Q_INVOKABLE void dumpTree() const; + Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); + Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); + Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); + signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -152,6 +155,8 @@ signals: void clearingEntities(); private: + bool actionWorker(const QUuid& entityID, std::function actor); + bool setVoxels(QUuid entityID, std::function actor); void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index bce16ba1c2..0c9b3efee6 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -24,9 +24,9 @@ typedef QSet SetOfEntities; typedef QVector VectorOfEntities; -// the EntitySimulation needs to know when these things change on an entity, +// the EntitySimulation needs to know when these things change on an entity, // so it can sort EntityItem or relay its state to the PhysicsEngine. -const int DIRTY_SIMULATION_FLAGS = +const int DIRTY_SIMULATION_FLAGS = EntityItem::DIRTY_POSITION | EntityItem::DIRTY_ROTATION | EntityItem::DIRTY_LINEAR_VELOCITY | @@ -56,6 +56,15 @@ public: friend class EntityTree; + virtual EntityActionPointer actionFactory(EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments) { return nullptr; } + virtual void addAction(EntityActionPointer action) { _actionsToAdd += action; } + virtual void removeAction(const QUuid actionID) { _actionsToRemove += actionID; } + virtual void removeActions(QList actionIDsToRemove) { _actionsToRemove += actionIDsToRemove; } + virtual void applyActionChanges() { _actionsToAdd.clear(); _actionsToRemove.clear(); } + protected: // these only called by the EntityTree? /// \param entity pointer to EntityItem to be added /// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked @@ -112,8 +121,12 @@ protected: SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion -private: + private: void moveSimpleKinematics(); + + protected: + QList _actionsToAdd; + QList _actionsToRemove; }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 47eb845daa..363f3c56d7 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -279,7 +279,7 @@ void EntityTree::setSimulation(EntitySimulation* simulation) { if (simulation) { // assert that the simulation's backpointer has already been properly connected assert(simulation->getEntityTree() == this); - } + } if (_simulation && _simulation != simulation) { // It's important to clearEntities() on the simulation since taht will update each // EntityItem::_simulationState correctly so as to not confuse the next _simulation. @@ -381,6 +381,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) } if (_simulation) { + theEntity->clearActions(_simulation); _simulation->removeEntity(theEntity); } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5897d3e9a8..1ad3ef6b1c 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -31,7 +31,7 @@ public: class EntityItemFBXService { public: virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) = 0; - virtual const Model* getModelForEntityItem(EntityItemPointer entityItem) = 0; + virtual const Model* getModelForEntityItem(EntityItemPointer entityItem) = 0; virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) = 0; }; @@ -63,23 +63,23 @@ public: // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const { return true; } virtual PacketType expectedDataPacketType() const { return PacketTypeEntityData; } - virtual bool canProcessVersion(PacketVersion thisVersion) const + virtual bool canProcessVersion(PacketVersion thisVersion) const { return thisVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS; } virtual bool handlesEditPacketType(PacketType packetType) const; virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); virtual bool rootElementHasData() const { return true; } - + // the root at least needs to store the number of entities in the packet/buffer virtual int minimumRequiredRootDataBytes() const { return sizeof(uint16_t); } virtual bool suppressEmptySubtrees() const { return false; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const; virtual bool mustIncludeAllChildData() const { return false; } - virtual bool versionHasSVOfileBreaks(PacketVersion thisVersion) const + virtual bool versionHasSVOfileBreaks(PacketVersion thisVersion) const { return thisVersion >= VERSION_ENTITIES_HAS_FILE_BREAKS; } - + virtual void update(); // The newer API... @@ -111,13 +111,13 @@ public: /// \param foundEntities[out] vector of EntityItemPointer /// \remark Side effect: any initial contents in foundEntities will be lost void findEntities(const glm::vec3& center, float radius, QVector& foundEntities); - + /// finds all entities that touch a cube /// \param cube the query cube in world-frame (meters) /// \param foundEntities[out] vector of non-EntityItemPointer /// \remark Side effect: any initial contents in entities will be lost void findEntities(const AACube& cube, QVector& foundEntities); - + /// finds all entities that touch a box /// \param box the query box in world-frame (meters) /// \param foundEntities[out] vector of non-EntityItemPointer @@ -129,13 +129,13 @@ public: bool hasAnyDeletedEntities() const { return _recentlyDeletedEntityItemIDs.size() > 0; } bool hasEntitiesDeletedSince(quint64 sinceTime); - bool encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, + bool encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); void forgetEntitiesDeletedBefore(quint64 sinceTime); int processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); int processEraseMessageDetails(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); - + EntityItemFBXService* getFBXService() const { return _fbxService; } void setFBXService(EntityItemFBXService* service) { _fbxService = service; } const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) { @@ -144,7 +144,7 @@ public: const Model* getModelForEntityItem(EntityItemPointer entityItem) { return _fbxService ? _fbxService->getModelForEntityItem(entityItem) : NULL; } - + EntityTreeElement* getContainingElement(const EntityItemID& entityItemID) /*const*/; void setContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element); void debugDumpMap(); @@ -158,13 +158,14 @@ public: void emitEntityScriptChanging(const EntityItemID& entityItemID); void setSimulation(EntitySimulation* simulation); - + EntitySimulation* getSimulation() const { return _simulation; } + bool wantEditLogging() const { return _wantEditLogging; } void setWantEditLogging(bool value) { _wantEditLogging = value; } bool writeToMap(QVariantMap& entityDescription, OctreeElement* element, bool skipDefaultValues); bool readFromMap(QVariantMap& entityDescription); - + float getContentsLargestDimension(); signals: @@ -177,7 +178,7 @@ signals: private: void processRemovedEntities(const DeleteEntityOperator& theOperator); - bool updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& properties, + bool updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& properties, EntityTreeElement* containingElement, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); static bool findNearPointOperation(OctreeElement* element, void* extraData); @@ -198,7 +199,7 @@ private: QHash _entityToElementMap; EntitySimulation* _simulation; - + bool _wantEditLogging = false; void maybeNotifyNewCollisionSoundURL(const QString& oldCollisionSoundURL, const QString& newCollisionSoundURL); }; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 90fb035d7b..05d1904384 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -33,7 +33,7 @@ public: _totalItems(0), _movingItems(0) { } - + QList _movingEntities; int _totalElements; int _totalItems; @@ -42,8 +42,8 @@ public: class EntityTreeElementExtraEncodeData { public: - EntityTreeElementExtraEncodeData() : - elementCompleted(false), + EntityTreeElementExtraEncodeData() : + elementCompleted(false), subtreeCompleted(false), entities() { memset(childCompleted, 0, sizeof(childCompleted)); @@ -140,7 +140,7 @@ public: virtual bool canRayIntersect() const { return hasEntities(); } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking, float distanceToElementCube); virtual bool findSpherePenetration(const glm::vec3& center, float radius, @@ -148,10 +148,11 @@ public: const EntityItems& getEntities() const { return *_entityItems; } EntityItems& getEntities() { return *_entityItems; } - + bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; } void setTree(EntityTree* tree) { _myTree = tree; } + EntityTree* getTree() const { return _myTree; } bool updateEntity(const EntityItem& entity); void addEntityItem(EntityItemPointer entity); 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/physics/src/DynamicCharacterController.cpp b/libraries/physics/src/DynamicCharacterController.cpp index 1fca236f63..ebdba5f754 100644 --- a/libraries/physics/src/DynamicCharacterController.cpp +++ b/libraries/physics/src/DynamicCharacterController.cpp @@ -156,7 +156,7 @@ void DynamicCharacterController::playerStep(btCollisionWorld* dynaWorld, btScala velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp; } _rigidBody->setLinearVelocity(actualVelocity + tau * velocityCorrection); - } + } } } diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp new file mode 100644 index 0000000000..6ff4098ba8 --- /dev/null +++ b/libraries/physics/src/ObjectAction.cpp @@ -0,0 +1,35 @@ +// +// ObjectAction.cpp +// libraries/physcis/src +// +// Created by Seth Alves 2015-6-2 +// 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 +// + +#include "EntitySimulation.h" + +#include "ObjectAction.h" + +ObjectAction::ObjectAction(QUuid id, EntityItemPointer ownerEntity) : + btActionInterface(), + _id(id), + _active(false), + _ownerEntity(ownerEntity) { +} + +ObjectAction::~ObjectAction() { +} + +void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { + qDebug() << "ObjectAction::updateAction called"; +} + +void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) { +} + +void ObjectAction::removeFromSimulation(EntitySimulation* simulation) const { + simulation->removeAction(_id); +} diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h new file mode 100644 index 0000000000..10b086c07d --- /dev/null +++ b/libraries/physics/src/ObjectAction.h @@ -0,0 +1,50 @@ +// +// ObjectAction.h +// libraries/physcis/src +// +// Created by Seth Alves 2015-6-2 +// 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 +// +// http://bulletphysics.org/Bullet/BulletFull/classbtActionInterface.html + +#ifndef hifi_ObjectAction_h +#define hifi_ObjectAction_h + +#include + +#include + +#include + +class ObjectAction : public btActionInterface, public EntityActionInterface { +public: + ObjectAction(QUuid id, EntityItemPointer ownerEntity); + virtual ~ObjectAction(); + + const QUuid& getID() const { return _id; } + virtual void removeFromSimulation(EntitySimulation* simulation) const; + virtual const EntityItemPointer& getOwnerEntity() const { return _ownerEntity; } + virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } + virtual bool updateArguments(QVariantMap arguments) { return false; } + + // these are from btActionInterface + virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); + virtual void debugDraw(btIDebugDraw* debugDrawer); + +private: + QUuid _id; + QReadWriteLock _lock; + +protected: + bool tryLockForRead() { return _lock.tryLockForRead(); } + void lockForWrite() { _lock.lockForWrite(); } + void unlock() { _lock.unlock(); } + + bool _active; + EntityItemPointer _ownerEntity; +}; + +#endif // hifi_ObjectAction_h diff --git a/libraries/physics/src/ObjectActionPullToPoint.cpp b/libraries/physics/src/ObjectActionPullToPoint.cpp new file mode 100644 index 0000000000..78f202a24f --- /dev/null +++ b/libraries/physics/src/ObjectActionPullToPoint.cpp @@ -0,0 +1,69 @@ +// +// ObjectActionPullToPoint.cpp +// libraries/physics/src +// +// Created by Seth Alves 2015-6-2 +// 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 +// + +#include "ObjectMotionState.h" +#include "BulletUtil.h" + +#include "ObjectActionPullToPoint.h" + +ObjectActionPullToPoint::ObjectActionPullToPoint(QUuid id, EntityItemPointer ownerEntity) : + ObjectAction(id, ownerEntity) { + #if WANT_DEBUG + qDebug() << "ObjectActionPullToPoint::ObjectActionPullToPoint"; + #endif +} + +ObjectActionPullToPoint::~ObjectActionPullToPoint() { + #if WANT_DEBUG + qDebug() << "ObjectActionPullToPoint::~ObjectActionPullToPoint"; + #endif +} + +void ObjectActionPullToPoint::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { + if (!tryLockForRead()) { + // don't risk hanging the thread running the physics simulation + return; + } + void* physicsInfo = _ownerEntity->getPhysicsInfo(); + + if (_active && physicsInfo) { + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (rigidBody) { + glm::vec3 offset = _target - bulletToGLM(rigidBody->getCenterOfMassPosition()); + float offsetLength = glm::length(offset); + if (offsetLength > IGNORE_POSITION_DELTA) { + glm::vec3 newVelocity = glm::normalize(offset) * _speed; + rigidBody->setLinearVelocity(glmToBullet(newVelocity)); + rigidBody->activate(); + } else { + rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); + } + } + } + unlock(); +} + + +bool ObjectActionPullToPoint::updateArguments(QVariantMap arguments) { + bool ok = true; + glm::vec3 target = EntityActionInterface::extractVec3Argument("pull-to-point action", arguments, "target", ok); + float speed = EntityActionInterface::extractFloatArgument("pull-to-point action", arguments, "speed", ok); + if (ok) { + lockForWrite(); + _target = target; + _speed = speed; + _active = true; + unlock(); + return true; + } + return false; +} diff --git a/libraries/physics/src/ObjectActionPullToPoint.h b/libraries/physics/src/ObjectActionPullToPoint.h new file mode 100644 index 0000000000..3aca70d640 --- /dev/null +++ b/libraries/physics/src/ObjectActionPullToPoint.h @@ -0,0 +1,34 @@ +// +// ObjectActionPullToPoint.h +// libraries/physics/src +// +// Created by Seth Alves 2015-6-3 +// 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 +// + +#ifndef hifi_ObjectActionPullToPoint_h +#define hifi_ObjectActionPullToPoint_h + +#include + +#include +#include "ObjectAction.h" + +class ObjectActionPullToPoint : public ObjectAction { +public: + ObjectActionPullToPoint(QUuid id, EntityItemPointer ownerEntity); + virtual ~ObjectActionPullToPoint(); + + virtual bool updateArguments(QVariantMap arguments); + virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); + +private: + + glm::vec3 _target; + float _speed; +}; + +#endif // hifi_ObjectActionPullToPoint_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 70853fb5f6..711c5e49da 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -9,10 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "PhysicalEntitySimulation.h" #include "PhysicsHelpers.h" #include "PhysicsLogging.h" #include "ShapeManager.h" +#include "ObjectActionPullToPoint.h" + +#include "PhysicalEntitySimulation.h" PhysicalEntitySimulation::PhysicalEntitySimulation() { } @@ -232,3 +234,37 @@ void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionE } } +EntityActionPointer PhysicalEntitySimulation::actionFactory(EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments) { + EntityActionPointer action = nullptr; + switch (type) { + case ACTION_TYPE_NONE: + return nullptr; + case ACTION_TYPE_PULL_TO_POINT: + action = (EntityActionPointer) new ObjectActionPullToPoint(id, ownerEntity); + break; + } + + bool ok = action->updateArguments(arguments); + if (ok) { + ownerEntity->addAction(this, action); + return action; + } + + action = nullptr; + return action; +} + +void PhysicalEntitySimulation::applyActionChanges() { + if (_physicsEngine) { + foreach (EntityActionPointer actionToAdd, _actionsToAdd) { + _physicsEngine->addAction(actionToAdd); + } + foreach (QUuid actionToRemove, _actionsToRemove) { + _physicsEngine->removeAction(actionToRemove); + } + } + EntitySimulation::applyActionChanges(); +} diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 1aae27962a..82b0f8ad51 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -32,6 +32,12 @@ public: void init(EntityTree* tree, PhysicsEngine* engine, EntityEditPacketSender* packetSender); + virtual EntityActionPointer actionFactory(EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments); + virtual void applyActionChanges(); + protected: // only called by EntitySimulation // overrides for EntitySimulation virtual void updateEntitiesInternal(const quint64& now); diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 54b05b63d6..e5c974dfbc 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -436,3 +436,28 @@ int16_t PhysicsEngine::getCollisionMask(int16_t group) const { const int16_t* mask = _collisionMasks.find(btHashInt((int)group)); return mask ? *mask : COLLISION_MASK_DEFAULT; } + +void PhysicsEngine::addAction(EntityActionPointer action) { + assert(action); + const QUuid& actionID = action->getID(); + if (_objectActions.contains(actionID)) { + assert(_objectActions[actionID] == action); + return; + } + + _objectActions[actionID] = action; + + // bullet needs a pointer to the action, but it doesn't use shared pointers. + // is there a way to bump the reference count? + ObjectAction* objectAction = static_cast(action.get()); + _dynamicsWorld->addAction(objectAction); +} + +void PhysicsEngine::removeAction(const QUuid actionID) { + if (_objectActions.contains(actionID)) { + EntityActionPointer action = _objectActions[actionID]; + ObjectAction* objectAction = static_cast(action.get()); + _dynamicsWorld->removeAction(objectAction); + _objectActions.remove(actionID); + } +} diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index db00a2ba01..6bb6ef8305 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -24,6 +24,7 @@ #include "DynamicCharacterController.h" #include "ObjectMotionState.h" #include "ThreadSafeDynamicsWorld.h" +#include "ObjectAction.h" const float HALF_SIMULATION_EXTENT = 512.0f; // meters @@ -93,6 +94,9 @@ public: int16_t getCollisionMask(int16_t group) const; + void addAction(EntityActionPointer action); + void removeAction(const QUuid actionID); + private: void removeContacts(ObjectMotionState* motionState); @@ -120,6 +124,9 @@ private: QUuid _sessionID; CollisionEvents _collisionEvents; + + QHash _objectActions; + btHashMap _collisionMasks; }; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 175eff059f..58ecf4adb2 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -54,7 +54,6 @@ public: bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); const QString& getScriptName() const { return _scriptName; } - void cleanupMenuItems(); QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,