diff --git a/examples/tests/avatarAttachmentTest.js b/examples/tests/avatarAttachmentTest.js new file mode 100644 index 0000000000..23b94ba8d5 --- /dev/null +++ b/examples/tests/avatarAttachmentTest.js @@ -0,0 +1,148 @@ +// +// avatarAttachmentTest.js +// examples/tests +// +// Created by Anthony Thibault on January 7, 2016 +// Copyright 2016 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 +// + +// Test for MyAvatar attachment API +// MyAvatar.setAttachmentData(); +// MyAvatar.getAttachmentData(); + +// Toggle button helper +function ToggleButtonBuddy(x, y, width, height, urls) { + this.up = Overlays.addOverlay("image", { + x: x, y: y, width: width, height: height, + subImage: { x: 0, y: height, width: width, height: height}, + imageURL: urls.up, + visible: true, + alpha: 1.0 + }); + this.down = Overlays.addOverlay("image", { + x: x, y: y, width: width, height: height, + subImage: { x: 0, y: height, width: width, height: height}, + imageURL: urls.down, + visible: false, + alpha: 1.0 + }); + this.callbacks = []; + + var self = this; + Controller.mousePressEvent.connect(function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + if (clickedOverlay === self.up) { + // flip visiblity + Overlays.editOverlay(self.up, {visible: false}); + Overlays.editOverlay(self.down, {visible: true}); + self.onToggle(true); + } else if (clickedOverlay === self.down) { + // flip visiblity + Overlays.editOverlay(self.up, {visible: true}); + Overlays.editOverlay(self.down, {visible: false}); + self.onToggle(false); + } + }); +} +ToggleButtonBuddy.prototype.destroy = function () { + Overlays.deleteOverlay(this.up); + Overlays.deleteOverlay(this.down); +}; +ToggleButtonBuddy.prototype.addToggleHandler = function (callback) { + this.callbacks.push(callback); + return callback; +}; +ToggleButtonBuddy.prototype.removeToggleHandler = function (callback) { + var index = this.callbacks.indexOf(callback); + if (index != -1) { + this.callbacks.splice(index, 1); + } +}; +ToggleButtonBuddy.prototype.onToggle = function (isDown) { + var i, l = this.callbacks.length; + for (i = 0; i < l; i++) { + this.callbacks[i](isDown); + } +}; + +var windowDimensions = Controller.getViewportDimensions(); +var BUTTON_WIDTH = 64; +var BUTTON_HEIGHT = 64; +var BUTTON_PADDING = 10; +var buttonPositionX = windowDimensions.x - BUTTON_PADDING - BUTTON_WIDTH; +var buttonPositionY = (windowDimensions.y - BUTTON_HEIGHT) / 2 - (BUTTON_HEIGHT + BUTTON_PADDING); + +var hatButton = new ToggleButtonBuddy(buttonPositionX, buttonPositionY, BUTTON_WIDTH, BUTTON_HEIGHT, { + up: "https://s3.amazonaws.com/hifi-public/tony/icons/hat-up.svg", + down: "https://s3.amazonaws.com/hifi-public/tony/icons/hat-down.svg" +}); + +buttonPositionY += BUTTON_HEIGHT + BUTTON_PADDING; +var coatButton = new ToggleButtonBuddy(buttonPositionX, buttonPositionY, BUTTON_WIDTH, BUTTON_HEIGHT, { + up: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-up.svg", + down: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-down.svg" +}); + +var HAT_ATTACHMENT = { + modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx", + jointName: "Head", + translation: {"x": 0, "y": 0.2, "z": 0}, + rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, + scale: 1, + isSoft: false +}; + +var COAT_ATTACHMENT = { + modelURL: "https://hifi-content.s3.amazonaws.com/ozan/dev/clothes/coat/coat_rig.fbx", + jointName: "Hips", + translation: {"x": 0, "y": 0, "z": 0}, + rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, + scale: 1, + isSoft: true +}; + +hatButton.addToggleHandler(function (isDown) { + if (isDown) { + wearAttachment(HAT_ATTACHMENT); + } else { + removeAttachment(HAT_ATTACHMENT); + } +}); + +coatButton.addToggleHandler(function (isDown) { + if (isDown) { + wearAttachment(COAT_ATTACHMENT); + } else { + removeAttachment(COAT_ATTACHMENT); + } +}); + + +function wearAttachment(attachment) { + MyAvatar.attach(attachment.modelURL, + attachment.jointName, + attachment.translation, + attachment.rotation, + attachment.scale, + attachment.isSoft); +} + +function removeAttachment(attachment) { + var attachments = MyAvatar.attachmentData; + var i, l = attachments.length; + for (i = 0; i < l; i++) { + if (attachments[i].modelURL === attachment.modelURL) { + attachments.splice(i, 1); + MyAvatar.attachmentData = attachments; + break; + } + } +} + +Script.scriptEnding.connect(function() { + hatButton.destroy(); + coatbutton.destroy(); +}); diff --git a/examples/tests/qmlTest.js b/examples/tests/qmlTest.js index f1aa59cc09..c891b6a1b7 100644 --- a/examples/tests/qmlTest.js +++ b/examples/tests/qmlTest.js @@ -8,33 +8,6 @@ qmlWindow = new OverlayWindow({ visible: true }); -//qmlWindow.eventBridge.webEventReceived.connect(function(data) { -// print("JS Side event received: " + data); -//}); -// -//var titles = ["A", "B", "C"]; -//var titleIndex = 0; -// -//Script.setInterval(function() { -// qmlWindow.eventBridge.emitScriptEvent("JS Event sent"); -// var size = qmlWindow.size; -// var position = qmlWindow.position; -// print("Window visible: " + qmlWindow.visible) -// if (qmlWindow.visible) { -// print("Window size: " + size.x + "x" + size.y) -// print("Window pos: " + position.x + "x" + position.y) -// qmlWindow.setVisible(false); -// } else { -// qmlWindow.setVisible(true); -// qmlWindow.setTitle(titles[titleIndex]); -// qmlWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100); -// titleIndex += 1; -// titleIndex %= titles.length; -// } -//}, 2 * 1000); -// -//Script.setTimeout(function() { -// print("Closing script"); -// qmlWindow.close(); -// Script.stop(); -//}, 15 * 1000) +Script.setInterval(function() { + qmlWindow.raise(); +}, 2 * 1000); diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index d84ea306b8..6c94f9d254 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -16,6 +16,7 @@ import "styles" DialogContainer { id: root HifiConstants { id: hifi } + z: 1000 objectName: "AddressBarDialog" @@ -150,6 +151,17 @@ DialogContainer { addressLine.forceActiveFocus() } } + + Timer { + running: root.enabled + interval: 500 + repeat: true + onTriggered: { + if (root.enabled && !addressLine.activeFocus) { + addressLine.forceActiveFocus() + } + } + } onVisibleChanged: { if (!visible) { diff --git a/interface/resources/qml/AvatarInputs.qml b/interface/resources/qml/AvatarInputs.qml index 49aeee3074..ce729d468d 100644 --- a/interface/resources/qml/AvatarInputs.qml +++ b/interface/resources/qml/AvatarInputs.qml @@ -10,21 +10,32 @@ import Hifi 1.0 as Hifi import QtQuick 2.4 import QtQuick.Controls 1.3 import QtGraphicalEffects 1.0 +import Qt.labs.settings 1.0 Hifi.AvatarInputs { id: root objectName: "AvatarInputs" - anchors.fill: parent + width: mirrorWidth + height: controls.height + mirror.height + x: 10; y: 5 -// width: 800 -// height: 600 -// color: "black" - readonly property int iconPadding: 5 readonly property int mirrorHeight: 215 readonly property int mirrorWidth: 265 - readonly property int mirrorTopPad: iconPadding - readonly property int mirrorLeftPad: 10 readonly property int iconSize: 24 + readonly property int iconPadding: 5 + + Settings { + category: "Overlay.AvatarInputs" + property alias x: root.x + property alias y: root.y + } + + MouseArea { + id: hover + hoverEnabled: true + drag.target: parent + anchors.fill: parent + } Item { id: mirror @@ -32,18 +43,8 @@ Hifi.AvatarInputs { height: root.mirrorVisible ? root.mirrorHeight : 0 visible: root.mirrorVisible anchors.left: parent.left - anchors.leftMargin: root.mirrorLeftPad - anchors.top: parent.top - anchors.topMargin: root.mirrorTopPad clip: true - MouseArea { - id: hover - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - } - Image { id: closeMirror visible: hover.containsMouse @@ -64,7 +65,7 @@ Hifi.AvatarInputs { Image { id: zoomIn - visible: hover.containsMouse + visible: hover.containsMouse width: root.iconSize height: root.iconSize anchors.bottom: parent.bottom @@ -82,14 +83,11 @@ Hifi.AvatarInputs { } Item { + id: controls width: root.mirrorWidth height: 44 visible: !root.isHMD - - x: root.mirrorLeftPad - y: root.mirrorVisible ? root.mirrorTopPad + root.mirrorHeight : 5 - - + anchors.top: mirror.bottom Rectangle { anchors.fill: parent diff --git a/interface/resources/qml/Global.js b/interface/resources/qml/Global.js new file mode 100644 index 0000000000..d41dde3c61 --- /dev/null +++ b/interface/resources/qml/Global.js @@ -0,0 +1,165 @@ +var OFFSCREEN_ROOT_OBJECT_NAME = "desktopRoot" +var OFFSCREEN_WINDOW_OBJECT_NAME = "topLevelWindow" + +function findChild(item, name) { + for (var i = 0; i < item.children.length; ++i) { + if (item.children[i].objectName === name) { + return item.children[i]; + } + } + return null; +} + +function findParent(item, name) { + while (item) { + if (item.objectName === name) { + return item; + } + item = item.parent; + } + return null; +} + +function getDesktop(item) { + return findParent(item, OFFSCREEN_ROOT_OBJECT_NAME); +} + +function findRootMenu(item) { + item = getDesktop(item); + return item ? item.rootMenu : null; +} + + +function getTopLevelWindows(item) { + var desktop = getDesktop(item); + var currentWindows = []; + if (!desktop) { + console.log("Could not find desktop for " + item) + return currentWindows; + } + + for (var i = 0; i < desktop.children.length; ++i) { + var child = desktop.children[i]; + if (Global.OFFSCREEN_WINDOW_OBJECT_NAME === child.objectName) { + var windowId = child.toString(); + currentWindows.push(child) + } + } + return currentWindows; +} + + +function getDesktopWindow(item) { + item = findParent(item, OFFSCREEN_WINDOW_OBJECT_NAME); + return item; +} + +function closeWindow(item) { + item = findDialog(item); + if (item) { + item.enabled = false + } else { + console.warn("Could not find top level dialog") + } +} + +function findMenuChild(menu, childName) { + if (!menu) { + return null; + } + + if (menu.type !== 2) { + console.warn("Tried to find child of a non-menu"); + return null; + } + + var items = menu.items; + var count = items.length; + for (var i = 0; i < count; ++i) { + var child = items[i]; + var name; + switch (child.type) { + case 2: + name = child.title; + break; + case 1: + name = child.text; + break; + default: + break; + } + + if (name && name === childName) { + return child; + } + } +} + +function findMenu(rootMenu, path) { + if ('string' === typeof(path)) { + path = [ path ] + } + + var currentMenu = rootMenu; + for (var i = 0; currentMenu && i < path.length; ++i) { + currentMenu = findMenuChild(currentMenu, path[i]); + } + + return currentMenu; +} + +function findInRootMenu(item, path) { + return findMenu(findRootMenu(item), path); +} + + +function menuItemsToListModel(parent, items) { + var newListModel = Qt.createQmlObject('import QtQuick 2.5; ListModel {}', parent); + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + switch (item.type) { + case 2: + newListModel.append({"type":item.type, "name": item.title, "item": item}) + break; + case 1: + newListModel.append({"type":item.type, "name": item.text, "item": item}) + break; + case 0: + newListModel.append({"type":item.type, "name": "-----", "item": item}) + break; + } + } + return newListModel; +} + +function raiseWindow(item) { + var targetWindow = getDesktopWindow(item); + if (!targetWindow) { + console.warn("Could not find top level window for " + item); + return; + } + + var desktop = getDesktop(targetWindow); + if (!desktop) { + //console.warn("Could not find desktop for window " + targetWindow); + return; + } + + var maxZ = 0; + var minZ = 100000; + var windows = desktop.windows; + windows.sort(function(a, b){ + return a.z - b.z; + }); + var lastZ = -1; + var lastTargetZ = -1; + for (var i = 0; i < windows.length; ++i) { + var window = windows[i]; + if (window.z > lastZ) { + lastZ = window.z; + ++lastTargetZ; + } + window.z = lastTargetZ; + } + targetWindow.z = lastTargetZ + 1; +} diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 188351c113..008aaeccc3 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -47,10 +47,20 @@ VrDialog { anchors.fill: parent focus: true + property var originalUrl + property var lastFixupTime: 0 + onUrlChanged: { var currentUrl = url.toString(); var newUrl = urlHandler.fixupUrl(currentUrl).toString(); if (newUrl != currentUrl) { + var now = new Date().valueOf(); + if (url === originalUrl && (now - lastFixupTime < 100)) { + console.warn("URL fixup loop detected") + return; + } + originalUrl = url + lastFixupTime = now url = newUrl; } } diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 2d8bf08dc5..f8217371e7 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -5,6 +5,8 @@ import QtWebChannel 1.0 import QtWebSockets 1.0 import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel +import "Global.js" as Global + import "controls" import "styles" @@ -23,6 +25,10 @@ VrDialog { contentImplicitWidth: clientArea.implicitWidth contentImplicitHeight: clientArea.implicitHeight property alias source: pageLoader.source + + function raiseWindow() { + Global.raiseWindow(root) + } Item { id: clientArea diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/Root.qml index 14799f78c0..49262cb7db 100644 --- a/interface/resources/qml/Root.qml +++ b/interface/resources/qml/Root.qml @@ -2,16 +2,23 @@ import Hifi 1.0 import QtQuick 2.5 import QtQuick.Controls 1.4 +import "Global.js" as Global + // This is our primary 'window' object to which all dialogs and controls will // be childed. Root { id: root - objectName: "desktopRoot" - anchors.fill: parent + objectName: Global.OFFSCREEN_ROOT_OBJECT_NAME + anchors.fill: parent; + property var windows: []; property var rootMenu: Menu { objectName: "rootMenu" } + onChildrenChanged: { + windows = Global.getTopLevelWindows(root); + } + onParentChanged: { forceActiveFocus(); } diff --git a/interface/resources/qml/controls/VrDialog.qml b/interface/resources/qml/controls/VrDialog.qml index 411fdbbb0b..18cab04533 100644 --- a/interface/resources/qml/controls/VrDialog.qml +++ b/interface/resources/qml/controls/VrDialog.qml @@ -3,6 +3,8 @@ import QtQuick.Controls 1.2 import "." import "../styles" +import "../Global.js" as Global + /* * FIXME Need to create a client property here so that objects can be * placed in it without having to think about positioning within the outer @@ -45,9 +47,18 @@ DialogBase { // trigger making it visible. if (enabled) { visible = true; + if (root.parent) { + Global.raiseWindow(root); + } } } + onParentChanged: { + if (enabled && parent) { + Global.raiseWindow(root); + } + } + // The actual animator Behavior on opacity { NumberAnimation { @@ -60,6 +71,12 @@ DialogBase { onOpacityChanged: { visible = (opacity != 0.0); } + + Component.onCompleted: { + if (visible) { + Global.raiseWindow(root); + } + } // Some dialogs should be destroyed when they become invisible, // so handle that @@ -118,6 +135,7 @@ DialogBase { y: root.titleY width: root.titleWidth height: root.titleHeight + onClicked: Global.raiseWindow(root) drag { target: root diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e8673e7eac..b142b6cb71 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -696,7 +696,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : auto userInputMapper = DependencyManager::get(); connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { using namespace controller; - static auto offscreenUi = DependencyManager::get(); + auto offscreenUi = DependencyManager::get(); if (offscreenUi->navigationFocused()) { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; @@ -737,6 +737,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : case Action::UI_NAV_SELECT: key = Qt::Key_Return; break; + default: + break; } if (navAxis) { @@ -834,7 +836,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : return (float)qApp->getMyAvatar()->getCharacterController()->onGround(); })); _applicationStateDevice->addInputVariant(QString("NavigationFocused"), controller::StateController::ReadLambda([]() -> float { - static auto offscreenUi = DependencyManager::get(); + auto offscreenUi = DependencyManager::get(); return offscreenUi->navigationFocused() ? 1.0 : 0.0; })); @@ -1049,6 +1051,8 @@ void Application::cleanupBeforeQuit() { #ifdef HAVE_IVIEWHMD DependencyManager::destroy(); #endif + + DependencyManager::destroy(); } void Application::emptyLocalCache() { @@ -1082,8 +1086,8 @@ Application::~Application() { // remove avatars from physics engine DependencyManager::get()->clearOtherAvatars(); VectorOfMotionStates motionStates; - DependencyManager::get()->getObjectsToDelete(motionStates); - _physicsEngine->deleteObjects(motionStates); + DependencyManager::get()->getObjectsToRemoveFromPhysics(motionStates); + _physicsEngine->removeObjects(motionStates); DependencyManager::destroy(); DependencyManager::destroy(); @@ -1336,7 +1340,7 @@ void Application::paintGL() { renderArgs._context->syncCache(); } - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { PerformanceTimer perfTimer("Mirror"); auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); @@ -1625,7 +1629,7 @@ void Application::aboutApp() { InfoView::show(INFO_HELP_PATH); } -void Application::showEditEntitiesHelp() { +void Application::showHelp() { InfoView::show(INFO_EDIT_ENTITIES_PATH); } @@ -1983,7 +1987,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_H: if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::Mirror); + Menu::getInstance()->triggerOption(MenuOption::MiniMirror); } else { Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)); if (!Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { @@ -3083,11 +3087,11 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("physics"); static VectorOfMotionStates motionStates; - _entitySimulation.getObjectsToDelete(motionStates); - _physicsEngine->deleteObjects(motionStates); + _entitySimulation.getObjectsToRemoveFromPhysics(motionStates); + _physicsEngine->removeObjects(motionStates); getEntities()->getTree()->withWriteLock([&] { - _entitySimulation.getObjectsToAdd(motionStates); + _entitySimulation.getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); }); @@ -3100,9 +3104,9 @@ void Application::update(float deltaTime) { _entitySimulation.applyActionChanges(); AvatarManager* avatarManager = DependencyManager::get().data(); - avatarManager->getObjectsToDelete(motionStates); - _physicsEngine->deleteObjects(motionStates); - avatarManager->getObjectsToAdd(motionStates); + avatarManager->getObjectsToRemoveFromPhysics(motionStates); + _physicsEngine->removeObjects(motionStates); + avatarManager->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); avatarManager->getObjectsToChange(motionStates); _physicsEngine->changeObjects(motionStates); @@ -3739,7 +3743,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se auto backgroundRenderData = make_shared(&_environment); auto backgroundRenderPayload = make_shared(backgroundRenderData); BackgroundRenderData::_item = _main3DScene->allocateID(); - pendingChanges.resetItem(WorldBoxRenderData::_item, backgroundRenderPayload); + pendingChanges.resetItem(BackgroundRenderData::_item, backgroundRenderPayload); } else { } @@ -4081,7 +4085,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { }); foreach (EntityItemPointer entity, entities) { - if (!entity->isReadyToComputeShape()) { + if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 78b753880d..39e5453d1d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -295,7 +295,7 @@ public slots: #endif void aboutApp(); - void showEditEntitiesHelp(); + void showHelp(); void cycleCamera(); void cameraMenuChanged(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 85af912892..d57c395b48 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -51,13 +51,16 @@ Menu* Menu::getInstance() { } Menu::Menu() { - MenuWrapper * fileMenu = addMenu("File"); -#ifdef Q_OS_MAC - addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole); -#endif auto dialogsManager = DependencyManager::get(); AccountManager& accountManager = AccountManager::getInstance(); + // File/Application menu ---------------------------------- + MenuWrapper* fileMenu = addMenu("File"); + + // File > Quit + addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp,SLOT(quit()), QAction::QuitRole); + + // File > Login menu items { addActionToQMenuAndActionHash(fileMenu, MenuOption::Login); @@ -68,256 +71,269 @@ Menu::Menu() { dialogsManager.data(), &DialogsManager::toggleLoginDialog); } - // File Menu > Scripts section -- "Advanced" grouping - addDisabledActionAndSeparator(fileMenu, "Scripts", UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, - qApp, SLOT(loadDialog()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, - Qt::CTRL | Qt::SHIFT | Qt::Key_O, qApp, SLOT(loadScriptURLDialog()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, qApp, SLOT(stopAllScripts()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R, - qApp, SLOT(reloadAllScripts()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, - qApp, SLOT(toggleRunningScriptsWidget()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // File > Update -- FIXME: needs implementation + auto updateAction = addActionToQMenuAndActionHash(fileMenu, "Update"); + updateAction->setDisabled(true); - auto addressManager = DependencyManager::get(); + // File > Help + addActionToQMenuAndActionHash(fileMenu, MenuOption::Help, 0, qApp, SLOT(showHelp())); - addDisabledActionAndSeparator(fileMenu, "History"); + // File > Crash Reporter...-- FIXME: needs implementation + auto crashReporterAction = addActionToQMenuAndActionHash(fileMenu, "Crash Reporter..."); + crashReporterAction->setDisabled(true); - QAction* backAction = addActionToQMenuAndActionHash(fileMenu, - MenuOption::Back, - 0, - addressManager.data(), - SLOT(goBack())); - - QAction* forwardAction = addActionToQMenuAndActionHash(fileMenu, - MenuOption::Forward, - 0, - addressManager.data(), - SLOT(goForward())); - - // connect to the AddressManager signal to enable and disable the back and forward menu items - connect(addressManager.data(), &AddressManager::goBackPossible, backAction, &QAction::setEnabled); - connect(addressManager.data(), &AddressManager::goForwardPossible, forwardAction, &QAction::setEnabled); - - // set the two actions to start disabled since the stacks are clear on startup - backAction->setDisabled(true); - forwardAction->setDisabled(true); - - addDisabledActionAndSeparator(fileMenu, "Location"); - qApp->getBookmarks()->setupMenus(this, fileMenu); - - addActionToQMenuAndActionHash(fileMenu, - MenuOption::AddressBar, - Qt::CTRL | Qt::Key_L, - dialogsManager.data(), - SLOT(toggleAddressBar())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyAddress, 0, - addressManager.data(), SLOT(copyAddress()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyPath, 0, - addressManager.data(), SLOT(copyPath()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(fileMenu, - MenuOption::Quit, - Qt::CTRL | Qt::Key_Q, - qApp, - SLOT(quit()), - QAction::QuitRole); + // File > About + addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole); + // Audio menu ---------------------------------- + MenuWrapper* audioMenu = addMenu("Audio"); + auto audioIO = DependencyManager::get(); + + // Audio > Mute + addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, + audioIO.data(), SLOT(toggleMute())); + + // Audio > Level Meter [advanced] -- FIXME: needs implementation + auto levelMeterAction = addCheckableActionToQMenuAndActionHash(audioMenu, "Level Meter", 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); + levelMeterAction->setDisabled(true); + + + // Avatar menu ---------------------------------- + MenuWrapper* avatarMenu = addMenu("Avatar"); + auto avatarManager = DependencyManager::get(); + QObject* avatar = avatarManager->getMyAvatar(); + + // Avatar > Attachments... + addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments, 0, + dialogsManager.data(), SLOT(editAttachments())); + + // Avatar > Size + MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); + + // Avatar > Size > Increase + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::IncreaseAvatarSize, + 0, // QML Qt::Key_Plus, + avatar, SLOT(increaseSize())); + + // Avatar > Size > Decrease + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::DecreaseAvatarSize, + 0, // QML Qt::Key_Minus, + avatar, SLOT(decreaseSize())); + + // Avatar > Size > Reset + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::ResetAvatarSize, + 0, // QML Qt::Key_Equal, + avatar, SLOT(resetSize())); + + // Avatar > Reset Sensors + addActionToQMenuAndActionHash(avatarMenu, + MenuOption::ResetSensors, + 0, // QML Qt::Key_Apostrophe, + qApp, SLOT(resetSensors())); + + + // Display menu ---------------------------------- + // FIXME - this is not yet matching Alan's spec because it doesn't have + // menus for "2D"/"3D" - we need to add support for detecting the appropriate + // default 3D display mode + addMenu(DisplayPlugin::MENU_PATH()); + MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); + QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); + displayModeGroup->setExclusive(true); + + + // View menu ---------------------------------- + MenuWrapper* viewMenu = addMenu("View"); + QActionGroup* cameraModeGroup = new QActionGroup(viewMenu); + + // View > [camera group] + cameraModeGroup->setExclusive(true); + + // View > First Person + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::FirstPerson, 0, // QML Qt:: Key_P + false, qApp, SLOT(cameraMenuChanged()))); + + // View > Third Person + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::ThirdPerson, 0, + true, qApp, SLOT(cameraMenuChanged()))); + + // View > Mirror + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::FullscreenMirror, 0, // QML Qt::Key_H, + false, qApp, SLOT(cameraMenuChanged()))); + + // View > Independent [advanced] + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::IndependentMode, 0, + false, qApp, SLOT(cameraMenuChanged()), + UNSPECIFIED_POSITION, "Advanced")); + + // View > Entity Camera [advanced] + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::CameraEntityMode, 0, + false, qApp, SLOT(cameraMenuChanged()), + UNSPECIFIED_POSITION, "Advanced")); + + viewMenu->addSeparator(); + + // View > Mini Mirror + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MiniMirror, 0, false); + + + // Edit menu ---------------------------------- MenuWrapper* editMenu = addMenu("Edit"); + // Edit > Undo QUndoStack* undoStack = qApp->getUndoStack(); QAction* undoAction = undoStack->createUndoAction(editMenu); undoAction->setShortcut(Qt::CTRL | Qt::Key_Z); addActionToQMenuAndActionHash(editMenu, undoAction); + // Edit > Redo QAction* redoAction = undoStack->createRedoAction(editMenu); redoAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Z); addActionToQMenuAndActionHash(editMenu, redoAction); - addActionToQMenuAndActionHash(editMenu, - MenuOption::Preferences, - Qt::CTRL | Qt::Key_Comma, - dialogsManager.data(), - SLOT(editPreferences()), - QAction::PreferencesRole); + // Edit > Running Sccripts + addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, + qApp, SLOT(toggleRunningScriptsWidget())); - addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, - dialogsManager.data(), SLOT(editAttachments()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // Edit > Open and Run Script from File... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, + qApp, SLOT(loadDialog()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - MenuWrapper* toolsMenu = addMenu("Tools"); - addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, - dialogsManager.data(), SLOT(showScriptEditor()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // Edit > Open and Run Script from Url... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::LoadScriptURL, + Qt::CTRL | Qt::SHIFT | Qt::Key_O, qApp, SLOT(loadScriptURLDialog()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // Edit > Stop All Scripts... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0, qApp, SLOT(stopAllScripts()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Reload All Scripts... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R, + qApp, SLOT(reloadAllScripts()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Scripts Editor... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, + dialogsManager.data(), SLOT(showScriptEditor()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Console... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, + DependencyManager::get().data(), + SLOT(toggleConsole()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Reload All Content [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + // Edit > Package Model... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, + qApp, SLOT(packageModel()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + // Navigate menu ---------------------------------- + MenuWrapper* navigateMenu = addMenu("Navigate"); + + // Navigate > Home -- FIXME: needs implementation + auto homeAction = addActionToQMenuAndActionHash(navigateMenu, "Home"); + homeAction->setDisabled(true); + + addActionToQMenuAndActionHash(navigateMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L, + dialogsManager.data(), SLOT(toggleAddressBar())); + + // Navigate > Directory -- FIXME: needs implementation + addActionToQMenuAndActionHash(navigateMenu, "Directory"); + + // Navigate > Bookmark related menus -- Note: the Bookmark class adds its own submenus here. + qApp->getBookmarks()->setupMenus(this, navigateMenu); + + // Navigate > Copy Address [advanced] + auto addressManager = DependencyManager::get(); + addActionToQMenuAndActionHash(navigateMenu, MenuOption::CopyAddress, 0, + addressManager.data(), SLOT(copyAddress()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Navigate > Copy Path [advanced] + addActionToQMenuAndActionHash(navigateMenu, MenuOption::CopyPath, 0, + addressManager.data(), SLOT(copyPath()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + // Market menu ---------------------------------- + MenuWrapper* marketMenu = addMenu("Market"); + + // Market > Marketplace... -- FIXME: needs implementation + auto marketplaceAction = addActionToQMenuAndActionHash(marketMenu, "Marketplace..."); + marketplaceAction->setDisabled(true); + + + // Settings menu ---------------------------------- + MenuWrapper* settingsMenu = addMenu("Settings"); + + // Settings > Advance Menus + addCheckableActionToQMenuAndActionHash(settingsMenu, "Advanced Menus", 0, false, this, SLOT(toggleAdvancedMenus())); + + // Settings > Developer Menus + addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menus", 0, false, this, SLOT(toggleDeveloperMenus())); + + // Settings > General... + addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, + dialogsManager.data(), SLOT(editPreferences()), QAction::PreferencesRole); + + // Settings > Avatar...-- FIXME: needs implementation + auto avatarAction = addActionToQMenuAndActionHash(settingsMenu, "Avatar..."); + avatarAction->setDisabled(true); + + // Settings > Audio...-- FIXME: needs implementation + auto audioAction = addActionToQMenuAndActionHash(settingsMenu, "Audio..."); + audioAction->setDisabled(true); + + // Settings > LOD...-- FIXME: needs implementation + auto lodAction = addActionToQMenuAndActionHash(settingsMenu, "LOD..."); + lodAction->setDisabled(true); + + // Settings > Control with Speech [advanced] #if defined(Q_OS_MAC) || defined(Q_OS_WIN) auto speechRecognizer = DependencyManager::get(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION, "Advanced"); + QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(settingsMenu, MenuOption::ControlWithSpeech, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, + speechRecognizer->getEnabled(), + speechRecognizer.data(), + SLOT(setEnabled(bool)), + UNSPECIFIED_POSITION, "Advanced"); connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, - 0, // QML Qt::Key_Backslash, - dialogsManager.data(), SLOT(showIRCLink()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, MenuOption::AddRemoveFriends, 0, - qApp, SLOT(showFriendsWindow())); - - MenuWrapper* visibilityMenu = toolsMenu->addMenu("I Am Visible To"); - { - QActionGroup* visibilityGroup = new QActionGroup(toolsMenu); - auto discoverabilityManager = DependencyManager::get(); - - QAction* visibleToEveryone = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToEveryone, - 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::All, - discoverabilityManager.data(), SLOT(setVisibility())); - visibilityGroup->addAction(visibleToEveryone); - - QAction* visibleToFriends = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToFriends, - 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::Friends, - discoverabilityManager.data(), SLOT(setVisibility())); - visibilityGroup->addAction(visibleToFriends); - - QAction* visibleToNoOne = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToNoOne, - 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::None, - discoverabilityManager.data(), SLOT(setVisibility())); - visibilityGroup->addAction(visibleToNoOne); - - connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, - discoverabilityManager.data(), &DiscoverabilityManager::visibilityChanged); - } - - addActionToQMenuAndActionHash(toolsMenu, - MenuOption::ToolWindow, - Qt::CTRL | Qt::ALT | Qt::Key_T, - dialogsManager.data(), - SLOT(toggleToolWindow()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, - MenuOption::Console, - Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, - MenuOption::ResetSensors, - 0, // QML Qt::Key_Apostrophe, - qApp, - SLOT(resetSensors()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, - qApp, SLOT(packageModel()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addMenu(DisplayPlugin::MENU_PATH()); - { - MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); - QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); - displayModeGroup->setExclusive(true); - } - - MenuWrapper* avatarMenu = addMenu("Avatar"); - QObject* avatar = DependencyManager::get()->getMyAvatar(); - + // Settings > Input Devices MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced"); QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); inputModeGroup->setExclusive(false); - - MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::IncreaseAvatarSize, - 0, // QML Qt::Key_Plus, - avatar, - SLOT(increaseSize())); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::DecreaseAvatarSize, - 0, // QML Qt::Key_Minus, - avatar, - SLOT(decreaseSize())); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::ResetAvatarSize, - 0, // QML Qt::Key_Equal, - avatar, - SLOT(resetSize())); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, - NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); - - MenuWrapper* viewMenu = addMenu("View"); - addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - MenuWrapper* cameraModeMenu = viewMenu->addMenu("Camera Mode"); - QActionGroup* cameraModeGroup = new QActionGroup(cameraModeMenu); - cameraModeGroup->setExclusive(true); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::FirstPerson, 0, // QML Qt:: Key_P - false, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::ThirdPerson, 0, - true, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::IndependentMode, 0, - false, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::CameraEntityMode, 0, - false, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::FullscreenMirror, 0, // QML Qt::Key_H, - false, qApp, SLOT(cameraMenuChanged()))); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, - 0, //QML Qt::SHIFT | Qt::Key_H, - true); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView, - 0, false, qApp, SLOT(rotationModeChanged()), - UNSPECIFIED_POSITION, "Advanced"); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::WorldAxes, 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Developer"); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Developer"); - addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, - Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - - addActionToQMenuAndActionHash(viewMenu, MenuOption::AudioNetworkStats, 0, - dialogsManager.data(), SLOT(audioStatsDetails()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - - addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, - dialogsManager.data(), SLOT(bandwidthDetails()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, - dialogsManager.data(), SLOT(octreeStatsDetails()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - - addCheckableActionToQMenuAndActionHash(viewMenu, "Advanced Menus", 0, false, this, SLOT(toggleAdvancedMenus())); - addCheckableActionToQMenuAndActionHash(viewMenu, "Developer Menus", 0, false, this, SLOT(toggleDeveloperMenus())); + // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); + // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, - 0, // QML Qt::SHIFT | Qt::Key_A, - true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing); + // Developer > Render > Ambient Light MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu); ambientLightGroup->setExclusive(true); @@ -333,8 +349,10 @@ Menu::Menu() { ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight8, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight9, 0, false)); + // Developer > Render > Throttle FPS If Not Focus addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true); + // Developer > Render > Resolution MenuWrapper* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); resolutionGroup->setExclusive(true); @@ -344,37 +362,40 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); + // Developer > Render > Stars addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, 0, // QML Qt::Key_Asterisk, true); + // Developer > Render > LOD Tools addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, // QML Qt::SHIFT | Qt::Key_L, dialogsManager.data(), SLOT(lodTools())); - + + // Developer > Assets >>> MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets"); - auto& assetDialogFactory = AssetUploadDialogFactory::getInstance(); assetDialogFactory.setDialogParent(this); - QAction* assetUpload = addActionToQMenuAndActionHash(assetDeveloperMenu, - MenuOption::UploadAsset, - 0, - &assetDialogFactory, - SLOT(showDialog())); - + MenuOption::UploadAsset, + 0, + &assetDialogFactory, + SLOT(showDialog())); + // disable the asset upload action by default - it gets enabled only if asset server becomes present assetUpload->setEnabled(false); - + auto& atpMigrator = ATPAssetMigrator::getInstance(); atpMigrator.setDialogParent(this); - + addActionToQMenuAndActionHash(assetDeveloperMenu, MenuOption::AssetMigration, - 0, &atpMigrator, - SLOT(loadEntityServerFile())); - + 0, &atpMigrator, + SLOT(loadEntityServerFile())); + + // Developer > Avatar >>> MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); + // Developer > Avatar > Face Tracking MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); { QActionGroup* faceTrackerGroup = new QActionGroup(avatarDebugMenu); @@ -423,6 +444,7 @@ Menu::Menu() { #endif #ifdef HAVE_IVIEWHMD + // Developer > Avatar > Eye Tracking MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); @@ -439,9 +461,8 @@ Menu::Menu() { qApp, SLOT(setActiveEyeTracker())); #endif - auto avatarManager = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, - avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); + avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); @@ -450,13 +471,13 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, - avatar, SLOT(setEnableDebugDrawDefaultPose(bool))); + avatar, SLOT(setEnableDebugDrawDefaultPose(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false, - avatar, SLOT(setEnableDebugDrawAnimPose(bool))); + avatar, SLOT(setEnableDebugDrawAnimPose(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawPosition, 0, false, - avatar, SLOT(setEnableDebugDrawPosition(bool))); + avatar, SLOT(setEnableDebugDrawPosition(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true, - avatar, SLOT(setEnableMeshVisible(bool))); + avatar, SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); @@ -473,37 +494,37 @@ Menu::Menu() { avatar, SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); - - + // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::LowVelocityFilter, 0, true, - qApp, SLOT(setLowVelocityFilter(bool))); + qApp, SLOT(setLowVelocityFilter(bool))); MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false); + // Developer > Network >>> MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false, - qApp->getEntityEditPacketSender(), - SLOT(toggleNackPackets())); + qApp->getEntityEditPacketSender(), + SLOT(toggleNackPackets())); addCheckableActionToQMenuAndActionHash(networkMenu, - MenuOption::DisableActivityLogger, - 0, - false, - &UserActivityLogger::getInstance(), - SLOT(disable(bool))); + MenuOption::DisableActivityLogger, + 0, + false, + &UserActivityLogger::getInstance(), + SLOT(disable(bool))); addActionToQMenuAndActionHash(networkMenu, MenuOption::CachesSize, 0, - dialogsManager.data(), SLOT(cachesSizeDialog())); + dialogsManager.data(), SLOT(cachesSizeDialog())); addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, - dialogsManager.data(), SLOT(toggleDiskCacheEditor())); + dialogsManager.data(), SLOT(toggleDiskCacheEditor())); addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0, - dialogsManager.data(), SLOT(showDomainConnectionDialog())); + dialogsManager.data(), SLOT(showDomainConnectionDialog())); + // Developer > Timing and Stats >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing and Stats"); - MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); @@ -520,60 +541,34 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::ShowRealtimeEntityStats); - auto audioIO = DependencyManager::get(); + // Developer > Audio >>> MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, - 0, - true, - audioIO.data(), - SLOT(toggleAudioNoiseReduction())); - + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, + audioIO.data(), SLOT(toggleAudioNoiseReduction())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, - audioIO.data(), SLOT(toggleServerEcho())); + audioIO.data(), SLOT(toggleServerEcho())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, - audioIO.data(), SLOT(toggleLocalEcho())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteAudio, - Qt::CTRL | Qt::Key_M, - false, - audioIO.data(), - SLOT(toggleMute())); - addActionToQMenuAndActionHash(audioDebugMenu, - MenuOption::MuteEnvironment, - 0, - audioIO.data(), - SLOT(sendMuteEnvironmentPacket())); + audioIO.data(), SLOT(toggleLocalEcho())); + addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0, + audioIO.data(), SLOT(sendMuteEnvironmentPacket())); auto scope = DependencyManager::get(); - MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, - Qt::CTRL | Qt::Key_P, false, - scope.data(), - SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, - Qt::CTRL | Qt::SHIFT | Qt::Key_P , - false, - scope.data(), - SLOT(togglePause())); + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, + scope.data(), SLOT(toggle())); + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_P, false, + scope.data(), SLOT(togglePause())); + addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); { - QAction *fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, - 0, - true, - scope.data(), - SLOT(selectAudioScopeFiveFrames())); + QAction* fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, + 0, true, scope.data(), SLOT(selectAudioScopeFiveFrames())); - QAction *twentyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeTwentyFrames, - 0, - false, - scope.data(), - SLOT(selectAudioScopeTwentyFrames())); + QAction* twentyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeTwentyFrames, + 0, false, scope.data(), SLOT(selectAudioScopeTwentyFrames())); - QAction *fiftyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiftyFrames, - 0, - false, - scope.data(), - SLOT(selectAudioScopeFiftyFrames())); + QAction* fiftyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiftyFrames, + 0, false, scope.data(), SLOT(selectAudioScopeFiftyFrames())); QActionGroup* audioScopeFramesGroup = new QActionGroup(audioScopeMenu); audioScopeFramesGroup->addAction(fiveFrames); @@ -581,19 +576,69 @@ Menu::Menu() { audioScopeFramesGroup->addAction(fiftyFrames); } + // Developer > Physics >>> MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + // Developer > Display Crash Options addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); + // Developer > Crash Application addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication())); - MenuWrapper* helpMenu = addMenu("Help"); - addActionToQMenuAndActionHash(helpMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); + // Developer > Log... + addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); + + // Developer > Stats + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + + // Developer > Audio Stats... + addActionToQMenuAndActionHash(developerMenu, MenuOption::AudioNetworkStats, 0, + dialogsManager.data(), SLOT(audioStatsDetails())); + + // Developer > Bandwidth Stats... + addActionToQMenuAndActionHash(developerMenu, MenuOption::BandwidthDetails, 0, + dialogsManager.data(), SLOT(bandwidthDetails())); + + // Developer > Entity Stats... + addActionToQMenuAndActionHash(developerMenu, MenuOption::OctreeStats, 0, + dialogsManager.data(), SLOT(octreeStatsDetails())); + + // Developer > World Axes + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::WorldAxes); + + + +#if 0 /// -------------- REMOVED FOR NOW -------------- + addDisabledActionAndSeparator(navigateMenu, "History"); + QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack())); + QAction* forwardAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Forward, 0, addressManager.data(), SLOT(goForward())); + + // connect to the AddressManager signal to enable and disable the back and forward menu items + connect(addressManager.data(), &AddressManager::goBackPossible, backAction, &QAction::setEnabled); + connect(addressManager.data(), &AddressManager::goForwardPossible, forwardAction, &QAction::setEnabled); + + // set the two actions to start disabled since the stacks are clear on startup + backAction->setDisabled(true); + forwardAction->setDisabled(true); + + MenuWrapper* toolsMenu = addMenu("Tools"); + addActionToQMenuAndActionHash(toolsMenu, + MenuOption::ToolWindow, + Qt::CTRL | Qt::ALT | Qt::Key_T, + dialogsManager.data(), + SLOT(toggleToolWindow()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, + NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); + + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView, + 0, false, qApp, SLOT(rotationModeChanged()), + UNSPECIFIED_POSITION, "Advanced"); -#ifndef Q_OS_MAC - QAction* aboutAction = helpMenu->addAction(MenuOption::AboutApp); - connect(aboutAction, SIGNAL(triggered()), qApp, SLOT(aboutApp())); #endif } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4d898caefc..085b349b8f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -205,7 +205,6 @@ namespace MenuOption { const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; - const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableCharacterController = "Enable avatar collisions"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; @@ -219,8 +218,9 @@ namespace MenuOption { const QString FixGaze = "Fix Gaze (no saccade)"; const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; - const QString FullscreenMirror = "Fullscreen Mirror"; + const QString FullscreenMirror = "Mirror"; const QString GlowWhenSpeaking = "Glow When Speaking"; + const QString Help = "Help..."; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; const QString InputMenu = "Avatar>Input Devices"; @@ -234,7 +234,7 @@ namespace MenuOption { const QString LogExtraTimings = "Log Extra Timing Details"; const QString LowVelocityFilter = "Low Velocity Filter"; const QString MeshVisible = "Draw Mesh"; - const QString Mirror = "Mirror"; + const QString MiniMirror = "Mini Mirror"; const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; const QString MuteFaceTracking = "Mute Face Tracking"; @@ -249,7 +249,7 @@ namespace MenuOption { const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString PhysicsShowHulls = "Draw Collision Hulls"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; - const QString Preferences = "Preferences..."; + const QString Preferences = "General..."; const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadContent = "Reload Content (Clears all caches)"; @@ -277,7 +277,7 @@ namespace MenuOption { const QString RenderAmbientLight9 = "FUNSTON_BEACH_SUNSET"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; - const QString RunningScripts = "Running Scripts"; + const QString RunningScripts = "Running Scripts..."; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 2f7ae62e2e..4f19b33abf 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -108,7 +108,11 @@ Avatar::Avatar(RigPointer rig) : } Avatar::~Avatar() { - assert(_motionState == nullptr); + assert(isDead()); // mark dead before calling the dtor + if (_motionState) { + delete _motionState; + _motionState = nullptr; + } } const float BILLBOARD_LOD_DISTANCE = 40.0f; @@ -585,7 +589,7 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { // virtual void Avatar::simulateAttachments(float deltaTime) { - for (int i = 0; i < _attachmentModels.size(); i++) { + for (int i = 0; i < (int)_attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); auto& model = _attachmentModels.at(i); int jointIndex = getJointIndex(attachment.jointName); @@ -940,7 +944,7 @@ static std::shared_ptr allocateAttachmentModel(bool isSoft, RigPointer ri void Avatar::setAttachmentData(const QVector& attachmentData) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, + QMetaObject::invokeMethod(this, "setAttachmentData", Qt::BlockingQueuedConnection, Q_ARG(const QVector, attachmentData)); return; } @@ -949,14 +953,14 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { AvatarData::setAttachmentData(attachmentData); // if number of attachments has been reduced, remove excess models. - while (_attachmentModels.size() > attachmentData.size()) { + while ((int)_attachmentModels.size() > attachmentData.size()) { auto attachmentModel = _attachmentModels.back(); _attachmentModels.pop_back(); _attachmentsToRemove.push_back(attachmentModel); } for (int i = 0; i < attachmentData.size(); i++) { - if (i == _attachmentModels.size()) { + if (i == (int)_attachmentModels.size()) { // if number of attachments has been increased, we need to allocate a new model _attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig())); } diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 4dda14e194..12c792e631 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -90,7 +90,7 @@ void AvatarActionHold::prepareForPhysicsSimulation() { // _palmRotationFromRigidBody = avatarRotationInverse * palmRotation; }); - activateBody(); + activateBody(true); } std::shared_ptr AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 2ef3956d99..833ed26cc9 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -76,6 +76,10 @@ AvatarManager::AvatarManager(QObject* parent) : packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket"); } +AvatarManager::~AvatarManager() { + _myAvatar->die(); +} + void AvatarManager::init() { _myAvatar->init(); { @@ -165,7 +169,12 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE); if (avatar->getTargetScale() <= MIN_FADE_SCALE) { avatar->removeFromScene(*fadingIterator, scene, pendingChanges); - fadingIterator = _avatarFades.erase(fadingIterator); + // only remove from _avatarFades if we're sure its motionState has been removed from PhysicsEngine + if (_motionStatesToRemoveFromPhysics.empty()) { + fadingIterator = _avatarFades.erase(fadingIterator); + } else { + ++fadingIterator; + } } else { avatar->simulate(deltaTime); ++fadingIterator; @@ -193,20 +202,6 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe return newAvatar; } -// protected -void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) { - auto rawPointer = std::static_pointer_cast(avatar); - AvatarMotionState* motionState = rawPointer->getMotionState(); - if (motionState) { - // clean up physics stuff - motionState->clearObjectBackPointer(); - rawPointer->setMotionState(nullptr); - _avatarMotionStates.remove(motionState); - _motionStatesToAdd.remove(motionState); - _motionStatesToDelete.push_back(motionState); - } -} - // virtual void AvatarManager::removeAvatar(const QUuid& sessionUUID) { QWriteLocker locker(&_hashLock); @@ -220,8 +215,18 @@ void AvatarManager::removeAvatar(const QUuid& sessionUUID) { void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) { AvatarHashMap::handleRemovedAvatar(removedAvatar); - removedAvatar->die(); - removeAvatarMotionState(removedAvatar); + // removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar + // class in this context so we can call methods that don't exist at the base class. + Avatar* avatar = static_cast(removedAvatar.get()); + avatar->die(); + + AvatarMotionState* motionState = avatar->getMotionState(); + if (motionState) { + _motionStatesThatMightUpdate.remove(motionState); + _motionStatesToAddToPhysics.remove(motionState); + _motionStatesToRemoveFromPhysics.push_back(motionState); + } + _avatarFades.push_back(removedAvatar); } @@ -274,22 +279,22 @@ AvatarData* AvatarManager::getAvatar(QUuid avatarID) { } -void AvatarManager::getObjectsToDelete(VectorOfMotionStates& result) { +void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { result.clear(); - result.swap(_motionStatesToDelete); + result.swap(_motionStatesToRemoveFromPhysics); } -void AvatarManager::getObjectsToAdd(VectorOfMotionStates& result) { +void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) { result.clear(); - for (auto motionState : _motionStatesToAdd) { + for (auto motionState : _motionStatesToAddToPhysics) { result.push_back(motionState); } - _motionStatesToAdd.clear(); + _motionStatesToAddToPhysics.clear(); } void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); - for (auto state : _avatarMotionStates) { + for (auto state : _motionStatesThatMightUpdate) { if (state->_dirtyFlags > 0) { result.push_back(state); } @@ -344,8 +349,8 @@ void AvatarManager::addAvatarToSimulation(Avatar* avatar) { // we don't add to the simulation now, we put it on a list to be added later AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); avatar->setMotionState(motionState); - _motionStatesToAdd.insert(motionState); - _avatarMotionStates.insert(motionState); + _motionStatesToAddToPhysics.insert(motionState); + _motionStatesThatMightUpdate.insert(motionState); } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 8494ce0b40..72fcb3f862 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -34,6 +34,8 @@ public: /// Registers the script types associated with the avatar manager. static void registerMetaTypes(QScriptEngine* engine); + virtual ~AvatarManager(); + void init(); MyAvatar* getMyAvatar() { return _myAvatar.get(); } @@ -59,8 +61,8 @@ public: Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); - void getObjectsToDelete(VectorOfMotionStates& motionStates); - void getObjectsToAdd(VectorOfMotionStates& motionStates); + void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); + void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates); void getObjectsToChange(VectorOfMotionStates& motionStates); void handleOutgoingChanges(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); @@ -80,7 +82,6 @@ private: // virtual overrides virtual AvatarSharedPointer newSharedAvatar(); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); - void removeAvatarMotionState(AvatarSharedPointer avatar); virtual void removeAvatar(const QUuid& sessionUUID); virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar); @@ -93,9 +94,9 @@ private: bool _shouldShowReceiveStats = false; - SetOfAvatarMotionStates _avatarMotionStates; - SetOfMotionStates _motionStatesToAdd; - VectorOfMotionStates _motionStatesToDelete; + SetOfAvatarMotionStates _motionStatesThatMightUpdate; + SetOfMotionStates _motionStatesToAddToPhysics; + VectorOfMotionStates _motionStatesToRemoveFromPhysics; }; Q_DECLARE_METATYPE(AvatarManager::LocalLight) diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index acd9a45aab..9ce9594d45 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -25,20 +25,17 @@ AvatarMotionState::AvatarMotionState(Avatar* avatar, btCollisionShape* shape) : } AvatarMotionState::~AvatarMotionState() { + assert(_avatar); _avatar = nullptr; } // virtual uint32_t AvatarMotionState::getIncomingDirtyFlags() { - uint32_t dirtyFlags = 0; - if (_body && _avatar) { - dirtyFlags = _dirtyFlags; - } - return dirtyFlags; + return _body ? _dirtyFlags : 0; } void AvatarMotionState::clearIncomingDirtyFlags() { - if (_body && _avatar) { + if (_body) { _dirtyFlags = 0; } } @@ -50,12 +47,9 @@ MotionType AvatarMotionState::computeObjectMotionType() const { // virtual and protected btCollisionShape* AvatarMotionState::computeNewShape() { - if (_avatar) { - ShapeInfo shapeInfo; - _avatar->computeShapeInfo(shapeInfo); - return getShapeManager()->getShape(shapeInfo); - } - return nullptr; + ShapeInfo shapeInfo; + _avatar->computeShapeInfo(shapeInfo); + return getShapeManager()->getShape(shapeInfo); } // virtual @@ -65,9 +59,6 @@ bool AvatarMotionState::isMoving() const { // virtual void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const { - if (!_avatar) { - return; - } worldTrans.setOrigin(glmToBullet(getObjectPosition())); worldTrans.setRotation(glmToBullet(getObjectRotation())); if (_body) { @@ -76,11 +67,8 @@ void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const { } } -// virtual +// virtual void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) { - if (!_avatar) { - return; - } // HACK: The PhysicsEngine does not actually move OTHER avatars -- instead it slaves their local RigidBody to the transform // as specified by a remote simulation. However, to give the remote simulation time to respond to our own objects we tie // the other avatar's body to its true position with a simple spring. This is a HACK that will have to be improved later. @@ -154,15 +142,8 @@ QUuid AvatarMotionState::getSimulatorID() const { return _avatar->getSessionUUID(); } -// virtual -int16_t AvatarMotionState::computeCollisionGroup() { +// virtual +int16_t AvatarMotionState::computeCollisionGroup() const { return COLLISION_GROUP_OTHER_AVATAR; } -// virtual -void AvatarMotionState::clearObjectBackPointer() { - ObjectMotionState::clearObjectBackPointer(); - _avatar = nullptr; -} - - diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index b5101d2c70..0465ddf50b 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -21,55 +21,65 @@ class Avatar; class AvatarMotionState : public ObjectMotionState { public: AvatarMotionState(Avatar* avatar, btCollisionShape* shape); - ~AvatarMotionState(); - virtual MotionType getMotionType() const { return _motionType; } + virtual MotionType getMotionType() const override { return _motionType; } - virtual uint32_t getIncomingDirtyFlags(); - virtual void clearIncomingDirtyFlags(); + virtual uint32_t getIncomingDirtyFlags() override; + virtual void clearIncomingDirtyFlags() override; - virtual MotionType computeObjectMotionType() const; + virtual MotionType computeObjectMotionType() const override; - virtual bool isMoving() const; + virtual bool isMoving() const override; // this relays incoming position/rotation to the RigidBody - virtual void getWorldTransform(btTransform& worldTrans) const; + virtual void getWorldTransform(btTransform& worldTrans) const override; // this relays outgoing position/rotation to the EntityItem - virtual void setWorldTransform(const btTransform& worldTrans); + virtual void setWorldTransform(const btTransform& worldTrans) override; // These pure virtual methods must be implemented for each MotionState type // and make it possible to implement more complicated methods in this base class. - virtual float getObjectRestitution() const; - virtual float getObjectFriction() const; - virtual float getObjectLinearDamping() const; - virtual float getObjectAngularDamping() const; + // pure virtual overrides from ObjectMotionState + virtual float getObjectRestitution() const override; + virtual float getObjectFriction() const override; + virtual float getObjectLinearDamping() const override; + virtual float getObjectAngularDamping() const override; - virtual glm::vec3 getObjectPosition() const; - virtual glm::quat getObjectRotation() const; - virtual glm::vec3 getObjectLinearVelocity() const; - virtual glm::vec3 getObjectAngularVelocity() const; - virtual glm::vec3 getObjectGravity() const; + virtual glm::vec3 getObjectPosition() const override; + virtual glm::quat getObjectRotation() const override; + virtual glm::vec3 getObjectLinearVelocity() const override; + virtual glm::vec3 getObjectAngularVelocity() const override; + virtual glm::vec3 getObjectGravity() const override; - virtual const QUuid& getObjectID() const; + virtual const QUuid& getObjectID() const override; - virtual QUuid getSimulatorID() const; + virtual QUuid getSimulatorID() const override; void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; } - virtual int16_t computeCollisionGroup(); + virtual int16_t computeCollisionGroup() const override; friend class AvatarManager; + friend class Avatar; protected: - virtual bool isReadyToComputeShape() { return true; } + // the dtor had been made protected to force the compiler to verify that it is only + // ever called by the Avatar class dtor. + ~AvatarMotionState(); + + virtual bool isReadyToComputeShape() const override { return true; } virtual btCollisionShape* computeNewShape(); - virtual void clearObjectBackPointer(); - Avatar* _avatar; + + // The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState + // instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor. + // In other words, it is impossible for the Avatar to be deleted out from under its MotionState. + // In conclusion: weak pointer shennanigans would be pure overhead. + Avatar* _avatar; // do NOT use smartpointer here, no need for weakpointer + uint32_t _dirtyFlags; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 98b09d6c59..a5303b401a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1022,12 +1022,12 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN } void MyAvatar::setAttachmentData(const QVector& attachmentData) { - Avatar::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, + QMetaObject::invokeMethod(this, "setAttachmentData", Qt::BlockingQueuedConnection, Q_ARG(const QVector, attachmentData)); return; } + Avatar::setAttachmentData(attachmentData); _billboardValid = false; } @@ -1165,21 +1165,25 @@ void MyAvatar::setCollisionSoundURL(const QString& url) { } } -void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, - const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { +void MyAvatar::attach(const QString& modelURL, const QString& jointName, + const glm::vec3& translation, const glm::quat& rotation, + float scale, bool isSoft, + bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { - Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); + Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); return; } if (useSaved) { AttachmentData attachment = loadAttachmentData(modelURL, jointName); if (attachment.isValid()) { - Avatar::attach(modelURL, attachment.jointName, attachment.translation, - attachment.rotation, attachment.scale, allowDuplicates, useSaved); + Avatar::attach(modelURL, attachment.jointName, + attachment.translation, attachment.rotation, + attachment.scale, attachment.isSoft, + allowDuplicates, useSaved); return; } } - Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); + Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); } void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 44823c9913..8c4bbcbba0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -295,7 +295,8 @@ private: void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); virtual void attach(const QString& modelURL, const QString& jointName = QString(), - const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, + const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), + float scale = 1.0f, bool isSoft = false, bool allowDuplicates = false, bool useSaved = true) override; //void beginFollowingHMD(); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index b11e4c7a95..9259478d46 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -158,7 +158,7 @@ void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) { } void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { - if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get(); diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 7b5f80ee80..fa6eaf072d 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -62,7 +62,7 @@ void AvatarInputs::update() { if (!Menu::getInstance()) { return; } - AI_UPDATE(mirrorVisible, Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && !qApp->isHMDMode() + AI_UPDATE(mirrorVisible, Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror) && !qApp->isHMDMode() && !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)); AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)); AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); @@ -129,7 +129,7 @@ void AvatarInputs::toggleZoom() { } void AvatarInputs::closeMirror() { - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - Menu::getInstance()->triggerOption(MenuOption::Mirror); + if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { + Menu::getInstance()->triggerOption(MenuOption::MiniMirror); } } diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 1acbf3a595..155d41c575 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -176,20 +176,6 @@ void DialogsManager::showScriptEditor() { _scriptEditor->raise(); } -void DialogsManager::showIRCLink() { - if (!_ircInfoBox) { - _ircInfoBox = new QMessageBox(QMessageBox::NoIcon, - "High Fidelity IRC", - "High Fidelity has an IRC channel on irc.freenode.net at #highfidelity.

