diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index f44c8185d8..5417220ef1 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -49,7 +49,7 @@ Var STR_CONTAINS_VAR_3 Var STR_CONTAINS_VAR_4 Var STR_RETURN_VAR - + Function StrContains Exch $STR_NEEDLE Exch 1 @@ -343,22 +343,29 @@ SectionEnd ;-------------------------------- ;Pages !insertmacro MUI_PAGE_WELCOME - + !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" + + Page custom InstallTypesPage ReadInstallTypes + + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction !insertmacro MUI_PAGE_DIRECTORY - + ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER - + + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction @CPACK_NSIS_PAGE_COMPONENTS@ - + Page custom PostInstallOptionsPage ReadPostInstallOptions !insertmacro MUI_PAGE_INSTFILES - + !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES @@ -442,6 +449,10 @@ Var CleanInstallCheckbox Var CurrentOffset Var OffsetUnits Var CopyFromProductionCheckbox +Var ExpressInstallRadioButton +Var CustomInstallRadioButton +Var InstallTypeDialog +Var Express !macro SetPostInstallOption Checkbox OptionName Default ; reads the value for the given post install option to the registry @@ -459,6 +470,60 @@ Var CopyFromProductionCheckbox ${EndIf} !macroend +Function InstallTypesPage + !insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install" + + nsDialogs::Create 1018 + Pop $InstallTypeDialog + + ${If} $InstallTypeDialog == error + Abort + ${EndIf} + + StrCpy $CurrentOffset 0 + StrCpy $OffsetUnits u + StrCpy $Express "0" + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls High Fidelity Interface and High Fidelity Sandbox" + pop $ExpressInstallRadioButton + ${NSD_OnClick} $ExpressInstallRadioButton ChangeExpressLabel + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateRadiobutton} 30% $CurrentOffset$OffsetUnits 100% 10u "Custom Install (Advanced)" + pop $CustomInstallRadioButton + ${NSD_OnClick} $CustomInstallRadioButton ChangeCustomLabel + ${EndIf} + + ; Express Install selected by default + ${NSD_Check} $ExpressInstallRadioButton + Call ChangeExpressLabel + + nsDialogs::Show +FunctionEnd + +Function ChangeExpressLabel + Push $R1 + GetDlgItem $R1 $HWNDPARENT 1 + SendMessage $R1 ${WM_SETTEXT} 0 "STR:Install" + Pop $R1 +FunctionEnd + +Function ChangeCustomLabel + Push $R1 + GetDlgItem $R1 $HWNDPARENT 1 + SendMessage $R1 ${WM_SETTEXT} 0 "STR:Next >" + Pop $R1 +FunctionEnd + +Function AbortFunction + ; Check if Express is set, if so, abort the post install options page + Call HandleInstallTypes ; Sets Express if ExpressInstallRadioButton is checked and installs with defaults + StrCmp $Express "1" 0 end + Abort + end: +FunctionEnd + Function PostInstallOptionsPage !insertmacro MUI_HEADER_TEXT "Setup Options" "" @@ -549,9 +614,15 @@ Function PostInstallOptionsPage ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install" Pop $CopyFromProductionCheckbox - ${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED} + ${NSD_SetState} $CopyFromProductionCheckbox ${BST_UNCHECKED} ${EndIf} + ; Check if Express is set, if so, abort the post install options page + Call HandleInstallTypes ; Sets Express if ExpressInstallRadioButton is checked and installs with defaults + StrCmp $Express "1" 0 end + Abort + end: + nsDialogs::Show FunctionEnd @@ -567,6 +638,16 @@ Var LaunchServerNowState Var LaunchClientNowState Var CopyFromProductionState Var CleanInstallState +Var ExpressInstallState +Var CustomInstallState + +Function ReadInstallTypes + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for express/custom install + ${NSD_GetState} $ExpressInstallRadioButton $ExpressInstallState + ${NSD_GetState} $CustomInstallRadioButton $CustomInstallState + ${EndIf} +FunctionEnd Function ReadPostInstallOptions ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} @@ -603,6 +684,28 @@ Function ReadPostInstallOptions ${EndIf} FunctionEnd +Function HandleInstallTypes + ${If} $ExpressInstallState == ${BST_CHECKED} + + StrCpy $Express "1" + + ; over ride custom checkboxes and select defaults + ${NSD_SetState} $DesktopClientCheckbox ${BST_CHECKED} + ${NSD_SetState} $ServerStartupCheckbox ${BST_CHECKED} + ${NSD_SetState} $LaunchServerNowCheckbox ${BST_CHECKED} + ${NSD_SetState} $LaunchClientNowCheckbox ${BST_CHECKED} + + ${If} @PR_BUILD@ == 1 + ${NSD_SetState} $CopyFromProductionCheckbox ${BST_UNCHECKED} + ${EndIf} + + ; call ReadPostInstallOptions and HandlePostInstallOptions with defaults selected + Call ReadPostInstallOptions + Call HandlePostInstallOptions + + ${EndIf} +FunctionEnd + Function HandlePostInstallOptions ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if the user asked for a desktop shortcut to High Fidelity @@ -624,6 +727,7 @@ Function HandlePostInstallOptions !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} + ; check if the user asked to have Sandbox launched every startup ${If} $ServerStartupState == ${BST_CHECKED} ; in case we added a shortcut in the global context, pull that now diff --git a/interface/resources/images/cursor-arrow.png b/interface/resources/images/cursor-arrow.png new file mode 100644 index 0000000000..408881b585 Binary files /dev/null and b/interface/resources/images/cursor-arrow.png differ diff --git a/interface/resources/images/cursor-link.png b/interface/resources/images/cursor-link.png new file mode 100644 index 0000000000..67ff4c7b67 Binary files /dev/null and b/interface/resources/images/cursor-link.png differ diff --git a/interface/resources/images/cursor-none.png b/interface/resources/images/cursor-none.png new file mode 100644 index 0000000000..0b3f499242 Binary files /dev/null and b/interface/resources/images/cursor-none.png differ diff --git a/interface/resources/images/cursor-reticle.png b/interface/resources/images/cursor-reticle.png new file mode 100644 index 0000000000..2d9ba8fd65 Binary files /dev/null and b/interface/resources/images/cursor-reticle.png differ diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index a400897276..ee37dbd8db 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -28,6 +28,7 @@ ScrollingWindow { minSize: Qt.vector2d(200, 300) property int colorScheme: hifi.colorSchemes.dark + property int selectionMode: SelectionMode.ExtendedSelection HifiConstants { id: hifi } @@ -35,7 +36,8 @@ ScrollingWindow { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - + property var selectedItems: treeView.selection.selectedIndexes.length; + Settings { category: "Overlay.AssetServer" property alias x: root.x @@ -48,7 +50,7 @@ ScrollingWindow { assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError); reload(); } - + function doDeleteFile(path) { console.log("Deleting " + path); @@ -118,11 +120,23 @@ ScrollingWindow { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; + + if (selectedItems > 1) { + return false; + } return supportedExtensions.reduce(function(total, current) { return total | new RegExp(current).test(path); }, false); } + + function canRename() { + if (treeView.selection.hasSelection && selectedItems == 1) { + return true; + } else { + return false; + } + } function clear() { Assets.mappingModel.clear(); @@ -299,23 +313,37 @@ ScrollingWindow { }); } function deleteFile(index) { + var path = []; + if (!index) { - index = treeView.selection.currentIndex; + for (var i = 0; i < selectedItems; i++) { + treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); + index = treeView.selection.currentIndex; + path[i] = assetProxyModel.data(index, 0x100); + } } - var path = assetProxyModel.data(index, 0x100); + if (!path) { return; } - + + var modalMessage = ""; + var items = selectedItems.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - + + if (selectedItems > 1) { + modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; + } else { + modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + } + var object = desktop.messageBox({ icon: hifi.icons.question, buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, defaultButton: OriginalDialogs.StandardButton.Yes, title: "Delete", - text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?" + text: modalMessage }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { @@ -455,20 +483,20 @@ ScrollingWindow { color: hifi.buttons.black colorScheme: root.colorScheme width: 120 - + enabled: canAddToWorld(assetProxyModel.data(treeView.selection.currentIndex, 0x100)) - + onClicked: root.addToWorld() } - + HifiControls.Button { text: "Rename" color: hifi.buttons.black colorScheme: root.colorScheme width: 80 - + onClicked: root.renameFile() - enabled: treeView.selection.hasSelection + enabled: canRename() } HifiControls.Button { @@ -524,6 +552,7 @@ ScrollingWindow { treeModel: assetProxyModel canEdit: true colorScheme: root.colorScheme + selectionMode: SelectionMode.ExtendedSelection modifyEl: renameEl diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 55c2c71c61..a7b4ad7a53 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -37,6 +37,7 @@ Rectangle { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; + property var selectedItems: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -119,11 +120,23 @@ Rectangle { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; + + if (selectedItems > 1) { + return false; + } return supportedExtensions.reduce(function(total, current) { return total | new RegExp(current).test(path); }, false); } + + function canRename() { + if (treeView.selection.hasSelection && selectedItems == 1) { + return true; + } else { + return false; + } + } function clear() { Assets.mappingModel.clear(); @@ -300,23 +313,37 @@ Rectangle { }); } function deleteFile(index) { + var path = []; + if (!index) { - index = treeView.selection.currentIndex; + for (var i = 0; i < selectedItems; i++) { + treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); + index = treeView.selection.currentIndex; + path[i] = assetProxyModel.data(index, 0x100); + } } - var path = assetProxyModel.data(index, 0x100); + if (!path) { return; } + var modalMessage = ""; + var items = selectedItems.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; + + if (selectedItems > 1) { + modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; + } else { + modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + } var object = tabletRoot.messageBox({ icon: hifi.icons.question, buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, defaultButton: OriginalDialogs.StandardButton.Yes, title: "Delete", - text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?" + text: modalMessage }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { @@ -469,7 +496,7 @@ Rectangle { width: 80 onClicked: root.renameFile() - enabled: treeView.selection.hasSelection + enabled: canRename() } HifiControls.Button { @@ -525,6 +552,7 @@ Rectangle { treeModel: assetProxyModel canEdit: true colorScheme: root.colorScheme + selectionMode: SelectionMode.ExtendedSelection modifyEl: renameEl diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7b6b83fedd..7c22e1bc55 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -624,6 +624,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false; +const QString DEFAULT_CURSOR_NAME = "DEFAULT"; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runningMarkerExisted) : QApplication(argc, argv), @@ -643,6 +644,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR), _preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), + _preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME), _scaleMirror(1.0f), _rotateMirror(0.0f), _raiseMirror(0.0f), @@ -938,14 +940,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); -#ifdef Q_OS_MAC - auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget -#else - // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the - // window menu, which is a pain, so only hide it for the GL surface - auto cursorTarget = _glWidget; -#endif - cursorTarget->setCursor(Qt::BlankCursor); + showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get())); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); @@ -1743,9 +1738,16 @@ void Application::checkChangeCursor() { } } -void Application::showCursor(const QCursor& cursor) { +void Application::showCursor(const Cursor::Icon& cursor) { QMutexLocker locker(&_changeCursorLock); - _desiredCursor = cursor; + + auto managedCursor = Cursor::Manager::instance().getCursor(); + auto curIcon = managedCursor->getIcon(); + if (curIcon != cursor) { + managedCursor->setIcon(cursor); + curIcon = cursor; + } + _desiredCursor = cursor == Cursor::Icon::SYSTEM ? Qt::ArrowCursor : Qt::BlankCursor; _cursorNeedsChanging = true; } @@ -2166,9 +2168,11 @@ void Application::initializeUi() { _window->setMenuBar(new Menu()); auto compositorHelper = DependencyManager::get(); - connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, [=] { + connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] { if (isHMDMode()) { - showCursor(compositorHelper->getAllowMouseCapture() ? Qt::BlankCursor : Qt::ArrowCursor); + showCursor(compositorHelper->getAllowMouseCapture() ? + Cursor::Manager::lookupIcon(_preferredCursor.get()) : + Cursor::Icon::SYSTEM); } }); @@ -2510,6 +2514,12 @@ void Application::setPreferAvatarFingerOverStylus(bool value) { _preferAvatarFingerOverStylusSetting.set(value); } +void Application::setPreferredCursor(const QString& cursorName) { + qCDebug(interfaceapp) << "setPreferredCursor" << cursorName; + _preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName); + showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get())); +} + void Application::setSettingConstrainToolbarPosition(bool setting) { _constrainToolbarPosition.set(setting); DependencyManager::get()->setConstrainToolbarToCenterX(setting); @@ -3080,9 +3090,13 @@ void Application::keyPressEvent(QKeyEvent* event) { auto cursor = Cursor::Manager::instance().getCursor(); auto curIcon = cursor->getIcon(); if (curIcon == Cursor::Icon::DEFAULT) { - cursor->setIcon(Cursor::Icon::LINK); + showCursor(Cursor::Icon::RETICLE); + } else if (curIcon == Cursor::Icon::RETICLE) { + showCursor(Cursor::Icon::SYSTEM); + } else if (curIcon == Cursor::Icon::SYSTEM) { + showCursor(Cursor::Icon::LINK); } else { - cursor->setIcon(Cursor::Icon::DEFAULT); + showCursor(Cursor::Icon::DEFAULT); } } else { resetSensors(true); diff --git a/interface/src/Application.h b/interface/src/Application.h index 54d4a9ef90..4f835d5525 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -55,6 +55,7 @@ #include "BandwidthRecorder.h" #include "FancyCamera.h" #include "ConnectionMonitor.h" +#include "CursorManager.h" #include "gpu/Context.h" #include "Menu.h" #include "octree/OctreePacketProcessor.h" @@ -165,7 +166,7 @@ public: QSize getDeviceSize() const; bool hasFocus() const; - void showCursor(const QCursor& cursor); + void showCursor(const Cursor::Icon& cursor); bool isThrottleRendering() const; @@ -400,6 +401,9 @@ public slots: void loadDomainConnectionDialog(); void showScriptLogs(); + const QString getPreferredCursor() const { return _preferredCursor.get(); } + void setPreferredCursor(const QString& cursor); + private slots: void showDesktop(); void clearDomainOctreeDetails(); @@ -567,6 +571,7 @@ private: Setting::Handle _hmdTabletBecomesToolbarSetting; Setting::Handle _preferAvatarFingerOverStylusSetting; Setting::Handle _constrainToolbarPosition; + Setting::Handle _preferredCursor; float _scaleMirror; float _rotateMirror; @@ -640,7 +645,7 @@ private: void checkChangeCursor(); mutable QMutex _changeCursorLock { QMutex::Recursive }; - QCursor _desiredCursor{ Qt::BlankCursor }; + Qt::CursorShape _desiredCursor{ Qt::BlankCursor }; bool _cursorNeedsChanging { false }; QThread* _deadlockWatchdogThread; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7400261858..07843b8c33 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -613,7 +613,7 @@ void MyAvatar::simulate(float deltaTime) { MovingEntitiesOperator moveOperator(entityTree); forEachDescendant([&](SpatiallyNestablePointer object) { // if the queryBox has changed, tell the entity-server - if (object->computePuffedQueryAACube() && object->getNestableType() == NestableType::Entity) { + if (object->getNestableType() == NestableType::Entity && object->checkAndMaybeUpdateQueryAACube()) { EntityItemPointer entity = std::static_pointer_cast(object); bool success; AACube newCube = entity->getQueryAACube(success); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 8eb276dad1..c9e59ada23 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -106,6 +106,12 @@ void setupPreferences() { auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); } + { + static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) }; + auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; }; + auto setter = [](bool value) { qApp->setPreferredCursor(value ? RETICLE_ICON_NAME : QString()); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter)); + } // Snapshots static const QString SNAPSHOTS { "Snapshots" }; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 100f853a96..d21bc974a8 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -323,7 +323,7 @@ signals: private: void cleanupOverlaysToDelete(); - mutable QMutex _mutex; + mutable QMutex _mutex { QMutex::Recursive }; QMap _overlaysHUD; QMap _overlaysWorld; #if OVERLAY_PANELS diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 378f344d26..422488f86f 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1355,8 +1355,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); AACube saveQueryAACube = _queryAACube; - checkAndAdjustQueryAACube(); - if (saveQueryAACube != _queryAACube) { + if (checkAndMaybeUpdateQueryAACube() && saveQueryAACube != _queryAACube) { somethingChanged = true; } @@ -1424,26 +1423,20 @@ void EntityItem::setDimensions(const glm::vec3& value) { /// AACube EntityItem::getMaximumAACube(bool& success) const { if (_recalcMaxAACube) { - // * we know that the position is the center of rotation glm::vec3 centerOfRotation = getPosition(success); // also where _registration point is if (success) { _recalcMaxAACube = false; - // * we know that the registration point is the center of rotation - // * we can calculate the length of the furthest extent from the registration point - // as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint) - glm::vec3 dimensions = getDimensions(); - glm::vec3 registrationPoint = (dimensions * _registrationPoint); - glm::vec3 registrationRemainder = (dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint)); - glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder); + // we want to compute the furthestExtent that an entity can extend out from its "position" + // to do this we compute the max of these two vec3s: registration and 1-registration + // and then scale by dimensions + glm::vec3 maxExtents = getDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint); - // * we know that if you rotate in any direction you would create a sphere - // that has a radius of the length of furthest extent from registration point - float radius = glm::length(furthestExtentFromRegistration); + // there exists a sphere that contains maxExtents for all rotations + float radius = glm::length(maxExtents); - // * we know that the minimum bounding cube of this maximum possible sphere is - // (center - radius) to (center + radius) + // put a cube around the sphere + // TODO? replace _maxAACube with _boundingSphereRadius glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); - _maxAACube = AACube(minimumCorner, radius * 2.0f); } } else { @@ -1635,6 +1628,8 @@ void EntityItem::updateDimensions(const glm::vec3& value) { if (getDimensions() != value) { setDimensions(value); markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); + _queryAACubeSet = false; + dimensionsChanged(); } } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a207902789..20b541f563 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -2157,12 +2157,17 @@ QList EntityItemProperties::listChangedProperties() { return out; } -bool EntityItemProperties::parentDependentPropertyChanged() const { - return localPositionChanged() || positionChanged() || - localRotationChanged() || rotationChanged() || - localVelocityChanged() || localAngularVelocityChanged(); +bool EntityItemProperties::transformChanged() const { + return positionChanged() || rotationChanged() || + localPositionChanged() || localRotationChanged(); } bool EntityItemProperties::parentRelatedPropertyChanged() const { - return parentDependentPropertyChanged() || parentIDChanged() || parentJointIndexChanged(); + return positionChanged() || rotationChanged() || + localPositionChanged() || localRotationChanged() || + parentIDChanged() || parentJointIndexChanged(); +} + +bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { + return parentRelatedPropertyChanged() || dimensionsChanged(); } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index b526ac663c..90afd39fbf 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -86,8 +86,9 @@ public: EntityPropertyFlags getChangedProperties() const; - bool parentDependentPropertyChanged() const; // was there a changed in a property that requires parent info to interpret? - bool parentRelatedPropertyChanged() const; // parentDependentPropertyChanged or parentID or parentJointIndex + bool transformChanged() const; + bool parentRelatedPropertyChanged() const; + bool queryAACubeRelatedPropertyChanged() const; AABox getAABox() const; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 9d2cb30c6e..e21c9581e1 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -221,7 +221,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { - if (propertiesWithSimID.parentRelatedPropertyChanged()) { + if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. bool success; AACube queryAACube = entity->getQueryAACube(success); @@ -435,7 +435,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& entity->rememberHasSimulationOwnershipBid(); } } - if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) { + if (properties.queryAACubeRelatedPropertyChanged()) { properties.setQueryAACube(entity->getQueryAACube()); } entity->setLastBroadcast(usecTimestampNow()); @@ -445,7 +445,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // if they've changed. entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { - if (descendant->computePuffedQueryAACube()) { + if (descendant->checkAndMaybeUpdateQueryAACube()) { EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5e58736477..f17b991b69 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1675,6 +1675,7 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen addToNeedsParentFixupList(entity); } entity->forceQueryAACubeUpdate(); + entity->checkAndMaybeUpdateQueryAACube(); moveOperator.addEntityToMoveList(entity, entity->getQueryAACube()); i++; } else { @@ -1693,7 +1694,7 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); if (entity) { // queue the packet to send to the server - entity->computePuffedQueryAACube(); + entity->updateQueryAACube(); EntityItemProperties properties = entity->getProperties(); properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties); diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index e406926141..d41b22137e 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -136,7 +136,7 @@ void SimpleEntitySimulation::sortEntitiesThatMoved() { SetOfEntities::iterator itemItr = _entitiesToSort.begin(); while (itemItr != _entitiesToSort.end()) { EntityItemPointer entity = *itemItr; - entity->computePuffedQueryAACube(); + entity->checkAndMaybeUpdateQueryAACube(); ++itemItr; } EntitySimulation::sortEntitiesThatMoved(); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index ecfb2b55e4..f02dcee8f6 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -484,11 +484,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { return false; } - if (_entity->dynamicDataNeedsTransmit()) { - return true; - } - - if (_entity->queryAABoxNeedsUpdate()) { + if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { return true; } @@ -577,9 +573,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setActionData(_serverActionData); } - if (properties.parentRelatedPropertyChanged() && _entity->computePuffedQueryAACube()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - properties.setQueryAACube(_entity->getQueryAACube()); + if (properties.transformChanged()) { + if (_entity->checkAndMaybeUpdateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); + } } // set the LastEdited of the properties but NOT the entity itself @@ -643,7 +641,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); - if (descendant->computePuffedQueryAACube()) { + if (descendant->checkAndMaybeUpdateQueryAACube()) { EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 4fe9cda825..fc189c5e3e 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -946,11 +946,35 @@ AACube SpatiallyNestable::getMaximumAACube(bool& success) const { return AACube(getPosition(success) - glm::vec3(defaultAACubeSize / 2.0f), defaultAACubeSize); } -bool SpatiallyNestable::checkAndAdjustQueryAACube() { - bool success; +const float PARENTED_EXPANSION_FACTOR = 3.0f; + +bool SpatiallyNestable::checkAndMaybeUpdateQueryAACube() { + bool success = false; AACube maxAACube = getMaximumAACube(success); - if (success && (!_queryAACubeSet || !_queryAACube.contains(maxAACube))) { - setQueryAACube(maxAACube); + if (success) { + // maybe update _queryAACube + if (!_queryAACubeSet || (_parentID.isNull() && _children.size() == 0) || !_queryAACube.contains(maxAACube)) { + if (_parentJointIndex != INVALID_JOINT_INDEX || _children.size() > 0 ) { + // make an expanded AACube centered on the object + float scale = PARENTED_EXPANSION_FACTOR * maxAACube.getScale(); + _queryAACube = AACube(maxAACube.calcCenter() - glm::vec3(0.5f * scale), scale); + } else { + _queryAACube = maxAACube; + } + + getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) { + bool childSuccess; + AACube descendantAACube = descendant->getQueryAACube(childSuccess); + if (childSuccess) { + if (_queryAACube.contains(descendantAACube)) { + return; + } + _queryAACube += descendantAACube.getMinimumPoint(); + _queryAACube += descendantAACube.getMaximumPoint(); + } + }); + _queryAACubeSet = true; + } } return success; } @@ -961,46 +985,34 @@ void SpatiallyNestable::setQueryAACube(const AACube& queryAACube) { return; } _queryAACube = queryAACube; - if (queryAACube.getScale() > 0.0f) { - _queryAACubeSet = true; - } + _queryAACubeSet = true; } -bool SpatiallyNestable::queryAABoxNeedsUpdate() const { - bool success; - AACube currentAACube = getMaximumAACube(success); - if (!success) { - qCDebug(shared) << "can't getMaximumAACube for" << getID(); - return false; +bool SpatiallyNestable::queryAACubeNeedsUpdate() const { + if (!_queryAACubeSet) { + return true; } // make sure children are still in their boxes, also. bool childNeedsUpdate = false; getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) { - if (!childNeedsUpdate && descendant->queryAABoxNeedsUpdate()) { + if (!childNeedsUpdate && descendant->queryAACubeNeedsUpdate()) { childNeedsUpdate = true; } }); - if (childNeedsUpdate) { - return true; - } - - if (_queryAACubeSet && _queryAACube.contains(currentAACube)) { - return false; - } - - return true; + return childNeedsUpdate; } -bool SpatiallyNestable::computePuffedQueryAACube() { - if (!queryAABoxNeedsUpdate()) { - return false; - } +void SpatiallyNestable::updateQueryAACube() { bool success; - AACube currentAACube = getMaximumAACube(success); - // make an AACube with edges thrice as long and centered on the object - _queryAACube = AACube(currentAACube.getCorner() - glm::vec3(currentAACube.getScale()), currentAACube.getScale() * 3.0f); - _queryAACubeSet = true; + AACube maxAACube = getMaximumAACube(success); + if (_parentJointIndex != INVALID_JOINT_INDEX || _children.size() > 0 ) { + // make an expanded AACube centered on the object + float scale = PARENTED_EXPANSION_FACTOR * maxAACube.getScale(); + _queryAACube = AACube(maxAACube.calcCenter() - glm::vec3(0.5f * scale), scale); + } else { + _queryAACube = maxAACube; + } getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) { bool success; @@ -1013,8 +1025,7 @@ bool SpatiallyNestable::computePuffedQueryAACube() { _queryAACube += descendantAACube.getMaximumPoint(); } }); - - return true; + _queryAACubeSet = true; } AACube SpatiallyNestable::getQueryAACube(bool& success) const { diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index b98ab4c358..78a014d7c1 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -102,11 +102,11 @@ public: virtual glm::vec3 getParentAngularVelocity(bool& success) const; virtual AACube getMaximumAACube(bool& success) const; - virtual bool checkAndAdjustQueryAACube(); - virtual bool computePuffedQueryAACube(); + bool checkAndMaybeUpdateQueryAACube(); + void updateQueryAACube(); virtual void setQueryAACube(const AACube& queryAACube); - virtual bool queryAABoxNeedsUpdate() const; + virtual bool queryAACubeNeedsUpdate() const; void forceQueryAACubeUpdate() { _queryAACubeSet = false; } virtual AACube getQueryAACube(bool& success) const; virtual AACube getQueryAACube() const; @@ -197,7 +197,7 @@ protected: mutable QHash _children; virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed - virtual void dimensionsChanged() { } // called when a this object's dimensions have changed + virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed virtual void parentDeleted() { } // called on children of a deleted parent // _queryAACube is used to decide where something lives in the octree diff --git a/libraries/ui/src/CursorManager.cpp b/libraries/ui/src/CursorManager.cpp index f768b5f227..106574ff4f 100644 --- a/libraries/ui/src/CursorManager.cpp +++ b/libraries/ui/src/CursorManager.cpp @@ -31,12 +31,34 @@ namespace Cursor { } }; - static QMap ICONS; + QMap Manager::ICON_NAMES { + { Icon::SYSTEM, "SYSTEM", }, + { Icon::DEFAULT, "DEFAULT", }, + { Icon::LINK, "LINK", }, + { Icon::ARROW, "ARROW", }, + { Icon::RETICLE, "RETICLE", }, + }; + QMap Manager::ICONS; static uint16_t _customIconId = Icon::USER_BASE; Manager::Manager() { - ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/arrow.png"; - ICONS[Icon::LINK] = PathUtils::resourcesPath() + "images/link.png"; + ICONS[Icon::SYSTEM] = PathUtils::resourcesPath() + "images/cursor-none.png"; + ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/cursor-arrow.png"; + ICONS[Icon::LINK] = PathUtils::resourcesPath() + "images/cursor-link.png"; + ICONS[Icon::ARROW] = PathUtils::resourcesPath() + "images/cursor-arrow.png"; + ICONS[Icon::RETICLE] = PathUtils::resourcesPath() + "images/cursor-reticle.png"; + } + + Icon Manager::lookupIcon(const QString& name) { + for (const auto& kv : ICON_NAMES.toStdMap()) { + if (kv.second == name) { + return static_cast(kv.first); + } + } + return Icon::DEFAULT; + } + const QString& Manager::getIconName(const Icon& icon) { + return ICON_NAMES.count(icon) ? ICON_NAMES[icon] : ICON_NAMES[Icon::DEFAULT]; } Manager& Manager::instance() { diff --git a/libraries/ui/src/CursorManager.h b/libraries/ui/src/CursorManager.h index 99d5ccdc77..2a92cc7f45 100644 --- a/libraries/ui/src/CursorManager.h +++ b/libraries/ui/src/CursorManager.h @@ -18,16 +18,18 @@ namespace Cursor { }; enum Icon { + SYSTEM, DEFAULT, LINK, GRAB, + ARROW, + RETICLE, // Add new system cursors here // User cursors will have ids over this value USER_BASE = 0xFF, }; - class Instance { public: virtual Source getType() const = 0; @@ -49,6 +51,11 @@ namespace Cursor { uint16_t registerIcon(const QString& path); QList registeredIcons() const; const QString& getIconImage(uint16_t icon); + + static QMap ICONS; + static QMap ICON_NAMES; + static Icon lookupIcon(const QString& name); + static const QString& getIconName(const Icon& icon); private: float _scale{ 1.0f }; }; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 93f4787f0f..0df504dfa2 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -126,15 +126,17 @@ void OculusBaseDisplayPlugin::internalDeactivate() { } bool OculusBaseDisplayPlugin::activateStandBySession() { - _session = acquireOculusSession(); if (!_session) { - return false; + _session = acquireOculusSession(); } - return true; + return _session; } void OculusBaseDisplayPlugin::deactivateSession() { - releaseOculusSession(); - _session = nullptr; + // FIXME + // Switching to Qt 5.9 exposed a race condition or similar issue that caused a crash when putting on an Rift + // while already in VR mode. Commenting these out is a workaround. + //releaseOculusSession(); + //_session = nullptr; } void OculusBaseDisplayPlugin::updatePresentPose() { //mat4 sensorResetMat;