diff --git a/BUILD.md b/BUILD.md index c45b7cb636..30302d611b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,28 +1,25 @@ ### Dependencies -* [cmake](https://cmake.org/download/) ~> 3.3.2 -* [Qt](https://www.qt.io/download-open-source) ~> 5.6.2 -* [OpenSSL](https://www.openssl.org/community/binaries.html) - * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. -* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) +- [cmake](https://cmake.org/download/): 3.9 +- [Qt](https://www.qt.io/download-open-source): 5.9.1 +- [OpenSSL](https://www.openssl.org/): Use the latest available version of OpenSSL to avoid security vulnerabilities. +- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) -#### CMake External Project Dependencies +### CMake External Project Dependencies -* [boostconfig](https://github.com/boostorg/config) ~> 1.58 -* [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83 -* [GLEW](http://glew.sourceforge.net/) -* [glm](https://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4 -* [gverb](https://github.com/highfidelity/gverb) -* [Oculus SDK](https://developer.oculus.com/downloads/) ~> 0.6 (Win32) / 0.5 (Mac / Linux) -* [oglplus](http://oglplus.org/) ~> 0.63 -* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only) -* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1 -* [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1 -* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3 -* [soxr](https://sourceforge.net/p/soxr/wiki/Home/) ~> 0.1.1 -* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3 -* [Sixense](http://sixense.com/) ~> 071615 -* [zlib](http://www.zlib.net/) ~> 1.28 (Win32 only) +These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required. +- [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83 +- [GLEW](http://glew.sourceforge.net/): 1.13 +- [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8 +- [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac) +- [OpenVR](https://github.com/ValveSoftware/openvr): 1.0.6 (Win32 only) +- [Polyvox](http://www.volumesoffun.com/): 0.2.1 +- [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3 +- [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3 +- [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/): 4.3 +- [Sixense](http://sixense.com/): 071615 +- [zlib](http://www.zlib.net/): 1.28 (Win32 only) +- nVidia Texture Tools: 2.1 The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build/ext` folder in each of the subfolders for each external project. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 818a176f75..3e93656d45 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -1,42 +1,45 @@ This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit. ## Building High Fidelity +Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. -### Step 1. Installing Visual Studio 2013 +Note: The prerequisites will require about 10 GB of space on your drive. -If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer. +### Step 1. Visual Studio 2017 -Note: Newer versions of Visual Studio are not yet compatible. +If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). + +When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right. ### Step 2. Installing CMake -Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. +Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). Make sure to check "Add CMake to system PATH for all users" when prompted during installation. ### Step 3. Installing Qt -Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe). +Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.9.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)". -Keep the default components checked when going through the installer. +Note: Installing the Sources is optional but recommended if you have room for them (~2GB). ### Step 4. Setting Qt Environment Variable Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search). * Set "Variable name": `QT_CMAKE_PREFIX_PATH` -* Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake` +* Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake` ### Step 5. Installing OpenSSL -Download and install the [Win64 OpenSSL v1.0.2L Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe). +Download and install the Win64 OpenSSL v1.0.2 Installer[https://slproweb.com/products/Win32OpenSSL.html]. ### Step 6. Running CMake to Generate Build Files Run Command Prompt from Start and run the following commands: -```` +``` cd "%HIFI_DIR%" mkdir build cd build -cmake .. -G "Visual Studio 12 Win64" -```` +cmake .. -G "Visual Studio 15 Win64" +``` Where `%HIFI_DIR%` is the directory for the highfidelity repository. @@ -72,14 +75,6 @@ For any problems after Step #6, first try this: Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. -#### nmake cannot be found - -Make sure nmake.exe is located at the following path: - - C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin - -If not, add the directory where nmake is located to the PATH environment variable. - #### Qt is throwing an error -Make sure you have the correct version (5.6.2) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. +Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 0ef2db3b9d..1a27ddd479 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -13,7 +13,7 @@ setup_memory_debugger() link_hifi_libraries( audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver - physics plugins + controllers physics plugins midi ) if (WIN32) 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/CMakeLists.txt b/interface/CMakeLists.txt index dcb1cacef9..c97975e50a 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -195,7 +195,7 @@ link_hifi_libraries( shared octree ktx gpu gl gpu-gl procedural model render recording fbx networking model-networking entities avatars trackers audio audio-client animation script-engine physics - render-utils entities-renderer avatars-renderer ui auto-updater + render-utils entities-renderer avatars-renderer ui auto-updater midi controllers plugins image trackers ui-plugins display-plugins input-plugins ${NON_ANDROID_LIBRARIES} diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 8f58ef3c98..73ab5cb2ae 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,6 +1,15 @@ { "name": "Vive to Standard", "channels": [ + { "from": "Vive.LY", "to": "Standard.LeftIndexPoint", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ] + }, + { "from": "Vive.RY", "to": "Standard.RightIndexPoint", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ] + }, + { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, { @@ -13,6 +22,10 @@ { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, { "from": "Vive.LS", "to": "Standard.LS" }, + { "from": "Vive.LSTouch", "to": "Standard.LeftThumbUp", + "peek": true, + "filters": [ { "type": "logicalNot" } ] + }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, @@ -27,6 +40,10 @@ { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, { "from": "Vive.RS", "to": "Standard.RS" }, + { "from": "Vive.RSTouch", "to": "Standard.RightThumbUp", + "peek": true, + "filters": [ { "type": "logicalNot" } ] + }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, { "from": "Vive.LSCenter", "to": "Standard.LeftPrimaryThumb" }, @@ -59,7 +76,7 @@ { "from": "Vive.Head", "to" : "Standard.Head"}, - { "from": "Vive.RightArm", "to" : "Standard.RightArm"}, - { "from": "Vive.LeftArm", "to" : "Standard.LeftArm"} + { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, + { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" } ] } 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 eb47afc951..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(); @@ -151,13 +165,17 @@ ScrollingWindow { var SHAPE_TYPE_SIMPLE_HULL = 1; var SHAPE_TYPE_SIMPLE_COMPOUND = 2; var SHAPE_TYPE_STATIC_MESH = 3; - + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var SHAPE_TYPES = []; SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; var DYNAMIC_DEFAULT = false; var prompt = desktop.customInputDialog({ @@ -196,6 +214,12 @@ ScrollingWindow { case SHAPE_TYPE_STATIC_MESH: shapeType = "static-mesh"; break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; default: shapeType = "none"; } @@ -289,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) { @@ -445,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 { @@ -514,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 0256805422..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(); @@ -152,13 +165,17 @@ Rectangle { var SHAPE_TYPE_SIMPLE_HULL = 1; var SHAPE_TYPE_SIMPLE_COMPOUND = 2; var SHAPE_TYPE_STATIC_MESH = 3; - + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var SHAPE_TYPES = []; SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; var DYNAMIC_DEFAULT = false; var prompt = tabletRoot.customInputDialog({ @@ -197,6 +214,12 @@ Rectangle { case SHAPE_TYPE_STATIC_MESH: shapeType = "static-mesh"; break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; default: shapeType = "none"; } @@ -290,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) { @@ -459,7 +496,7 @@ Rectangle { width: 80 onClicked: root.renameFile() - enabled: treeView.selection.hasSelection + enabled: canRename() } HifiControls.Button { @@ -515,6 +552,7 @@ Rectangle { treeModel: assetProxyModel canEdit: true colorScheme: root.colorScheme + selectionMode: SelectionMode.ExtendedSelection modifyEl: renameEl diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index d47c981440..47d28486a9 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -145,7 +145,9 @@ Rectangle { model: ["No Collision", "Basic - Whole model", "Good - Sub-meshes", - "Exact - All polygons"] + "Exact - All polygons", + "Box", + "Sphere"] } Row { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 699ddfcb2d..a38a55af33 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -398,6 +399,10 @@ public: return true; } } + + if (message->message == WM_DEVICECHANGE) { + Midi::USBchanged(); // re-scan the MIDI bus + } } return false; } @@ -567,6 +572,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -618,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), @@ -637,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), @@ -932,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); @@ -1756,9 +1757,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; } @@ -2133,7 +2141,6 @@ void Application::initializeUi() { surfaceContext->setContextProperty("AvatarManager", DependencyManager::get().data()); surfaceContext->setContextProperty("UndoStack", &_undoStackScriptingInterface); surfaceContext->setContextProperty("LODManager", DependencyManager::get().data()); - surfaceContext->setContextProperty("Paths", DependencyManager::get().data()); surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); surfaceContext->setContextProperty("Scene", DependencyManager::get().data()); surfaceContext->setContextProperty("Render", _renderEngine->getConfiguration().get()); @@ -2179,9 +2186,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); } }); @@ -2523,6 +2532,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); @@ -3039,7 +3054,9 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_F: { - _physicsEngine->dumpNextStats(); + if (isOption) { + _physicsEngine->dumpNextStats(); + } break; } @@ -3083,9 +3100,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); @@ -4479,27 +4500,31 @@ void Application::cameraModeChanged() { void Application::cameraMenuChanged() { - if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { - if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { + auto menu = Menu::getInstance(); + if (menu->isOptionChecked(MenuOption::FullscreenMirror)) { + if (isHMDMode()) { + menu->setIsOptionChecked(MenuOption::FullscreenMirror, false); + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + } else if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { _myCamera.setMode(CAMERA_MODE_MIRROR); } - } else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { + } else if (menu->isOptionChecked(MenuOption::FirstPerson)) { if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); } - } else if (Menu::getInstance()->isOptionChecked(MenuOption::ThirdPerson)) { + } else if (menu->isOptionChecked(MenuOption::ThirdPerson)) { if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) { getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT); } } - } else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) { + } else if (menu->isOptionChecked(MenuOption::IndependentMode)) { if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { _myCamera.setMode(CAMERA_MODE_INDEPENDENT); } - } else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) { + } else if (menu->isOptionChecked(MenuOption::CameraEntityMode)) { if (_myCamera.getMode() != CAMERA_MODE_ENTITY) { _myCamera.setMode(CAMERA_MODE_ENTITY); } diff --git a/interface/src/Application.h b/interface/src/Application.h index ce27c4a70a..123aa85e2e 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(); @@ -564,6 +568,7 @@ private: Setting::Handle _hmdTabletBecomesToolbarSetting; Setting::Handle _preferAvatarFingerOverStylusSetting; Setting::Handle _constrainToolbarPosition; + Setting::Handle _preferredCursor; float _scaleMirror; float _rotateMirror; @@ -637,7 +642,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/Menu.cpp b/interface/src/Menu.cpp index c7223be208..56bd3eb749 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -222,19 +222,19 @@ Menu::Menu() { cameraModeGroup->setExclusive(true); // View > First Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::FirstPerson, 0, - true, qApp, SLOT(cameraMenuChanged()))); + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F, + true, qApp, SLOT(cameraMenuChanged()))); // View > Third Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::ThirdPerson, 0, - false, qApp, SLOT(cameraMenuChanged()))); + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G, + false, qApp, SLOT(cameraMenuChanged()))); // View > Mirror - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::FullscreenMirror, 0, - false, qApp, SLOT(cameraMenuChanged()))); + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H, + false, qApp, SLOT(cameraMenuChanged()))); // View > Independent [advanced] cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7400261858..490f22ed8b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -258,6 +258,7 @@ MyAvatar::~MyAvatar() { void MyAvatar::setDominantHand(const QString& hand) { if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) { _dominantHand = hand; + emit dominantHandChanged(_dominantHand); } } @@ -613,7 +614,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/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1657f78b26..9ba6cb0353 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -615,6 +615,7 @@ signals: void wentAway(); void wentActive(); void skeletonChanged(); + void dominantHandChanged(const QString& hand); private: 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.cpp b/interface/src/ui/overlays/Overlays.cpp index 40ab4309b5..b6c7dcbd46 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -116,7 +116,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { QMutexLocker locker(&_mutex); foreach(Overlay::Pointer thisOverlay, _overlaysHUD) { - + // Reset all batch pipeline settings between overlay geometryCache->useSimpleDrawPipeline(batch); batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? @@ -136,7 +136,7 @@ void Overlays::enable() { _enabled = true; } -// Note, can't be invoked by scripts, but can be called by the InterfaceParentFinder +// Note, can't be invoked by scripts, but can be called by the InterfaceParentFinder // class on packet processing threads Overlay::Pointer Overlays::getOverlay(OverlayID id) const { QMutexLocker locker(&_mutex); @@ -244,8 +244,8 @@ OverlayID Overlays::cloneOverlay(OverlayID id) { bool Overlays::editOverlay(OverlayID id, const QVariant& properties) { if (QThread::currentThread() != thread()) { - // NOTE editOverlay can be called very frequently in scripts and can't afford to - // block waiting on the main thread. Additionally, no script actually + // NOTE editOverlay can be called very frequently in scripts and can't afford to + // block waiting on the main thread. Additionally, no script actually // examines the return value and does something useful with it, so use a non-blocking // invoke and just always return true QMetaObject::invokeMethod(this, "editOverlay", Q_ARG(OverlayID, id), Q_ARG(QVariant, properties)); @@ -706,27 +706,27 @@ bool Overlays::isAddedOverlay(OverlayID id) { } void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - emit mousePressOnOverlay(overlayID, event); + QMetaObject::invokeMethod(this, "mousePressOnOverlay", Q_ARG(OverlayID, overlayID), Q_ARG(PointerEvent, event)); } void Overlays::sendMouseReleaseOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - emit mouseReleaseOnOverlay(overlayID, event); + QMetaObject::invokeMethod(this, "mouseReleaseOnOverlay", Q_ARG(OverlayID, overlayID), Q_ARG(PointerEvent, event)); } void Overlays::sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - emit mouseMoveOnOverlay(overlayID, event); + QMetaObject::invokeMethod(this, "mouseMoveOnOverlay", Q_ARG(OverlayID, overlayID), Q_ARG(PointerEvent, event)); } void Overlays::sendHoverEnterOverlay(const OverlayID& id, const PointerEvent& event) { - emit hoverEnterOverlay(id, event); + QMetaObject::invokeMethod(this, "hoverEnterOverlay", Q_ARG(OverlayID, id), Q_ARG(PointerEvent, event)); } -void Overlays::sendHoverOverOverlay(const OverlayID& id, const PointerEvent& event) { - emit hoverOverOverlay(id, event); +void Overlays::sendHoverOverOverlay(const OverlayID& id, const PointerEvent& event) { + QMetaObject::invokeMethod(this, "hoverOverOverlay", Q_ARG(OverlayID, id), Q_ARG(PointerEvent, event)); } -void Overlays::sendHoverLeaveOverlay(const OverlayID& id, const PointerEvent& event) { - emit hoverLeaveOverlay(id, event); +void Overlays::sendHoverLeaveOverlay(const OverlayID& id, const PointerEvent& event) { + QMetaObject::invokeMethod(this, "hoverLeaveOverlay", Q_ARG(OverlayID, id), Q_ARG(PointerEvent, event)); } OverlayID Overlays::getKeyboardFocusOverlay() { @@ -775,7 +775,7 @@ float Overlays::height() { static const uint32_t MOUSE_POINTER_ID = 0; -static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay, +static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay, const RayToOverlayIntersectionResult& rayPickResult) { // Project the intersection point onto the local xy plane of the overlay. diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a6cc300b67..a915acb06a 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/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 1dd3389352..16d6940221 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -100,30 +100,14 @@ Web3DOverlay::~Web3DOverlay() { } _webSurface->pause(); - _webSurface->disconnect(_connection); - - QObject::disconnect(_mousePressConnection); - _mousePressConnection = QMetaObject::Connection(); - QObject::disconnect(_mouseReleaseConnection); - _mouseReleaseConnection = QMetaObject::Connection(); - QObject::disconnect(_mouseMoveConnection); - _mouseMoveConnection = QMetaObject::Connection(); - QObject::disconnect(_hoverLeaveConnection); - _hoverLeaveConnection = QMetaObject::Connection(); - - QObject::disconnect(_emitScriptEventConnection); - _emitScriptEventConnection = QMetaObject::Connection(); - QObject::disconnect(_webEventReceivedConnection); - _webEventReceivedConnection = QMetaObject::Connection(); - - // The lifetime of the QML surface MUST be managed by the main thread - // Additionally, we MUST use local variables copied by value, rather than - // member variables, since they would implicitly refer to a this that - // is no longer valid - auto webSurface = _webSurface; - AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { - DependencyManager::get()->release(QML, webSurface); - }); + auto overlays = &(qApp->getOverlays()); + QObject::disconnect(overlays, &Overlays::mousePressOnOverlay, this, nullptr); + QObject::disconnect(overlays, &Overlays::mouseReleaseOnOverlay, this, nullptr); + QObject::disconnect(overlays, &Overlays::mouseMoveOnOverlay, this, nullptr); + QObject::disconnect(overlays, &Overlays::hoverLeaveOverlay, this, nullptr); + QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); + QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); + DependencyManager::get()->release(QML, _webSurface); _webSurface.reset(); } auto geometryCache = DependencyManager::get(); @@ -153,6 +137,9 @@ QString Web3DOverlay::pickURL() { void Web3DOverlay::loadSourceURL() { + if (!_webSurface) { + return; + } QUrl sourceUrl(_url); if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || @@ -252,23 +239,24 @@ void Web3DOverlay::render(RenderArgs* args) { } }; - _mousePressConnection = connect(&(qApp->getOverlays()), &Overlays::mousePressOnOverlay, this, forwardPointerEvent, Qt::DirectConnection); - _mouseReleaseConnection = connect(&(qApp->getOverlays()), &Overlays::mouseReleaseOnOverlay, this, forwardPointerEvent, Qt::DirectConnection); - _mouseMoveConnection = connect(&(qApp->getOverlays()), &Overlays::mouseMoveOnOverlay, this, forwardPointerEvent, Qt::DirectConnection); - _hoverLeaveConnection = connect(&(qApp->getOverlays()), &Overlays::hoverLeaveOverlay, this, [=](OverlayID overlayID, const PointerEvent& event) { + auto overlays = &(qApp->getOverlays()); + QObject::connect(overlays, &Overlays::mousePressOnOverlay, this, forwardPointerEvent); + QObject::connect(overlays, &Overlays::mouseReleaseOnOverlay, this, forwardPointerEvent); + QObject::connect(overlays, &Overlays::mouseMoveOnOverlay, this, forwardPointerEvent); + QObject::connect(overlays, &Overlays::hoverLeaveOverlay, this, [=](OverlayID overlayID, const PointerEvent& event) { auto self = weakSelf.lock(); if (!self) { return; } - if (self->_pressed && overlayID == selfOverlayID) { + if (overlayID == selfOverlayID && (self->_pressed || (!self->_activeTouchPoints.empty() && self->_touchBeginAccepted))) { PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), event.getButton(), event.getButtons(), event.getKeyboardModifiers()); forwardPointerEvent(overlayID, event); } - }, Qt::DirectConnection); + }); - _emitScriptEventConnection = connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); - _webEventReceivedConnection = connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); + QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); + QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); } else { if (_currentMaxFPS != _desiredMaxFPS) { setMaxFPS(_desiredMaxFPS); @@ -361,19 +349,68 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { return; } - glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); - QPointF windowPoint(windowPos.x, windowPos.y); - - if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { - this->_pressed = true; - } else if (event.getType() == PointerEvent::Release && event.getButton() == PointerEvent::PrimaryButton) { - this->_pressed = false; + //do not send secondary button events to tablet + if (event.getButton() == PointerEvent::SecondaryButton || + //do not block composed events + event.getButtons() == PointerEvent::SecondaryButton) { + return; } - QEvent::Type touchType; - Qt::TouchPointState touchPointState; - QEvent::Type mouseType; + QPointF windowPoint; + { + glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); + windowPoint = QPointF(windowPos.x, windowPos.y); + } + + Qt::TouchPointState state = Qt::TouchPointStationary; + if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { + state = Qt::TouchPointPressed; + } else if (event.getType() == PointerEvent::Release) { + state = Qt::TouchPointReleased; + } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) { + state = Qt::TouchPointMoved; + } + + QEvent::Type touchType = QEvent::TouchUpdate; + if (_activeTouchPoints.empty()) { + // If the first active touch point is being created, send a begin + touchType = QEvent::TouchBegin; + } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { + // If the last active touch point is being released, send an end + touchType = QEvent::TouchEnd; + } + + { + QTouchEvent::TouchPoint point; + point.setId(event.getID()); + point.setState(state); + point.setPos(windowPoint); + point.setScreenPos(windowPoint); + _activeTouchPoints[event.getID()] = point; + } + + QTouchEvent touchEvent(touchType, &_touchDevice, event.getKeyboardModifiers()); + { + QList touchPoints; + Qt::TouchPointStates touchPointStates; + for (const auto& entry : _activeTouchPoints) { + touchPointStates |= entry.second.state(); + touchPoints.push_back(entry.second); + } + + touchEvent.setWindow(_webSurface->getWindow()); + touchEvent.setTarget(_webSurface->getRootItem()); + touchEvent.setTouchPoints(touchPoints); + touchEvent.setTouchPointStates(touchPointStates); + } + + // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. + // FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times). + // This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery". + // + // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will + // receive mouse events Qt::MouseButton button = Qt::NoButton; Qt::MouseButtons buttons = Qt::NoButton; if (event.getButton() == PointerEvent::PrimaryButton) { @@ -383,85 +420,28 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { buttons |= Qt::LeftButton; } - switch (event.getType()) { - case PointerEvent::Press: - touchType = QEvent::TouchBegin; - touchPointState = Qt::TouchPointPressed; - mouseType = QEvent::MouseButtonPress; - break; - case PointerEvent::Release: - touchType = QEvent::TouchEnd; - touchPointState = Qt::TouchPointReleased; - mouseType = QEvent::MouseButtonRelease; - break; - case PointerEvent::Move: - touchType = QEvent::TouchUpdate; - touchPointState = Qt::TouchPointMoved; - mouseType = QEvent::MouseMove; - - if (((event.getButtons() & PointerEvent::PrimaryButton) > 0) != this->_pressed) { - // Mouse was pressed/released while off the overlay; convert touch and mouse events to press/release to reflect - // current mouse/touch status. - this->_pressed = !this->_pressed; - if (this->_pressed) { - touchType = QEvent::TouchBegin; - touchPointState = Qt::TouchPointPressed; - mouseType = QEvent::MouseButtonPress; - - } else { - touchType = QEvent::TouchEnd; - touchPointState = Qt::TouchPointReleased; - mouseType = QEvent::MouseButtonRelease; - - } - button = Qt::LeftButton; - buttons |= Qt::LeftButton; - } - - break; - default: - return; - } - - //do not send secondary button events to tablet - if (event.getButton() == PointerEvent::SecondaryButton || - //do not block composed events - event.getButtons() == PointerEvent::SecondaryButton) { - return; - } - - QTouchEvent::TouchPoint point; - point.setId(event.getID()); - point.setState(touchPointState); - point.setPos(windowPoint); - point.setScreenPos(windowPoint); - QList touchPoints; - touchPoints.push_back(point); - - QTouchEvent* touchEvent = new QTouchEvent(touchType, &_touchDevice, event.getKeyboardModifiers()); - touchEvent->setWindow(_webSurface->getWindow()); - touchEvent->setTarget(_webSurface->getRootItem()); - touchEvent->setTouchPoints(touchPoints); - touchEvent->setTouchPointStates(touchPointState); - - // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. - // FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times). - // This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery". - // - // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will - // receive mouse events #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { - QMouseEvent* mouseEvent = new QMouseEvent(mouseType, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); - QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } #endif - QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); + + if (touchType == QEvent::TouchBegin) { + _touchBeginAccepted = QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); + } else if (_touchBeginAccepted) { + QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); + } + + // If this was a release event, remove the point from the active touch points + if (state == Qt::TouchPointReleased) { + _activeTouchPoints.erase(event.getID()); + } #if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { - QMouseEvent* mouseEvent = new QMouseEvent(mouseType, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); - QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } #endif } @@ -505,8 +485,8 @@ void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) { return; } - QMouseEvent* mouseEvent = new QMouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); - QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent); + QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } void Web3DOverlay::setProperties(const QVariantMap& properties) { @@ -608,6 +588,9 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { _scriptURL = scriptURL; if (_webSurface) { AbstractViewStateInterface::instance()->postLambdaEvent([this, scriptURL] { + if (!_webSurface) { + return; + } _webSurface->getRootItem()->setProperty("scriptURL", scriptURL); }); } @@ -631,9 +614,5 @@ Web3DOverlay* Web3DOverlay::createClone() const { } void Web3DOverlay::emitScriptEvent(const QVariant& message) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); - } else { - emit scriptEventReceived(message); - } + QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 3d2f9dd514..c08499cdf4 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -72,7 +72,6 @@ signals: private: InputMode _inputMode { Touch }; QSharedPointer _webSurface; - QMetaObject::Connection _connection; gpu::TexturePointer _texture; QString _url; QString _scriptURL; @@ -82,20 +81,14 @@ private: bool _showKeyboardFocusHighlight{ true }; bool _pressed{ false }; + bool _touchBeginAccepted { false }; + std::map _activeTouchPoints; QTouchDevice _touchDevice; uint8_t _desiredMaxFPS { 10 }; uint8_t _currentMaxFPS { 0 }; bool _mayNeedResize { false }; - - QMetaObject::Connection _mousePressConnection; - QMetaObject::Connection _mouseReleaseConnection; - QMetaObject::Connection _mouseMoveConnection; - QMetaObject::Connection _hoverLeaveConnection; - - QMetaObject::Connection _emitScriptEventConnection; - QMetaObject::Connection _webEventReceivedConnection; }; #endif // hifi_Web3DOverlay_h diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index e75465f007..b6c3ed4d27 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -22,6 +22,7 @@ #include "filters/DeadZoneFilter.h" #include "filters/HysteresisFilter.h" #include "filters/InvertFilter.h" +#include "filters/NotFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" #include "filters/TranslateFilter.h" @@ -40,6 +41,7 @@ REGISTER_FILTER_CLASS_INSTANCE(ConstrainToPositiveIntegerFilter, "constrainToPos REGISTER_FILTER_CLASS_INSTANCE(DeadZoneFilter, "deadZone") REGISTER_FILTER_CLASS_INSTANCE(HysteresisFilter, "hysteresis") REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert") +REGISTER_FILTER_CLASS_INSTANCE(NotFilter, "logicalNot") REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale") REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse") REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate") diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 581d51ea61..bc1ef55725 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -24,6 +24,7 @@ #include "filters/DeadZoneFilter.h" #include "filters/HysteresisFilter.h" #include "filters/InvertFilter.h" +#include "filters/NotFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" #include "filters/TranslateFilter.h" @@ -148,6 +149,11 @@ QObject* RouteBuilderProxy::pulse(float interval) { return this; } +QObject* RouteBuilderProxy::logicalNot() { + addFilter(std::make_shared()); + return this; +} + void RouteBuilderProxy::addFilter(Filter::Pointer filter) { _route->filters.push_back(filter); } diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 102e72b3ad..75f3747566 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -53,6 +53,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* postTransform(glm::mat4 transform); Q_INVOKABLE QObject* rotate(glm::quat rotation); Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant); + Q_INVOKABLE QObject* logicalNot(); private: void to(const Endpoint::Pointer& destination); diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp new file mode 100644 index 0000000000..73460aed91 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp @@ -0,0 +1,10 @@ +#include "NotFilter.h" + +using namespace controller; + +NotFilter::NotFilter() { +} + +float NotFilter::apply(float value) const { + return (value == 0.0f) ? 1.0f : 0.0f; +} diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.h b/libraries/controllers/src/controllers/impl/filters/NotFilter.h new file mode 100644 index 0000000000..ceb7d29de3 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.h @@ -0,0 +1,21 @@ +#pragma once +#ifndef hifi_Controllers_Filters_Not_h +#define hifi_Controllers_Filters_Not_h + +#include "../Filter.h" + +namespace controller { + +class NotFilter : public Filter { + REGISTER_FILTER_CLASS(NotFilter); +public: + NotFilter(); + + virtual float apply(float value) const override; + virtual Pose apply(Pose value) const override { return value; } +}; + +} + +#endif + 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/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 108cb39222..487bf60f61 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -1001,7 +1001,10 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int if (!bestFitBefore && bestFitAfter) { // This is the case where the entity existed, and is in some element in our tree... if (currentContainingElement.get() != this) { - currentContainingElement->removeEntityItem(entityItem); + // if the currentContainingElement is non-null, remove the entity from it + if (currentContainingElement) { + currentContainingElement->removeEntityItem(entityItem); + } addEntityItem(entityItem); } } 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/midi/CMakeLists.txt b/libraries/midi/CMakeLists.txt new file mode 100644 index 0000000000..dc54819c2b --- /dev/null +++ b/libraries/midi/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME midi) +setup_hifi_library(Network) +link_hifi_libraries(shared networking) diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp new file mode 100644 index 0000000000..ad650cf067 --- /dev/null +++ b/libraries/midi/src/Midi.cpp @@ -0,0 +1,275 @@ +// +// Midi.cpp +// libraries/midi/src +// +// Created by Burt Sloane +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Midi.h" + + +#include + + +#if defined Q_OS_WIN32 +#include "Windows.h" +#endif + + +#if defined Q_OS_WIN32 +const int MIDI_BYTE_MASK = 0x0FF; +const int MIDI_SHIFT_NOTE = 8; +const int MIDI_SHIFT_VELOCITY = 16; +#endif +const int MIDI_STATUS_MASK = 0x0F0; +const int MIDI_NOTE_OFF = 0x080; +const int MIDI_NOTE_ON = 0x090; +const int MIDI_CONTROL_CHANGE = 0x0b0; +const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; + + +static Midi* instance = NULL; // communicate this to non-class callbacks +static bool thruModeEnabled = false; + +std::vector Midi::midiinexclude; +std::vector Midi::midioutexclude; + + +#if defined Q_OS_WIN32 + +#pragma comment(lib, "Winmm.lib") + +// +std::vector midihin; +std::vector midihout; + + +void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MIM_OPEN: + // message not used + break; + case MIM_CLOSE: + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + midihin[i] = NULL; + instance->allNotesOff(); + } + } + break; + case MIM_DATA: { + int status = MIDI_BYTE_MASK & dwParam1; + int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); + int vel = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); + if (thruModeEnabled) { + instance->sendNote(status, note, vel); // relay the note on to all other midi devices + } + instance->noteReceived(status, note, vel); // notify the javascript + break; + } + } +} + + +void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { + switch (wMsg) { + case MOM_OPEN: + // message not used + break; + case MOM_CLOSE: + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] == hmo) { + midihout[i] = NULL; + instance->allNotesOff(); + } + } + break; + } +} + + +void Midi::sendNote(int status, int note, int vel) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (vel << MIDI_SHIFT_VELOCITY)); + } + } +} + + +void Midi::MidiSetup() { + midihin.clear(); + midihout.clear(); + + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midiinexclude.size(); j++) { + if (midiinexclude[j].toStdString().compare(incaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN INPUT BY NAME + HMIDIIN tmphin; + midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION); + midiInStart(tmphin); + midihin.push_back(tmphin); + } + + } + + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + + bool found = false; + for (int j = 0; j < midioutexclude.size(); j++) { + if (midioutexclude[j].toStdString().compare(outcaps.szPname) == 0) { + found = true; + break; + } + } + if (!found) { // EXCLUDE AN OUTPUT BY NAME + HMIDIOUT tmphout; + midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION); + midihout.push_back(tmphout); + } + } + + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); + + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] != NULL) { + midiInStop(midihin[i]); + midiInClose(midihin[i]); + } + } + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutClose(midihout[i]); + } + } + midihin.clear(); + midihout.clear(); +} +#else +void Midi::sendNote(int status, int note, int vel) { +} + +void Midi::MidiSetup() { + allNotesOff(); +} + +void Midi::MidiCleanup() { + allNotesOff(); +} +#endif + +void Midi::noteReceived(int status, int note, int velocity) { + if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && + ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON)) { + return; // NOTE: only sending note-on and note-off to Javascript + } + + QVariantMap eventData; + eventData["status"] = status; + eventData["note"] = note; + eventData["velocity"] = velocity; + emit midiNote(eventData); +} + +// + +Midi::Midi() { + instance = this; +#if defined Q_OS_WIN32 + midioutexclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing +#endif + MidiSetup(); +} + +Midi::~Midi() { +} + +void Midi::playMidiNote(int status, int note, int velocity) { + sendNote(status, note, velocity); +} + +void Midi::allNotesOff() { + sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off +} + +void Midi::resetDevices() { + MidiCleanup(); + MidiSetup(); +} + +void Midi::USBchanged() { + instance->MidiCleanup(); + instance->MidiSetup(); +} + +// + +QStringList Midi::listMidiDevices(bool output) { + QStringList rv; +#if defined Q_OS_WIN32 + if (output) { + MIDIOUTCAPS outcaps; + for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { + midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); + rv.append(outcaps.szPname); + } + } else { + MIDIINCAPS incaps; + for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { + midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); + rv.append(incaps.szPname); + } + } +#endif + return rv; +} + +void Midi::unblockMidiDevice(QString name, bool output) { + if (output) { + for (unsigned long i = 0; i < midioutexclude.size(); i++) { + if (midioutexclude[i].toStdString().compare(name.toStdString()) == 0) { + midioutexclude.erase(midioutexclude.begin() + i); + break; + } + } + } else { + for (unsigned long i = 0; i < midiinexclude.size(); i++) { + if (midiinexclude[i].toStdString().compare(name.toStdString()) == 0) { + midiinexclude.erase(midiinexclude.begin() + i); + break; + } + } + } +} + +void Midi::blockMidiDevice(QString name, bool output) { + unblockMidiDevice(name, output); // make sure it's only in there once + if (output) { + midioutexclude.push_back(name); + } else { + midiinexclude.push_back(name); + } +} + +void Midi::thruModeEnable(bool enable) { + thruModeEnabled = enable; +} + diff --git a/libraries/midi/src/Midi.h b/libraries/midi/src/Midi.h new file mode 100644 index 0000000000..0ffa27986d --- /dev/null +++ b/libraries/midi/src/Midi.h @@ -0,0 +1,72 @@ +// +// Midi.h +// libraries/midi/src +// +// Created by Burt Sloane +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Midi_h +#define hifi_Midi_h + +#include +#include +#include + +#include +#include + +class Midi : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + void noteReceived(int status, int note, int velocity); // relay a note to Javascript + void sendNote(int status, int note, int vel); // relay a note to MIDI outputs + static void USBchanged(); + +private: + static std::vector midiinexclude; + static std::vector midioutexclude; + +private: + void MidiSetup(); + void MidiCleanup(); + +signals: + void midiNote(QVariantMap eventData); + +public slots: +/// play a note on all connected devices +/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc +/// @param {int} note: midi note number +/// @param {int} velocity: note velocity (0 means noteoff) +Q_INVOKABLE void playMidiNote(int status, int note, int velocity); + +/// turn off all notes on all connected devices +Q_INVOKABLE void allNotesOff(); + +/// clean up and re-discover attached devices +Q_INVOKABLE void resetDevices(); + +/// ask for a list of inputs/outputs +Q_INVOKABLE QStringList listMidiDevices(bool output); + +/// block an input/output by name +Q_INVOKABLE void blockMidiDevice(QString name, bool output); + +/// unblock an input/output by name +Q_INVOKABLE void unblockMidiDevice(QString name, bool output); + +/// repeat all incoming notes to all outputs (default disabled) +Q_INVOKABLE void thruModeEnable(bool enable); + +public: + Midi(); + virtual ~Midi(); +}; + +#endif // hifi_Midi_h 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/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 0c0004d132..87296b187f 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -16,6 +16,6 @@ if (NOT ANDROID) endif () -link_hifi_libraries(shared networking octree gpu procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image) +link_hifi_libraries(shared networking octree gpu procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image midi) # ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit include_hifi_library_headers(gl) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 3a876a0e0a..8b8eed02fd 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -77,6 +77,7 @@ #include +#include "../../midi/src/Midi.h" // FIXME why won't a simpler include work? #include "MIDIEvent.h" const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { @@ -667,6 +668,8 @@ void ScriptEngine::init() { registerGlobalObject("Audio", DependencyManager::get().data()); + registerGlobalObject("Midi", DependencyManager::get().data()); + registerGlobalObject("Entities", entityScriptingInterface.data()); registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 577c215c9e..3cb3cd3b63 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -25,8 +25,8 @@ class PathUtils : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - Q_PROPERTY(QString resources READ resourcesPath) - Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation) + Q_PROPERTY(QString resources READ resourcesPath CONSTANT) + Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT) public: static const QString& resourcesPath(); 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/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 34865ea058..2012ebbe30 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -329,6 +329,7 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); rootContext->setContextProperty("HFTabletWebEngineProfile", new HFTabletWebEngineProfile(rootContext)); + rootContext->setContextProperty("Paths", DependencyManager::get().data()); } QQmlEngine* acquireEngine(QQuickWindow* window) { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 6ff5e46cea..37a2bae0bf 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -825,7 +825,13 @@ TabletButtonProxy::~TabletButtonProxy() { void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { Q_ASSERT(QThread::currentThread() == qApp->thread()); + if (_qmlButton) { + QObject::disconnect(_qmlButton, &QQuickItem::destroyed, this, nullptr); + } _qmlButton = qmlButton; + if (_qmlButton) { + QObject::connect(_qmlButton, &QQuickItem::destroyed, this, [this] { _qmlButton = nullptr; }); + } } void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { 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; diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index f4e1bb76a2..12d0236cc2 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -13,7 +13,7 @@ if (APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() - link_hifi_libraries(shared gl gpu gpu-gl plugins ui ui-plugins display-plugins input-plugins) + link_hifi_libraries(shared gl gpu gpu-gl plugins ui ui-plugins display-plugins input-plugins midi) include_hifi_library_headers(octree) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 57cb8b2407..29b78f0f1b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -337,6 +337,8 @@ var toolBar = (function () { var SHAPE_TYPE_SIMPLE_HULL = 1; var SHAPE_TYPE_SIMPLE_COMPOUND = 2; var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; var DYNAMIC_DEFAULT = false; function handleNewModelDialogResult(result) { @@ -353,6 +355,12 @@ var toolBar = (function () { case SHAPE_TYPE_STATIC_MESH: shapeType = "static-mesh"; break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; default: shapeType = "none"; } @@ -449,6 +457,8 @@ var toolBar = (function () { SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; // tablet version of new-model dialog @@ -654,6 +664,7 @@ var toolBar = (function () { selectionDisplay.triggerMapping.enable(); print("starting tablet in landscape mode"); tablet.landscape = true; + entityIconOverlayManager.setIconsSelectable(null,false); // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); @@ -981,6 +992,7 @@ function mouseClickEvent(event) { } else { selectionManager.addEntity(foundEntity, true); } + if (wantDebug) { print("Model selected: " + foundEntity); } diff --git a/scripts/system/libraries/entityIconOverlayManager.js b/scripts/system/libraries/entityIconOverlayManager.js index f557a05f60..a374783b1f 100644 --- a/scripts/system/libraries/entityIconOverlayManager.js +++ b/scripts/system/libraries/entityIconOverlayManager.js @@ -1,6 +1,7 @@ /* globals EntityIconOverlayManager:true */ EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) { + var visible = false; // List of all created overlays @@ -69,6 +70,29 @@ EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) { } }; + + this.setIconsSelectable = function(arrayOfSelectedEntityIDs, isIconsSelectable) { + if (arrayOfSelectedEntityIDs === null) { + for (var id in entityOverlays) { + Overlays.editOverlay(entityOverlays[id], { + ignoreRayIntersection: isIconsSelectable + }); + } + } else { + for (var id in entityOverlays) { + if (arrayOfSelectedEntityIDs.indexOf(id) !== -1) { // in the entityOverlays array and selectable + Overlays.editOverlay(entityOverlays[id], { + ignoreRayIntersection: isIconsSelectable + }); + } else { + Overlays.editOverlay(entityOverlays[id], { + ignoreRayIntersection: !isIconsSelectable + }); + } + } + } + }; + // Allocate or get an unused overlay function getOverlay() { var overlay; @@ -114,6 +138,9 @@ EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) { overlayProperties[key] = customProperties[key]; } } + if(properties.type === 'ParticleEffect' || properties.type === 'Light'){ + overlayProperties.ignoreRayIntersection = true; + } Overlays.editOverlay(overlay, overlayProperties); } } diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 2d1853fae2..77b62913bf 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -4060,6 +4060,8 @@ SelectionDisplay = (function() { return false; } + entityIconOverlayManager.setIconsSelectable(selectionManager.selections,true); + // ignore ray intersection for our selection box and yaw/pitch/roll result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] ); if (result.intersects) { diff --git a/scripts/tutorials/createMidiSphere.js b/scripts/tutorials/createMidiSphere.js new file mode 100644 index 0000000000..56eae98585 --- /dev/null +++ b/scripts/tutorials/createMidiSphere.js @@ -0,0 +1,50 @@ +// +// Created by James B. Pollack @imgntn on April 18, 2016. +// Adapted by Burt +// 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 +// + +//var SCRIPT_URL = "file:///e:/hifi/scripts/tutorials/entity_scripts/midiSphere.js"; +var SCRIPT_URL = "http://hifi-files.s3-website-us-west-2.amazonaws.com/midiSphere.js"; +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(0.5, Quat.getForward(Camera.getOrientation()))); + +var BALL_GRAVITY = { + x: 0, + y: 0, + z: 0 +}; + +var BALL_DIMENSIONS = { + x: 0.4, + y: 0.4, + z: 0.4 +}; + + +var BALL_COLOR = { + red: 255, + green: 0, + blue: 0 +}; + +var midiSphereProperties = { + name: 'MIDI Sphere', + shapeType: 'sphere', + type: 'Sphere', + script: SCRIPT_URL, + color: BALL_COLOR, + dimensions: BALL_DIMENSIONS, + gravity: BALL_GRAVITY, + dynamic: false, + position: center, + collisionless: false, + ignoreForCollisions: true +}; + +var midiSphere = Entities.addEntity(midiSphereProperties); + +Script.stop(); diff --git a/scripts/tutorials/entity_scripts/midiSphere.js b/scripts/tutorials/entity_scripts/midiSphere.js new file mode 100644 index 0000000000..980f8f834e --- /dev/null +++ b/scripts/tutorials/entity_scripts/midiSphere.js @@ -0,0 +1,52 @@ +// midiSphere.js +// +// Script Type: Entity +// Created by James B. Pollack @imgntn on 9/21/2015 +// Adapted by Burt +// Copyright 2015 High Fidelity, Inc. +// +// This script listens to MIDI and makes the ball change color. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var _this; + + function MidiSphere() { + _this = this; + this.clicked = false; + return; + } + + MidiSphere.prototype = { + preload: function(entityID) { + this.entityID = entityID; + Midi.midiNote.connect(function(eventData) { + print("MidiSphere.noteReceived: "+JSON.stringify(eventData)); + Entities.editEntity(entityID, { color: { red: 2*eventData.note, green: 2*eventData.note, blue: 2*eventData.note} }); + }); + print("MidiSphere.preload"); + }, + unload: function(entityID) { + print("MidiSphere.unload"); + }, + + clickDownOnEntity: function(entityID, mouseEvent) { + print("MidiSphere.clickDownOnEntity"); + if (this.clicked) { + Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} }); + this.clicked = false; + Midi.playMidiNote(144, 64, 0); + } else { + Entities.editEntity(entityID, { color: { red: 255, green: 255, blue: 0} }); + this.clicked = true; + Midi.playMidiNote(144, 64, 100); + } + } + + }; + + // entity scripts should return a newly constructed object of our type + return new MidiSphere(); +}); \ No newline at end of file diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index b6989a57b7..77c8ab2177 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -12,8 +12,7 @@ setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural) - +link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural midi) package_libraries_for_deployment() diff --git a/unpublishedScripts/marketplace/dart/createDart.js b/unpublishedScripts/marketplace/dart/createDart.js new file mode 100644 index 0000000000..ff13a695a8 --- /dev/null +++ b/unpublishedScripts/marketplace/dart/createDart.js @@ -0,0 +1,91 @@ +"use strict"; +// +// createDart.js +// +// Created by MrRoboman on 17/05/04 +// Copyright 2017 High Fidelity, Inc. +// +// Creates five throwing darts. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var MODEL_URL = "https://hifi-content.s3.amazonaws.com/wadewatts/dart.fbx"; +var SCRIPT_URL = Script.resolvePath("./dart.js?v=" + Date.now()); +var START_POSITION = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 0.75)); +var START_ROTATION = MyAvatar.orientation +var SCALE_FACTOR = 1; + +var dart = { + type: "Model", + shapeType: "box", + name: "Dart", + description: "Throw it at something!", + script: SCRIPT_URL, + modelURL: MODEL_URL, + position: START_POSITION, + rotation: START_ROTATION, + lifetime: 300, + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + dimensions: { + x: 0.1, + y: 0.1, + z: 0.3 + }, + dynamic: true, + owningAvatarID: MyAvatar.sessionUUID, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + invertSolidWhileHeld: true, + ignoreIK: false + } + }) +}; + +var avatarUp = Quat.getUp(MyAvatar.orientation); +var platformPosition = Vec3.sum(START_POSITION, Vec3.multiply(avatarUp, -0.05)); +var platform = { + type: "Box", + name: "Dart Platform", + description: "Holds darts", + position: platformPosition, + rotation: START_ROTATION, + lifetime: 60, + dimensions: { + x: 0.15 * 5, + y: 0.01, + z: 0.3 + }, + color: { + red: 129, + green: 92, + blue: 11 + }, + owningAvatarID: MyAvatar.sessionUUID, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + invertSolidWhileHeld: true, + ignoreIK: false + } + }) +}; + + +Entities.addEntity(platform); + +var dartCount = 5; +var avatarRight = Quat.getRight(MyAvatar.orientation); +for (var i = 0; i < dartCount; i++) { + var j = i - Math.floor(dartCount / 2); + var position = Vec3.sum(START_POSITION, Vec3.multiply(avatarRight, 0.15 * j)); + dart.position = position; + Entities.addEntity(dart); +} + +Script.stop(); diff --git a/unpublishedScripts/marketplace/dart/dart.js b/unpublishedScripts/marketplace/dart/dart.js new file mode 100644 index 0000000000..9debe910e3 --- /dev/null +++ b/unpublishedScripts/marketplace/dart/dart.js @@ -0,0 +1,81 @@ +"use strict"; +// +// dart.js +// +// Created by MrRoboman on 17/05/13 +// Copyright 2017 High Fidelity, Inc. +// +// Simple throwing dart. Sticks to static objects. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +(function() { + var THROW_FACTOR = 3; + var DART_SOUND_URL = Script.resolvePath('https://hifi-content.s3.amazonaws.com/wadewatts/dart.wav?v=' + Date.now()); + + var Dart = function() {}; + + Dart.prototype = { + + preload: function(entityID) { + this.entityID = entityID; + this.actionID = null; + this.soundHasPlayed = true; + this.dartSound = SoundCache.getSound(DART_SOUND_URL); + }, + + playDartSound: function(sound) { + if (this.soundHasPlayed) { + return; + } + this.soundHasPlayed = true; + var position = Entities.getEntityProperties(this.entityID, 'position').position; + var audioProperties = { + volume: 0.15, + position: position + }; + Audio.playSound(this.dartSound, audioProperties); + }, + + releaseGrab: function() { + this.soundHasPlayed = false; + var velocity = Entities.getEntityProperties(this.entityID, 'velocity').velocity; + + var newVelocity = {}; + Object.keys(velocity).forEach(function(key) { + newVelocity[key] = velocity[key] * THROW_FACTOR; + }); + + Entities.editEntity(this.entityID, { + velocity: newVelocity + }); + + if (this.actionID) { + Entities.deleteAction(this.entityID, this.actionID); + } + this.actionID = Entities.addAction("travel-oriented", this.entityID, { + forward: { x: 0, y: 0, z: 1 }, + angularTimeScale: 0.1, + tag: "throwing dart", + ttl: 3600 + }); + }, + + collisionWithEntity: function(myID, otherID, collisionInfo) { + this.playDartSound(); + + Entities.editEntity(myID, { + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0} + }); + + if (this.actionID) { + Entities.deleteAction(myID, this.actionID); + this.actionID = null; + } + } + }; + + return new Dart(); +});