Web chat is available here.", - QMessageBox::Ok); - _ircInfoBox->setTextFormat(Qt::RichText); - _ircInfoBox->setAttribute(Qt::WA_DeleteOnClose); - _ircInfoBox->show(); - } - - _ircInfoBox->raise(); -} - void DialogsManager::showDomainConnectionDialog() { // if the dialog already exists we delete it so the connection data is refreshed if (_domainConnectionDialog) { diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index f6ad1d6f98..e0b841fa12 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -57,7 +57,6 @@ public slots: void lodTools(); void hmdTools(bool showTools); void showScriptEditor(); - void showIRCLink(); void showDomainConnectionDialog(); // Application Update diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 0015a064fb..af8e1c1cda 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1085,12 +1085,15 @@ void AvatarData::setAttachmentData(const QVector& attachmentData _attachmentData = attachmentData; } -void AvatarData::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, - const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { +void AvatarData::attach(const QString& modelURL, const QString& jointName, + const glm::vec3& translation, const glm::quat& rotation, + float scale, bool isSoft, + bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "attach", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName), - Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation), - Q_ARG(float, scale), Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved)); + Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation), + Q_ARG(float, scale), Q_ARG(bool, isSoft), + Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved)); return; } QVector attachmentData = getAttachmentData(); @@ -1107,6 +1110,7 @@ void AvatarData::attach(const QString& modelURL, const QString& jointName, const data.translation = translation; data.rotation = rotation; data.scale = scale; + data.isSoft = isSoft; attachmentData.append(data); setAttachmentData(attachmentData); } @@ -1334,7 +1338,7 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft; } -void AttachmentDataObject::setModelURL(const QString& modelURL) const { +void AttachmentDataObject::setModelURL(const QString& modelURL) { AttachmentData data = qscriptvalue_cast(thisObject()); data.modelURL = modelURL; thisObject() = engine()->toScriptValue(data); @@ -1344,7 +1348,7 @@ QString AttachmentDataObject::getModelURL() const { return qscriptvalue_cast(thisObject()).modelURL.toString(); } -void AttachmentDataObject::setJointName(const QString& jointName) const { +void AttachmentDataObject::setJointName(const QString& jointName) { AttachmentData data = qscriptvalue_cast(thisObject()); data.jointName = jointName; thisObject() = engine()->toScriptValue(data); @@ -1354,7 +1358,7 @@ QString AttachmentDataObject::getJointName() const { return qscriptvalue_cast(thisObject()).jointName; } -void AttachmentDataObject::setTranslation(const glm::vec3& translation) const { +void AttachmentDataObject::setTranslation(const glm::vec3& translation) { AttachmentData data = qscriptvalue_cast(thisObject()); data.translation = translation; thisObject() = engine()->toScriptValue(data); @@ -1364,7 +1368,7 @@ glm::vec3 AttachmentDataObject::getTranslation() const { return qscriptvalue_cast(thisObject()).translation; } -void AttachmentDataObject::setRotation(const glm::quat& rotation) const { +void AttachmentDataObject::setRotation(const glm::quat& rotation) { AttachmentData data = qscriptvalue_cast(thisObject()); data.rotation = rotation; thisObject() = engine()->toScriptValue(data); @@ -1374,7 +1378,7 @@ glm::quat AttachmentDataObject::getRotation() const { return qscriptvalue_cast(thisObject()).rotation; } -void AttachmentDataObject::setScale(float scale) const { +void AttachmentDataObject::setScale(float scale) { AttachmentData data = qscriptvalue_cast(thisObject()); data.scale = scale; thisObject() = engine()->toScriptValue(data); @@ -1384,6 +1388,16 @@ float AttachmentDataObject::getScale() const { return qscriptvalue_cast(thisObject()).scale; } +void AttachmentDataObject::setIsSoft(bool isSoft) { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.isSoft = isSoft; + thisObject() = engine()->toScriptValue(data); +} + +bool AttachmentDataObject::getIsSoft() const { + return qscriptvalue_cast(thisObject()).isSoft; +} + void registerAvatarTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 83195364bf..c443af7b70 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -304,8 +304,9 @@ public: Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData); Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(), - const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, - bool allowDuplicates = false, bool useSaved = true); + const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), + float scale = 1.0f, bool isSoft = false, + bool allowDuplicates = false, bool useSaved = true); Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); @@ -344,9 +345,6 @@ public: glm::vec3 getClientGlobalPosition() { return _globalPosition; } - void die() { _isDead = true; } - bool isDead() const { return _isDead; } - public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -423,8 +421,6 @@ protected: // updates about one avatar to another. glm::vec3 _globalPosition; - bool _isDead { false }; - private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; @@ -468,23 +464,27 @@ class AttachmentDataObject : public QObject, protected QScriptable { Q_PROPERTY(glm::vec3 translation READ getTranslation WRITE setTranslation) Q_PROPERTY(glm::quat rotation READ getRotation WRITE setRotation) Q_PROPERTY(float scale READ getScale WRITE setScale) + Q_PROPERTY(bool isSoft READ getIsSoft WRITE setIsSoft) public: - Q_INVOKABLE void setModelURL(const QString& modelURL) const; + Q_INVOKABLE void setModelURL(const QString& modelURL); Q_INVOKABLE QString getModelURL() const; - Q_INVOKABLE void setJointName(const QString& jointName) const; + Q_INVOKABLE void setJointName(const QString& jointName); Q_INVOKABLE QString getJointName() const; - Q_INVOKABLE void setTranslation(const glm::vec3& translation) const; + Q_INVOKABLE void setTranslation(const glm::vec3& translation); Q_INVOKABLE glm::vec3 getTranslation() const; - Q_INVOKABLE void setRotation(const glm::quat& rotation) const; + Q_INVOKABLE void setRotation(const glm::quat& rotation); Q_INVOKABLE glm::quat getRotation() const; - Q_INVOKABLE void setScale(float scale) const; + Q_INVOKABLE void setScale(float scale); Q_INVOKABLE float getScale() const; + + Q_INVOKABLE void setIsSoft(bool scale); + Q_INVOKABLE bool getIsSoft() const; }; void registerAvatarTypes(QScriptEngine* engine); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 7b3bbc4c02..fd28bc043d 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -122,13 +122,19 @@ void RenderablePolyLineEntityItem::updateVertices() { glm::vec3 v1, v2, tangent, binormal, point; int finalIndex = minVectorSize - 1; + + // Guard against an empty polyline + if (finalIndex < 0) { + return; + } + for (int i = 0; i < finalIndex; i++) { float width = _strokeWidths.at(i); point = _points.at(i); tangent = _points.at(i); - tangent = _points.at(i + 1) - point; + tangent = _points.at(i + 1) - point; glm::vec3 normal = _normals.at(i); binormal = glm::normalize(glm::cross(tangent, normal)) * width; @@ -141,11 +147,6 @@ void RenderablePolyLineEntityItem::updateVertices() { _vertices << v1 << v2; } - // Guard against an empty polyline - if (finalIndex < 0) { - return; - } - // For last point we can assume binormals are the same since it represents the last two vertices of quad point = _points.at(finalIndex); v1 = point + binormal; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 0411945ede..efd9b4afda 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -77,7 +77,7 @@ public: glm::mat4 localToVoxelMatrix() const; virtual ShapeType getShapeType() const; - virtual bool shouldBePhysical() const { return true; } + virtual bool shouldBePhysical() const { return !isDead(); } virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index 351feb7e54..6196346b9a 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -53,7 +53,7 @@ public: } virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } - virtual bool shouldBePhysical() const { return true; } + virtual bool shouldBePhysical() const { return !isDead(); } virtual void debugDump() const; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 80021d3d90..83f2ad164e 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -56,6 +56,7 @@ namespace render { #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) #define debugTreeVector(V) V << "[" << V << " in meters ]" + /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. @@ -301,7 +302,7 @@ public: virtual bool contains(const glm::vec3& point) const; - virtual bool isReadyToComputeShape() { return true; } + virtual bool isReadyToComputeShape() { return !isDead(); } virtual void computeShapeInfo(ShapeInfo& info); virtual float getVolumeEstimate() const { return getDimensions().x * getDimensions().y * getDimensions().z; } @@ -336,6 +337,8 @@ public: bool isMoving() const; + bool isSimulated() const { return _simulated; } + void* getPhysicsInfo() const { return _physicsInfo; } void setPhysicsInfo(void* data) { _physicsInfo = data; } @@ -390,6 +393,8 @@ public: protected: + void setSimulated(bool simulated) { _simulated = simulated; } + const QByteArray getActionDataInternal() const; void setActionDataInternal(QByteArray actionData); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index ad354465d0..ab29cfb2a4 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -38,15 +38,35 @@ void EntitySimulation::updateEntities() { sortEntitiesThatMoved(); } -void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) { +void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { QMutexLocker lock(&_mutex); for (auto entity : _entitiesToDelete) { - // this entity is still in its tree, so we insert into the external list + // push this entity onto the external list entitiesToDelete.push_back(entity); } _entitiesToDelete.clear(); } +void EntitySimulation::removeEntityInternal(EntityItemPointer entity) { + // remove from all internal lists except _entitiesToDelete + _mortalEntities.remove(entity); + _entitiesToUpdate.remove(entity); + _entitiesToSort.remove(entity); + _simpleKinematicEntities.remove(entity); + _allEntities.remove(entity); + entity->setSimulated(false); +} + +void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { + assert(entity); + assert(entity->isDead()); + if (entity->isSimulated()) { + entity->clearActions(this); + removeEntityInternal(entity); + _entitiesToDelete.insert(entity); + } +} + void EntitySimulation::addEntityInternal(EntityItemPointer entity) { if (entity->isMoving() && !entity->getPhysicsInfo()) { _simpleKinematicEntities.insert(entity); @@ -71,15 +91,9 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { EntityItemPointer entity = *itemItr; quint64 expiry = entity->getExpiry(); if (expiry < now) { - _entitiesToDelete.insert(entity); itemItr = _mortalEntities.erase(itemItr); - _entitiesToUpdate.remove(entity); - _entitiesToSort.remove(entity); - _simpleKinematicEntities.remove(entity); - removeEntityInternal(entity); - - _allEntities.remove(entity); - entity->_simulated = false; + entity->die(); + prepareEntityForDelete(entity); } else { if (expiry < _nextExpiry) { // remeber the smallest _nextExpiry so we know when to start the next search @@ -97,7 +111,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); while (itemItr != _entitiesToUpdate.end()) { EntityItemPointer entity = *itemItr; - // TODO: catch transition from needing update to not as a "change" + // TODO: catch transition from needing update to not as a "change" // so we don't have to scan for it here. if (!entity->needsToCallUpdate()) { itemItr = _entitiesToUpdate.erase(itemItr); @@ -123,16 +137,9 @@ void EntitySimulation::sortEntitiesThatMoved() { AACube newCube = entity->getQueryAACube(success); if (success && !domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; - _entitiesToDelete.insert(entity); - _mortalEntities.remove(entity); - _entitiesToUpdate.remove(entity); - _simpleKinematicEntities.remove(entity); - removeEntityInternal(entity); - - _allEntities.remove(entity); - entity->_simulated = false; - itemItr = _entitiesToSort.erase(itemItr); + entity->die(); + prepareEntityForDelete(entity); } else { moveOperator.addEntityToMoveList(entity, newCube); ++itemItr; @@ -163,39 +170,25 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { addEntityInternal(entity); _allEntities.insert(entity); - entity->_simulated = true; + entity->setSimulated(true); - // DirtyFlags are used to signal changes to entities that have already been added, + // DirtyFlags are used to signal changes to entities that have already been added, // so we can clear them for this entity which has just been added. entity->clearDirtyFlags(); } -void EntitySimulation::removeEntity(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - assert(entity); - _entitiesToUpdate.remove(entity); - _mortalEntities.remove(entity); - _entitiesToSort.remove(entity); - _simpleKinematicEntities.remove(entity); - _entitiesToDelete.remove(entity); - removeEntityInternal(entity); - - _allEntities.remove(entity); - entity->_simulated = false; -} - void EntitySimulation::changeEntity(EntityItemPointer entity) { QMutexLocker lock(&_mutex); assert(entity); - if (!entity->_simulated) { + if (!entity->isSimulated()) { // This entity was either never added to the simulation or has been removed - // (probably for pending delete), so we don't want to keep a pointer to it + // (probably for pending delete), so we don't want to keep a pointer to it // on any internal lists. return; } // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes - // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence + // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); @@ -205,13 +198,8 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { AACube newCube = entity->getQueryAACube(success); if (success && !domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; - _entitiesToDelete.insert(entity); - _mortalEntities.remove(entity); - _entitiesToUpdate.remove(entity); - _entitiesToSort.remove(entity); - _simpleKinematicEntities.remove(entity); - removeEntityInternal(entity); - entity->_simulated = false; + entity->die(); + prepareEntityForDelete(entity); wasRemoved = true; } } @@ -244,14 +232,15 @@ void EntitySimulation::clearEntities() { _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); - _entitiesToDelete.clear(); clearEntitiesInternal(); - for (auto entityItr : _allEntities) { - entityItr->_simulated = false; + for (auto entity : _allEntities) { + entity->setSimulated(false); + entity->die(); } _allEntities.clear(); + _entitiesToDelete.clear(); } void EntitySimulation::moveSimpleKinematics(const quint64& now) { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 4519effbd3..442ff4a74b 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -63,25 +63,20 @@ public: /// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked void addEntity(EntityItemPointer entity); - /// \param entity pointer to EntityItem to be removed - /// \brief the actual removal may happen later when appropriate data structures are locked - /// \sideeffect nulls relevant backpointers in entity - void removeEntity(EntityItemPointer entity); - - /// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation + /// \param entity pointer to EntityItem that may have changed in a way that would affect its simulation /// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself) void changeEntity(EntityItemPointer entity); void clearEntities(); void moveSimpleKinematics(const quint64& now); -protected: // these only called by the EntityTree? - -public: EntityTreePointer getEntityTree() { return _entityTree; } - void getEntitiesToDelete(VectorOfEntities& entitiesToDelete); + virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete); + + /// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others. + virtual void prepareEntityForDelete(EntityItemPointer entity); signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -106,6 +101,9 @@ protected: QList _actionsToAdd; QSet _actionsToRemove; +protected: + SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) + private: void moveSimpleKinematics(); @@ -120,7 +118,6 @@ private: SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() - SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ca7b892523..9861252d63 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -442,6 +442,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) const RemovedEntities& entities = theOperator.getEntities(); foreach(const EntityToDeleteDetails& details, entities) { EntityItemPointer theEntity = details.entity; + theEntity->die(); if (getIsServer()) { // set up the deleted entities ID @@ -453,8 +454,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) } if (_simulation) { - theEntity->clearActions(_simulation); - _simulation->removeEntity(theEntity); + _simulation->prepareEntityForDelete(theEntity); } } } @@ -1006,7 +1006,7 @@ void EntityTree::update() { withWriteLock([&] { _simulation->updateEntities(); VectorOfEntities pendingDeletes; - _simulation->getEntitiesToDelete(pendingDeletes); + _simulation->takeEntitiesToDelete(pendingDeletes); if (pendingDeletes.size() > 0) { // translate into list of ID's diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 709cd67ef5..bef4406f71 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -377,7 +377,7 @@ void ModelEntityItem::setAnimationFPS(float value) { // virtual bool ModelEntityItem::shouldBePhysical() const { - return getShapeType() != SHAPE_TYPE_NONE; + return !isDead() && getShapeType() != SHAPE_TYPE_NONE; } void ModelEntityItem::resizeJointArrays(int newSize) { diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 81d9445f92..3b2523bc92 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -53,6 +53,7 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { + EntitySimulation::removeEntityInternal(entity); _entitiesWithSimulator.remove(entity); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index fff0659067..83c51525a8 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -22,11 +22,11 @@ public: virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); } protected: - virtual void updateEntitiesInternal(const quint64& now); - virtual void addEntityInternal(EntityItemPointer entity); - virtual void removeEntityInternal(EntityItemPointer entity); - virtual void changeEntityInternal(EntityItemPointer entity); - virtual void clearEntitiesInternal(); + virtual void updateEntitiesInternal(const quint64& now) override; + virtual void addEntityInternal(EntityItemPointer entity) override; + virtual void removeEntityInternal(EntityItemPointer entity) override; + virtual void changeEntityInternal(EntityItemPointer entity) override; + virtual void clearEntitiesInternal() override; SetOfEntities _entitiesWithSimulator; }; diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index 941d5a167c..fda5eab009 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -52,7 +52,7 @@ public: } virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; } - virtual bool shouldBePhysical() const { return true; } + virtual bool shouldBePhysical() const { return !isDead(); } virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 19206f8acc..bf323248c0 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -57,7 +57,7 @@ public: static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; } static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } - virtual bool isReadyToComputeShape() { return true; } + virtual bool isReadyToComputeShape() { return false; } void updateShapeType(ShapeType type) { _shapeType = type; } virtual ShapeType getShapeType() const; diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index f89f1f5b72..d9e8579d58 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -254,7 +254,7 @@ private: _quit = true; } - static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 5; + static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 2; void stop() { if (_thread.isRunning()) { qDebug() << "Stopping QML render thread " << _thread.currentThreadId(); diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 2f5ab7a015..303755c8f3 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -20,10 +20,16 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : Assignment(message), - _isFinished(false) - + _isFinished(false), + _domainServerTimer(this), + _statsTimer(this) { + static const int STATS_TIMEOUT_MS = 1000; + _statsTimer.setInterval(STATS_TIMEOUT_MS); + connect(&_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); + connect(&_domainServerTimer, &QTimer::timeout, this, &ThreadedAssignment::checkInWithDomainServerOrExit); + _domainServerTimer.setInterval(DOMAIN_SERVER_CHECK_IN_MSECS); } void ThreadedAssignment::setFinished(bool isFinished) { @@ -47,16 +53,9 @@ void ThreadedAssignment::setFinished(bool isFinished) { // send a disconnect packet to the domain nodeList->getDomainHandler().disconnect(); - if (_domainServerTimer) { - // stop the domain-server check in timer by calling deleteLater so it gets cleaned up on NL thread - _domainServerTimer->deleteLater(); - _domainServerTimer = nullptr; - } - - if (_statsTimer) { - _statsTimer->deleteLater(); - _statsTimer = nullptr; - } + // stop our owned timers + _domainServerTimer.stop(); + _statsTimer.stop(); // call our virtual aboutToFinish method - this gives the ThreadedAssignment subclass a chance to cleanup aboutToFinish(); @@ -66,30 +65,22 @@ void ThreadedAssignment::setFinished(bool isFinished) { } } -void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats) { +void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType) { // change the logging target name while the assignment is running LogHandler::getInstance().setTargetName(targetName); auto nodeList = DependencyManager::get(); nodeList->setOwnerType(nodeType); - _domainServerTimer = new QTimer; - connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - - // send a domain-server check in immediately + // send a domain-server check in immediately and start the timer to fire them every DOMAIN_SERVER_CHECK_IN_MSECS checkInWithDomainServerOrExit(); - - // move the domain server time to the NL so check-ins fire from there - _domainServerTimer->moveToThread(nodeList->thread()); + _domainServerTimer.start(); - if (shouldSendStats) { - // start sending stats packet once we connect to the domain - connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, this, &ThreadedAssignment::startSendingStats); - - // stop sending stats if we disconnect - connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, this, &ThreadedAssignment::stopSendingStats); - } + // start sending stats packet once we connect to the domain + connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), &_statsTimer, SLOT(start())); + + // stop sending stats if we disconnect + connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, &_statsTimer, &QTimer::stop); } void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject &statsObject) { @@ -111,28 +102,12 @@ void ThreadedAssignment::sendStatsPacket() { addPacketStatsAndSendStatsPacket(statsObject); } -void ThreadedAssignment::startSendingStats() { - // send the stats packet every 1s - if (!_statsTimer) { - _statsTimer = new QTimer; - connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); - } - - _statsTimer->start(1000); -} - -void ThreadedAssignment::stopSendingStats() { - if (_statsTimer) { - // stop sending stats, we just disconnected from domain - _statsTimer->stop(); - } -} - void ThreadedAssignment::checkInWithDomainServerOrExit() { if (DependencyManager::get()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); } else { - DependencyManager::get()->sendDomainServerCheckIn(); + auto nodeList = DependencyManager::get(); + QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); } } diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 87d503d2bf..42d4903c2f 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -38,17 +38,15 @@ signals: void finished(); protected: - void commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats = true); + void commonInit(const QString& targetName, NodeType_t nodeType); bool _isFinished; - QTimer* _domainServerTimer = nullptr; - QTimer* _statsTimer = nullptr; + QTimer _domainServerTimer; + QTimer _statsTimer; protected slots: void domainSettingsRequestFailed(); private slots: - void startSendingStats(); - void stopSendingStats(); void checkInWithDomainServerOrExit(); }; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index cd07b4112f..c1338b772c 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -34,7 +34,7 @@ const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { - EntityTreeElementPointer element = _entity ? _entity->getElement() : nullptr; + EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; if (!tree) { return true; @@ -50,7 +50,8 @@ bool entityTreeIsLocked() { EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(shape), - _entity(entity), + _entityPtr(entity), + _entity(entity.get()), _sentInactive(true), _lastStep(0), _serverPosition(0.0f), @@ -69,14 +70,14 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _loopsWithoutOwner(0) { _type = MOTIONSTATE_TYPE_ENTITY; - assert(_entity != nullptr); + assert(_entity); assert(entityTreeIsLocked()); setMass(_entity->computeMass()); } EntityMotionState::~EntityMotionState() { - // be sure to clear _entity before calling the destructor - assert(!_entity); + assert(_entity); + _entity = nullptr; } void EntityMotionState::updateServerPhysicsVariables(const QUuid& sessionID) { @@ -138,11 +139,6 @@ bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } -void EntityMotionState::clearObjectBackPointer() { - ObjectMotionState::clearObjectBackPointer(); - _entity = nullptr; -} - MotionType EntityMotionState::computeObjectMotionType() const { if (!_entity) { return MOTION_TYPE_STATIC; @@ -221,22 +217,16 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { // virtual and protected -bool EntityMotionState::isReadyToComputeShape() { - if (_entity) { - return _entity->isReadyToComputeShape(); - } - return false; +bool EntityMotionState::isReadyToComputeShape() const { + return _entity->isReadyToComputeShape(); } // virtual and protected btCollisionShape* EntityMotionState::computeNewShape() { - if (_entity) { - ShapeInfo shapeInfo; - assert(entityTreeIsLocked()); - _entity->computeShapeInfo(shapeInfo); - return getShapeManager()->getShape(shapeInfo); - } - return nullptr; + ShapeInfo shapeInfo; + assert(entityTreeIsLocked()); + _entity->computeShapeInfo(shapeInfo); + return getShapeManager()->getShape(shapeInfo); } bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { @@ -553,26 +543,17 @@ void EntityMotionState::clearIncomingDirtyFlags() { // virtual quint8 EntityMotionState::getSimulationPriority() const { - if (_entity) { - return _entity->getSimulationPriority(); - } - return NO_PRORITY; + return _entity->getSimulationPriority(); } // virtual QUuid EntityMotionState::getSimulatorID() const { - if (_entity) { - assert(entityTreeIsLocked()); - return _entity->getSimulatorID(); - } - return QUuid(); + assert(entityTreeIsLocked()); + return _entity->getSimulatorID(); } -// virtual void EntityMotionState::bump(quint8 priority) { - if (_entity) { - setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); - } + setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -623,19 +604,13 @@ void EntityMotionState::setMotionType(MotionType motionType) { // virtual -QString EntityMotionState::getName() { - if (_entity) { - assert(entityTreeIsLocked()); - return _entity->getName(); - } - return ""; +QString EntityMotionState::getName() const { + assert(entityTreeIsLocked()); + return _entity->getName(); } // virtual -int16_t EntityMotionState::computeCollisionGroup() { - if (!_entity) { - return COLLISION_GROUP_STATIC; - } +int16_t EntityMotionState::computeCollisionGroup() const { if (_entity->getIgnoreForCollisions()) { return COLLISION_GROUP_COLLISIONLESS; } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index c666f87221..53e7982ae1 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -12,11 +12,11 @@ #ifndef hifi_EntityMotionState_h #define hifi_EntityMotionState_h +#include #include #include "ObjectMotionState.h" -class EntityItem; // From the MotionState's perspective: // Inside = physics simulation @@ -38,10 +38,10 @@ public: virtual bool isMoving() const; // this relays incoming position/rotation to the RigidBody - virtual void getWorldTransform(btTransform& worldTrans) const; + virtual void getWorldTransform(btTransform& worldTrans) const override; // this relays outgoing position/rotation to the EntityItem - virtual void setWorldTransform(const btTransform& worldTrans); + virtual void setWorldTransform(const btTransform& worldTrans) override; bool isCandidateForOwnership(const QUuid& sessionID) const; bool remoteSimulationOutOfSync(uint32_t simulationStep); @@ -55,32 +55,32 @@ public: void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; } - virtual float getObjectRestitution() const { return _entity->getRestitution(); } - virtual float getObjectFriction() const { return _entity->getFriction(); } - virtual float getObjectLinearDamping() const { return _entity->getDamping(); } - virtual float getObjectAngularDamping() const { return _entity->getAngularDamping(); } + virtual float getObjectRestitution() const override { return _entity->getRestitution(); } + virtual float getObjectFriction() const override { return _entity->getFriction(); } + virtual float getObjectLinearDamping() const override { return _entity->getDamping(); } + virtual float getObjectAngularDamping() const override { return _entity->getAngularDamping(); } - virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } - virtual glm::quat getObjectRotation() const { return _entity->getRotation(); } - virtual glm::vec3 getObjectLinearVelocity() const { return _entity->getVelocity(); } - virtual glm::vec3 getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } - virtual glm::vec3 getObjectGravity() const { return _entity->getGravity(); } - virtual glm::vec3 getObjectLinearVelocityChange() const; + virtual glm::vec3 getObjectPosition() const override { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } + virtual glm::quat getObjectRotation() const override { return _entity->getRotation(); } + virtual glm::vec3 getObjectLinearVelocity() const override { return _entity->getVelocity(); } + virtual glm::vec3 getObjectAngularVelocity() const override { return _entity->getAngularVelocity(); } + virtual glm::vec3 getObjectGravity() const override { return _entity->getGravity(); } + virtual glm::vec3 getObjectLinearVelocityChange() const override; - virtual const QUuid& getObjectID() const { return _entity->getID(); } + virtual const QUuid& getObjectID() const override { return _entity->getID(); } - virtual quint8 getSimulationPriority() const; - virtual QUuid getSimulatorID() const; - virtual void bump(quint8 priority); + virtual quint8 getSimulationPriority() const override; + virtual QUuid getSimulatorID() const override; + virtual void bump(quint8 priority) override; - EntityItemPointer getEntity() const { return _entity; } + EntityItemPointer getEntity() const { return _entityPtr.lock(); } void resetMeasuredBodyAcceleration(); void measureBodyAcceleration(); - virtual QString getName(); + virtual QString getName() const override; - virtual int16_t computeCollisionGroup(); + virtual int16_t computeCollisionGroup() const override; // eternal logic can suggest a simuator priority bid for the next outgoing update void setOutgoingPriority(quint8 priority); @@ -92,12 +92,19 @@ protected: bool entityTreeIsLocked() const; #endif - virtual bool isReadyToComputeShape(); + virtual bool isReadyToComputeShape() const override; virtual btCollisionShape* computeNewShape(); - virtual void clearObjectBackPointer(); virtual void setMotionType(MotionType motionType); - EntityItemPointer _entity; + // In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be + // properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that + // state of affairs we can't keep a real EntityItemPointer as data member (it would produce a + // recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while + // still granting us the capability to generate EntityItemPointers as necessary (for external data + // structures that use the MotionState to get to the EntityItem). + EntityItemWeakPointer _entityPtr; + // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. + EntityItem* _entity; bool _sentInactive; // true if body was inactive when we sent last update diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index e8e1188dc9..f90286845a 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -237,10 +237,13 @@ void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) { rigidBody->activate(); } -void ObjectAction::activateBody() { +void ObjectAction::activateBody(bool forceActivation) { auto rigidBody = getRigidBody(); if (rigidBody) { - rigidBody->activate(); + rigidBody->activate(forceActivation); + assert(rigidBody->isActive()); + } else { + qDebug() << "ObjectAction::activateBody -- no rigid body" << (void*)rigidBody; } } diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index 4ca13f2fbf..efab75b802 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -62,7 +62,7 @@ protected: virtual void setLinearVelocity(glm::vec3 linearVelocity) override; virtual glm::vec3 getAngularVelocity() override; virtual void setAngularVelocity(glm::vec3 angularVelocity) override; - virtual void activateBody(); + virtual void activateBody(bool forceActivation = false); virtual void forceBodyNonStatic(); EntityItemWeakPointer _ownerEntity; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 7389d18143..c434f67ad2 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -72,7 +72,8 @@ ObjectMotionState::ObjectMotionState(btCollisionShape* shape) : ObjectMotionState::~ObjectMotionState() { assert(!_body); - assert(!_shape); + releaseShape(); + _type = MOTIONSTATE_TYPE_INVALID; } void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 8f97b25dcc..e10d58e3ed 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -134,9 +134,9 @@ public: virtual QUuid getSimulatorID() const = 0; virtual void bump(quint8 priority) {} - virtual QString getName() { return ""; } + virtual QString getName() const { return ""; } - virtual int16_t computeCollisionGroup() = 0; + virtual int16_t computeCollisionGroup() const = 0; bool isActive() const { return _body ? _body->isActive() : false; } @@ -148,14 +148,11 @@ public: friend class PhysicsEngine; protected: - virtual bool isReadyToComputeShape() = 0; + virtual bool isReadyToComputeShape() const = 0; virtual btCollisionShape* computeNewShape() = 0; void setMotionType(MotionType motionType); void updateCCDConfiguration(); - // clearObjectBackPointer() overrrides should call the base method, then actually clear the object back pointer. - virtual void clearObjectBackPointer() { _type = MOTIONSTATE_TYPE_INVALID; } - void setRigidBody(btRigidBody* body); MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 5a12627abd..9ef27aaf53 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -44,10 +44,11 @@ void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { assert(entity); + assert(!entity->isDead()); if (entity->shouldBePhysical()) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (!motionState) { - _pendingAdds.insert(entity); + _entitiesToAddToPhysics.insert(entity); } } else if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); @@ -55,14 +56,33 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { } void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { + EntitySimulation::removeEntityInternal(entity); + _entitiesToAddToPhysics.remove(entity); + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { - motionState->clearObjectBackPointer(); - entity->setPhysicsInfo(nullptr); - _pendingRemoves.insert(motionState); _outgoingChanges.remove(motionState); + _entitiesToRemoveFromPhysics.insert(entity); + } else { + _entitiesToDelete.insert(entity); } - _pendingAdds.remove(entity); +} + +void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { + QMutexLocker lock(&_mutex); + for (auto entity : _entitiesToDelete) { + // this entity is still in its tree, so we insert into the external list + entitiesToDelete.push_back(entity); + + // Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // rather than do it here + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + delete motionState; + entity->setPhysicsInfo(nullptr); + } + } + _entitiesToDelete.clear(); } void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { @@ -74,8 +94,8 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { // the entity should be removed from the physical simulation _pendingChanges.remove(motionState); _physicalObjects.remove(motionState); - _pendingRemoves.insert(motionState); _outgoingChanges.remove(motionState); + _entitiesToRemoveFromPhysics.insert(entity); if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); } @@ -85,7 +105,7 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { } else if (entity->shouldBePhysical()) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // Perhaps it's shape has changed and it can now be added? - _pendingAdds.insert(entity); + _entitiesToAddToPhysics.insert(entity); _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic } else if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); @@ -102,55 +122,70 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // first disconnect each MotionStates from its Entity for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast(&(*stateItr)); - EntityItemPointer entity = motionState->getEntity(); - if (entity) { - entity->setPhysicsInfo(nullptr); - } - motionState->clearObjectBackPointer(); + _entitiesToDelete.insert(motionState->getEntity()); } - // then delete the objects (aka MotionStates) - _physicsEngine->deleteObjects(_physicalObjects); + // then remove the objects (aka MotionStates) from physics + _physicsEngine->removeObjects(_physicalObjects); - // finally clear all lists (which now have only dangling pointers) + // delete the MotionStates + // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete + // its own PhysicsInfo rather than do it here + for (auto entity : _entitiesToDelete) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + delete motionState; + entity->setPhysicsInfo(nullptr); + } + } + + // finally clear all lists maintained by this class _physicalObjects.clear(); - _pendingRemoves.clear(); - _pendingAdds.clear(); + _entitiesToRemoveFromPhysics.clear(); + _entitiesToAddToPhysics.clear(); _pendingChanges.clear(); _outgoingChanges.clear(); } + +// virtual +void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { + assert(entity); + assert(entity->isDead()); + entity->clearActions(this); + removeEntityInternal(entity); +} // end EntitySimulation overrides - -void PhysicalEntitySimulation::getObjectsToDelete(VectorOfMotionStates& result) { +void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - for (auto stateItr : _pendingRemoves) { - EntityMotionState* motionState = &(*stateItr); - _pendingChanges.remove(motionState); - _physicalObjects.remove(motionState); - - EntityItemPointer entity = motionState->getEntity(); - if (entity) { - _pendingAdds.remove(entity); - entity->setPhysicsInfo(nullptr); - motionState->clearObjectBackPointer(); + for (auto entity: _entitiesToRemoveFromPhysics) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + _pendingChanges.remove(motionState); + _physicalObjects.remove(motionState); + result.push_back(motionState); + } + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead()) { + _entitiesToDelete.insert(entity); } - result.push_back(motionState); } - _pendingRemoves.clear(); + _entitiesToRemoveFromPhysics.clear(); } -void PhysicalEntitySimulation::getObjectsToAdd(VectorOfMotionStates& result) { +void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - SetOfEntities::iterator entityItr = _pendingAdds.begin(); - while (entityItr != _pendingAdds.end()) { - EntityItemPointer entity = *entityItr; + SetOfEntities::iterator entityItr = _entitiesToAddToPhysics.begin(); + while (entityItr != _entitiesToAddToPhysics.end()) { + EntityItemPointer entity = (*entityItr); assert(!entity->getPhysicsInfo()); - if (!entity->shouldBePhysical()) { - // this entity should no longer be on the internal _pendingAdds - entityItr = _pendingAdds.erase(entityItr); + if (entity->isDead()) { + prepareEntityForDelete(entity); + } else if (!entity->shouldBePhysical()) { + // this entity should no longer be on the internal _entitiesToAddToPhysics + entityItr = _entitiesToAddToPhysics.erase(entityItr); if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); } @@ -163,7 +198,7 @@ void PhysicalEntitySimulation::getObjectsToAdd(VectorOfMotionStates& result) { entity->setPhysicsInfo(static_cast(motionState)); _physicalObjects.insert(motionState); result.push_back(motionState); - entityItr = _pendingAdds.erase(entityItr); + entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { //qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName(); ++entityItr; @@ -199,12 +234,11 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& if (state && state->getType() == MOTIONSTATE_TYPE_ENTITY) { EntityMotionState* entityState = static_cast(state); EntityItemPointer entity = entityState->getEntity(); - if (entity) { - if (entityState->isCandidateForOwnership(sessionID)) { - _outgoingChanges.insert(entityState); - } - _entitiesToSort.insert(entityState->getEntity()); + assert(entity.get()); + if (entityState->isCandidateForOwnership(sessionID)) { + _outgoingChanges.insert(entityState); } + _entitiesToSort.insert(entity); } } diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index c4f96e023a..bc80d50d0a 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -35,6 +35,8 @@ public: virtual void addAction(EntityActionPointer action) override; virtual void applyActionChanges() override; + virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override; + protected: // only called by EntitySimulation // overrides for EntitySimulation virtual void updateEntitiesInternal(const quint64& now) override; @@ -44,8 +46,10 @@ protected: // only called by EntitySimulation virtual void clearEntitiesInternal() override; public: - void getObjectsToDelete(VectorOfMotionStates& result); - void getObjectsToAdd(VectorOfMotionStates& result); + virtual void prepareEntityForDelete(EntityItemPointer entity) override; + + void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result); + void getObjectsToAddToPhysics(VectorOfMotionStates& result); void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void getObjectsToChange(VectorOfMotionStates& result); @@ -55,12 +59,10 @@ public: EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } private: - // incoming changes - SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted) - SetOfEntities _pendingAdds; // entities to be be added to PhysicsEngine (and a their EntityMotionState created) - SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed + SetOfEntities _entitiesToRemoveFromPhysics; + SetOfEntities _entitiesToAddToPhysics; - // outgoing changes + SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 22695a1b66..9e295d5cf5 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -67,7 +67,8 @@ void PhysicsEngine::init() { } } -void PhysicsEngine::addObject(ObjectMotionState* motionState) { +// private +void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { assert(motionState); btVector3 inertia(0.0f, 0.0f, 0.0f); @@ -144,7 +145,8 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) { motionState->clearIncomingDirtyFlags(); } -void PhysicsEngine::removeObject(ObjectMotionState* object) { +// private +void PhysicsEngine::removeObjectFromDynamicsWorld(ObjectMotionState* object) { // wake up anything touching this object bump(object); removeContacts(object); @@ -154,38 +156,34 @@ void PhysicsEngine::removeObject(ObjectMotionState* object) { _dynamicsWorld->removeRigidBody(body); } -void PhysicsEngine::deleteObjects(const VectorOfMotionStates& objects) { +void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { for (auto object : objects) { - removeObject(object); + removeObjectFromDynamicsWorld(object); // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. btRigidBody* body = object->getRigidBody(); object->setRigidBody(nullptr); body->setMotionState(nullptr); delete body; - object->releaseShape(); - delete object; } } // Same as above, but takes a Set instead of a Vector. Should only be called during teardown. -void PhysicsEngine::deleteObjects(const SetOfMotionStates& objects) { +void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) { for (auto object : objects) { btRigidBody* body = object->getRigidBody(); - removeObject(object); + removeObjectFromDynamicsWorld(object); // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. object->setRigidBody(nullptr); body->setMotionState(nullptr); delete body; - object->releaseShape(); - delete object; } } void PhysicsEngine::addObjects(const VectorOfMotionStates& objects) { for (auto object : objects) { - addObject(object); + addObjectToDynamicsWorld(object); } } @@ -211,8 +209,8 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob } void PhysicsEngine::reinsertObject(ObjectMotionState* object) { - removeObject(object); - addObject(object); + removeObjectFromDynamicsWorld(object); + addObjectToDynamicsWorld(object); } void PhysicsEngine::removeContacts(ObjectMotionState* motionState) { diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 05032ccae2..0ca9b2aca8 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -54,11 +54,9 @@ public: void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } const QUuid& getSessionID() const { return _sessionID; } - void addObject(ObjectMotionState* motionState); - void removeObject(ObjectMotionState* motionState); + void removeObjects(const VectorOfMotionStates& objects); + void removeObjects(const SetOfMotionStates& objects); // only called during teardown - void deleteObjects(const VectorOfMotionStates& objects); - void deleteObjects(const SetOfMotionStates& objects); // only called during teardown void addObjects(const VectorOfMotionStates& objects); VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); void reinsertObject(ObjectMotionState* object); @@ -86,8 +84,6 @@ public: /// \brief call bump on any objects that touch the object corresponding to motionState void bump(ObjectMotionState* motionState); - void removeRigidBody(btRigidBody* body); - void setCharacterController(CharacterController* character); void dumpNextStats() { _dumpNextStats = true; } @@ -100,6 +96,9 @@ public: void forEachAction(std::function actor); private: + void addObjectToDynamicsWorld(ObjectMotionState* motionState); + void removeObjectFromDynamicsWorld(ObjectMotionState* motionState); + void removeContacts(ObjectMotionState* motionState); void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB); @@ -116,7 +115,6 @@ private: ContactMap _contactMap; uint32_t _numContactFrames = 0; - uint32_t _lastNumSubstepsAtUpdateInternal = 0; /// character collisions CharacterController* _myAvatarController; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6aa48a532d..8026102478 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -51,6 +51,8 @@ #include "MIDIEvent.h" +static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; + Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) static int functionSignatureMetaID = qRegisterMetaType(); @@ -112,7 +114,7 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName const auto line = QString::number(engine.uncaughtExceptionLineNumber()); engine.clearExceptions(); - auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line); + auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); if (!backtrace.empty()) { static const auto lineSeparator = "\n "; message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); @@ -133,6 +135,10 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _allScriptsMutex.lock(); _allKnownScriptEngines.insert(this); _allScriptsMutex.unlock(); + + connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { + hadUncaughtExceptions(*this, _fileNameString); + }); } ScriptEngine::~ScriptEngine() { diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index cc67aedcd3..dc38671091 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -115,6 +115,9 @@ public: void forEachChild(std::function actor); void forEachDescendant(std::function actor); + void die() { _isDead = true; } + bool isDead() const { return _isDead; } + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; @@ -141,7 +144,8 @@ protected: private: mutable ReadWriteLockable _transformLock; Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform. - mutable bool _parentKnowsMe = false; + mutable bool _parentKnowsMe { false }; + bool _isDead { false }; }; diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 956a5a42c7..269ef6d6c9 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -15,6 +15,8 @@ #include #include +#include +#include #include #include @@ -90,7 +92,6 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QString url; QString title; int width = 100, height = 100; - bool isToolWindow = false; bool visible = true; if (argumentCount > 1) { @@ -141,6 +142,7 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { setupServer(); retVal = function(context, object); + context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); registerObject(url.toLower(), retVal); if (!title.isEmpty()) { retVal->setTitle(title); @@ -274,7 +276,7 @@ void QmlWindowClass::hasClosed() { } void QmlWindowClass::raise() { - // FIXME + QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::QueuedConnection); } #include "QmlWindowClass.moc" diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 768dbf9007..3377aac14c 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -58,7 +58,7 @@ bool SixenseManager::_sixenseLoaded = false; const QString SixenseManager::NAME = "Sixense"; const QString SixenseManager::HYDRA_ID_STRING = "Razer Hydra"; -const QString MENU_PARENT = "Avatar"; +const QString MENU_PARENT = "Developer"; const QString MENU_NAME = "Sixense"; const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; diff --git a/stack-manager/src/AppDelegate.cpp b/stack-manager/src/AppDelegate.cpp index 61d6a8ee48..c2d629faee 100644 --- a/stack-manager/src/AppDelegate.cpp +++ b/stack-manager/src/AppDelegate.cpp @@ -77,6 +77,8 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const Q fprintf(stdout, "Fatal: %s\n", qPrintable(msg)); txt += msg; break; + default: + break; } if (outStream) {