diff --git a/.github/workflows/master_build.yml b/.github/workflows/master_build.yml index 2caddef182..ee6ef88d0e 100644 --- a/.github/workflows/master_build.yml +++ b/.github/workflows/master_build.yml @@ -83,15 +83,15 @@ jobs: shell: bash run: | echo "${{ steps.buildenv1.outputs.symbols_archive }}" - echo "ARTIFACT_PATTERN=Vircadia-Alpha-*.$INSTALLER_EXT" >> $GITHUB_ENV + echo "ARTIFACT_PATTERN=Vircadia-*.$INSTALLER_EXT" >> $GITHUB_ENV # Build type variables echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV if [ "${{ matrix.build_type }}" = "full" ]; then echo "CLIENT_ONLY=FALSE" >> $GITHUB_ENV - echo "INSTALLER=Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV + echo "INSTALLER=Vircadia-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV else echo "CLIENT_ONLY=TRUE" >> $GITHUB_ENV - echo "INSTALLER=Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV + echo "INSTALLER=Vircadia-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV fi - name: Clear working directory if: startsWith(matrix.os, 'windows') diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml index 8a3dce98ec..9d6984b5b2 100644 --- a/.github/workflows/pr_build.yml +++ b/.github/workflows/pr_build.yml @@ -86,9 +86,9 @@ jobs: echo "${{ steps.buildenv1.outputs.symbols_archive }}" echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV if [[ "${{ matrix.build_type }}" != "android" ]]; then - echo "ARTIFACT_PATTERN=Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV + echo "ARTIFACT_PATTERN=Vircadia-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV # Build type variables - echo "INSTALLER=Vircadia-Alpha-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV + echo "INSTALLER=Vircadia-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV else echo "ARTIFACT_PATTERN=*.$INSTALLER_EXT" >> $GITHUB_ENV fi diff --git a/.gitignore b/.gitignore index 82c9535c5d..195def9544 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,4 @@ tools/unity-avatar-exporter server-console/package-lock.json vcpkg/ /tools/nitpick/compiledResources +qt/ \ No newline at end of file diff --git a/BUILD.md b/BUILD.md index 70fafc686d..6649cb1ef9 100644 --- a/BUILD.md +++ b/BUILD.md @@ -85,7 +85,7 @@ Windows: set HIFI_VCPKG_BASE=/path/to/directory -Where /path/to/directory is the path to a directory where you wish the build files to get stored. +Where `/path/to/directory` is the path to a directory where you wish the build files to get stored. #### Generating Build Files diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index fc73d7905e..c52f6a1d8f 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -1,8 +1,8 @@ # Build Android -*Last Updated on December 21, 2019* +*Last Updated on December 15, 2020* -Please read the [general build guide](BUILD.md) for information on building other platforms. Only Android specific instructions are found in this file. **Note that these instructions apply to building for Oculus Quest.** +Please read the [general build guide](BUILD.md) for information on building other platforms. Only Android specific instructions are found in this file. **Note that these instructions apply to building for the Oculus Quest 1.** ## Dependencies @@ -14,7 +14,7 @@ Please install the dependencies for your OS using the [Windows](BUILD_WIN.md), [ ### Android Studio -Download the [Android Studio](https://developer.android.com/studio/index.html) installer and run it. Once installed, at the welcome screen, click _Configure_ in the lower right corner and select _SDK Manager_. +Download the [Android Studio](https://developer.android.com/studio/index.html) installer and run it. Once installed, click _File_ then _Settings_, expand _Appearance & Behavior_ then expand _System Settings_ and select _Android SDK_. From the _SDK Platforms_ tab, select API levels 26 and 28. @@ -27,14 +27,16 @@ From the _SDK Tools_ tab, select the following * Android SDK Tools * NDK (even if you have the NDK installed separately) -Still in the _SDK Tools_ tab, click _Show Package Details_. Select CMake 3.6.4. Do this even if you have a separate CMake installation. +Still in the _SDK Tools_ tab, check off _Show Package Details_ at the bottom. Select CMake 3.6.4. Do this even if you have a separate CMake installation. Also, make sure the NDK installed version is 18 (or higher). -Also, make sure the NDK installed version is 18 (or higher). +Now go back to _File_ then _Project Structure_ then under _Project_ set the Android Gradle Plugin Version to `3.2.1` and Gradle Version to `4.10.1`. + +If Android Studio pops open the "Plugin Update Recommeded" dialog, do not click update, just click X on the top right to close. Later versions of the Gradle plugin have known issues with cz.malohlava. ## Environment ### Create a keystore in Android Studio -Follow the directions [here](https://developer.android.com/studio/publish/app-signing#generate-key) to create a keystore file. You can save it anywhere (preferably not in the `hifi` folder). +Follow the directions [here](https://developer.android.com/studio/publish/app-signing#generate-key) to create a keystore file. You can save it anywhere (preferably not in the `vircadia` folder). ### Set up machine specific Gradle properties @@ -62,7 +64,11 @@ Add these lines to `gradle.properties` SUPPRESS_INTERFACE SUPPRESS_FRAME_PLAYER -The above code to suppress modules is not necessary, but will speed up the build process. + +#### The Frame Player for both Android Phone and Oculus Quest is optional, so if you encounter problems with these during your build, you can skip them by adding these lines to `gradle.properties` + + SUPPRESS_FRAME_PLAYER + SUPPRESS_QUEST_FRAME_PLAYER ### Clone the repository @@ -74,12 +80,17 @@ The above code to suppress modules is not necessary, but will speed up the build * Open Android Studio * Choose _Open an existing Android Studio project_ -* Navigate to the `hifi` repository and choose the `android` folder and select _OK_ +* Navigate to the `vircadia` repository that had you cloned and choose the `android` folder and select _OK_ * Wait for Gradle to sync (this should take around 20 minutes the first time) -* From the _Build_ menu select _Make Project_ +* If a dialog pops open saying "Plugin Update Recommeded" dialog, do not click update, just click X on the top right to close. +* In the _Project_ window click on the project you wish to build (i.e. "questInterface") then click _Build_ in the top menu and choose _Make Module 'questInterface'_ +* By default this will build the "debug" apk, you can change this by opening the _Build Variants_ window along the left side and select other build types such as "release". +* Your newly build APK should reside in `vircadia\android\apps\questInterface\release` (if you chose release). ### Running a Module +You are free to use the "adb" command line or other development tools to install (sideload on Quest) your newly built APK, or you can follow the instructions below to load the APK via Android Studio. + * In the toolbar at the top of Android Studio, next to the green hammer icon, you should see a dropdown menu. * You may already see a configuration for the module you are trying to build. If so, select it. * Otherwise, select _Edit Configurations_. @@ -112,9 +123,17 @@ To view a more complete debug log, * Click the icon with the two overlapping squares in the upper left corner of the tab where the sync is running (hover text says _Toggle view_) * To change verbosity, click _File > Settings_. Under _Build, Execution, Deployment > Compiler_ you can add command-line flags, as per Gradle documentation +If you encounter CMake issues, try adding the following system environment variable: + +With your start menu, search for 'Edit the System Environment Variables' and open it. +* Click on 'Advanced' tab, then 'Environment Variables' +* Select 'New' under System variables +* Set "Variable name" to QT_CMAKE_PREFIX_PATH +* Set "Variable value" to the directory that your android build placed the CMake 3.6.4 library CMake directory (i.e. android\qt\lib\cmake). + Some things you can try if you want to do a clean build -* Delete the `build` and `.externalNativeBuild` folders from the folder for each module you're building (for example, `hifi/android/apps/interface`) +* Delete the `build` and `.externalNativeBuild` folders from the folder for each module you're building (for example, `vircadia/android/apps/interface`) * If you have set your `HIFI_VCPKG_ROOT` environment variable, delete the contents of that directory; otherwise, delete `AppData/Local/Temp/hifi` * In Android Studio, click _File > Invalidate Caches / Restart_ and select _Invalidate and Restart_ diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 4c4de5c225..91d629cc1e 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -1,40 +1,42 @@ # Build Windows -*Last Updated on 12 Dec 2020* +*Last Updated on 23 Feb 2021* This is a stand-alone guide for creating your first Vircadia build for Windows 64-bit. Note: We are now using Visual Studio 2019 and Qt 5.15.2. If you are upgrading from previous versions, do a clean uninstall of those versions before going through this guide. -Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. +**Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.** ### Step 1. Visual Studio & Python 3.x -If you don’t have Community or Professional edition of Visual Studio 2019, download [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/). If you have Visual Studio 2017, you need to download Visual Studio 2019. +If you don't have Community or Professional edition of Visual Studio 2019, download [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/). If you have Visual Studio 2017, you need to download Visual Studio 2019. -When selecting components, check "Desktop development with C++". On the right on the Summary toolbar, select the following components. +When selecting components, check "Desktop development with C++". +If you do not already have a Python 3.x development environment installed and want to install it with Visual Studio, check "Python Development". If you already have Visual Studio installed and need to add Python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes. #### Visual Studio 2019 +On the right on the Summary toolbar, select the following components. + +* MSVC v142 - VS 2019 C++ X64/x86 build tools * MSVC v141 - VS 2017 C++ x64/x86 build tools * MSVC v140 - VS 2015 C++ build tools (v14.00) -If you do not already have a Python 3.x development environment installed, also check "Python Development" in this screen. +### Step 1a. Alternate Python -If you already have Visual Studio installed and need to add Python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes. - -### Step 1a. Alternate Python - -If you do not wish to use the Python installation bundled with Visual Studio, you can download the installer from [here](https://www.python.org/downloads/). Ensure you get version 3.6.6 or higher. +If you do not wish to use the Python installation bundled with Visual Studio, you can download the installer from [here](https://www.python.org/downloads/). Ensure that you get version 3.6.6 or higher. ### Step 2. Python Dependencies -In a command-line that can access Python's pip you will need to run the following command: +In an administrator command-line that can access Python's pip you will need to run the following command: `pip install distro` +If you do not use an administrator command-line, you will get errors. + ### Step 3. Installing CMake Download and install the latest version of CMake 3.15. @@ -42,7 +44,11 @@ Download and install the latest version of CMake 3.15. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.15 Version page](https://cmake.org/files/v3.15/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted. -### Step 4. Create VCPKG environment variable +### Step 4. Node.JS and NPM + +Install version 10.15.0 LTS (or greater) of [Node.JS and NPM](). + +### Step 5. Create VCPKG environment variable In the next step, you will use CMake to build Vircadia. By default, the CMake process builds dependency files in Windows' `%TEMP%` directory, which is periodically cleared by the operating system. To prevent you from having to re-build the dependencies in the event that Windows clears that directory, we recommend that you create a `HIFI_VCPKG_BASE` environment variable linked to a directory somewhere on your machine. That directory will contain all dependency files until you manually remove them. To create this variable: @@ -61,7 +67,7 @@ To create this variable: * Set "Variable name" to `HIFI_VCPKG_BOOTSTRAP` * Set "Variable value" to `1` -### Step 5. Running CMake to Generate Build Files +### Step 6. Running CMake to Generate Build Files Run Command Prompt from Start and run the following commands: `cd "%VIRCADIA_DIR%"` @@ -73,7 +79,7 @@ Run `cmake .. -G "Visual Studio 16 2019" -A x64`. Where `%VIRCADIA_DIR%` is the directory for the Vircadia repository. -### Step 6. Making a Build +### Step 7. Making a Build Open `%VIRCADIA_DIR%\build\vircadia.sln` using Visual Studio. @@ -81,7 +87,7 @@ Change the Solution Configuration (menu ribbon under the menu bar, next to the g Run from the menu bar `Build > Build Solution`. -### Step 7. Testing Interface +### Step 8. Testing Interface Create another environment variable (see Step #3) * Set "Variable name": `_NO_DEBUG_HEAP` @@ -97,11 +103,11 @@ Note: You can also run Interface by launching it from command line or File Explo ## Troubleshooting -For any problems after Step #6, first try this: +For any problems after Step #7, first try this: * Delete your locally cloned copy of the Vircadia repository * Restart your computer * Redownload the [repository](https://github.com/vircadia/vircadia) -* Restart directions from Step #6 +* Restart directions from Step #7 #### CMake gives you the same error message repeatedly after the build fails diff --git a/CMakeLists.txt b/CMakeLists.txt index 424fbdc940..f111f482ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,12 @@ endif() file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" "${EXTERNAL_BUILD_ASSETS}") MESSAGE(STATUS "EXTERNAL_BUILD_ASSETS: ${EXTERNAL_BUILD_ASSETS}") +set(GLES_OPTION "$ENV{USE_GLES}") + +# Will affect VCPKG dependencies +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_env/USE_GLES.txt" "${GLES_OPTION}") +MESSAGE(STATUS "GLES_OPTION: ${GLES_OPTION}") + include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake") target_python() @@ -129,7 +135,6 @@ set(BUILD_TESTS_OPTION OFF) set(BUILD_MANUAL_TESTS_OPTION ${BUILD_TESTS_OPTION}) set(BUILD_TOOLS_OPTION ON) set(BUILD_INSTALLER_OPTION ON) -set(GLES_OPTION OFF) set(DISABLE_QML_OPTION OFF) set(DOWNLOAD_SERVERLESS_CONTENT_OPTION OFF) diff --git a/INSTALL.md b/INSTALLER.md similarity index 97% rename from INSTALL.md rename to INSTALLER.md index 994725ac28..84ee14eaaa 100644 --- a/INSTALL.md +++ b/INSTALLER.md @@ -60,8 +60,8 @@ To produce an executable installer on Windows, the following are required: 1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` 1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` -1. [Node.JS and NPM]() - 1. Install version 10.15.0 LTS +1. [Node.JS and NPM]() + 1. Install version 10.15.0 LTS (or greater) ##### Code Signing (optional) diff --git a/LICENSE b/LICENSE index 8dfe384174..d5ca6ae075 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright (c) 2013-2019, High Fidelity, Inc. -Copyright (c) 2019-2020, Vircadia contributors. +Copyright (c) 2019-2021, Vircadia contributors. All rights reserved. https://vircadia.com diff --git a/README.md b/README.md index 0368799c73..d260c76d0d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Vircadia +# Vircadia (Codename Athena) ### What is this? -Vircadia is a 3D social software project seeking to incrementally bring about a truly free and open metaverse, in desktop and XR. +Vircadia™ is a 3D social software project seeking to incrementally bring about a truly free and open metaverse, in desktop and XR. -### [Download](https://vircadia.com/download-vircadia/) +### [Website](https://vircadia.com/) | [Discord](https://discordapp.com/invite/Pvx2vke) | [Download](https://vircadia.com/download-vircadia/) ### Releases @@ -12,50 +12,46 @@ Vircadia is a 3D social software project seeking to incrementally bring about a ### How to build the Interface -[For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md) - -[For Mac](https://github.com/vircadia/vircadia/blob/master/BUILD_OSX.md) - -[For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md) - -[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder) +- [For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md) +- [For Mac](https://github.com/vircadia/vircadia/blob/master/BUILD_OSX.md) +- [For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md) +- [For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder) ### How to deploy a Server -[For Windows and Linux](https://vircadia.com/deploy-a-server/) +- [For Windows and Linux](https://vircadia.com/deploy-a-server/) ### How to build a Server -[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder) +- [For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md) +- [For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md) +- [For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder) ### How to generate an Installer -[For Windows](https://github.com/vircadia/vircadia/blob/master/INSTALL.md) +- [For Windows](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md) +- [For Mac](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md#os-x) +- [For Linux - AppImage - Vircadia Builder](https://github.com/vircadia/vircadia-builder/blob/master/README.md#building-appimages) -[For Linux - AppImage - Vircadia Builder](https://github.com/vircadia/vircadia-builder/blob/master/README.md#building-appimages) - -### Boot to Metaverse: The Goal +### Boot to Metaverse: [The Goal](https://vircadia.com/vision/) Having a place to experience adventure, a place to relax with calm breath, that's a world to live in. An engine to support infinite combinations and possibilities of worlds without censorship and interruption, that's a metaverse. Finding a way to make infinite realities our reality is the dream. ### Boot to Metaverse: The Technicals -Many developers have had personal combinations of High Fidelity from C++ modifications to different default scripts, all of which are lost to time as their fullest potential is never truly shared and propagated through the system. +Vircadia consists of many projects and codebases with its unifying structure's goal being a decentralized metaverse. -The goal of this project is to achieve the metaverse dream through shared contribution and building. Setting goals that are achievable yet meaningful is key to making proper forward progress on the technical front whilst maintaining morale. +- The Interface (Codename Athena) - You are here! +- The Server (Codename Athena) - You are also here! +- The UI Framework (Codename Nyx) - Codebase coming soon. +- [The Metaverse (Codename Iamus)](https://github.com/vircadia/Iamus/) +- [The Metaverse Dashboard (Codename Iamus)](https://github.com/vircadia/project-iamus-dashboard/) +- [The Launcher (Codename Pantheon)](https://github.com/vircadia/pantheon-launcher/) -### Why High Fidelity's Virtual Reality Platform? - -Because of all the options, it is the only starting point that is open-source, cross-platform, fully VR integrated + fully desktop integrated with an aim for quality visuals and performance. It also provides a foundation to build from including components like entity management, full body IK, etc. - -WebXR offers the open-source and decentralized aspect but does not have any of the full featured starting points such as avatars, IK, etc. which means that a lot of ground work will have to be laid to make something functional. Far more work will need to be done to create a truly seamless and extensive experience as well. - -Platforms like NeosVR or VRChat are not viable from go due to their fundamental closed-source and centralized nature. A metaverse to live in cannot have the keys handed over to any singular entity, if any at all. - -We need to do the best we can with what we've got and our best bet as open source developers is to not redesign the wheel if we can help it! +#### Child Projects +- [Vircadia Builder for Linux](https://github.com/vircadia/vircadia-builder/) +- [General Documentation](https://github.com/vircadia/vircadia-docs-sphinx/) ### Contribution -A special thanks to the contributors of Vircadia. - -[Contribution](CONTRIBUTING.md) +There are many contributors to Vircadia. Code writers, reviewers, testers, documentation writers, modelers, and general supporters of the project are all integral to its development and success towards its goals. Find out how you can [contribute](CONTRIBUTING.md)! diff --git a/android/build_android.sh b/android/build_android.sh index 9cf1b9e2ab..d3c79afdbe 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -17,7 +17,7 @@ fi ANDROID_APP=interface ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE} ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk -ANDROID_APK_NAME=Vircadia-Alpha-${ANDROID_APK_SUFFIX} +ANDROID_APK_NAME=Vircadia-${ANDROID_APK_SUFFIX} ./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 50eee258ab..adc7f5e3c5 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -33,6 +33,7 @@ #include #include +#include #include "AssignmentClientLogging.h" #include "AssignmentFactory.h" @@ -235,10 +236,13 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointergetDomainHandler().getIP().toString(); // start the deployed assignment - QThread* workerThread = new QThread; + QThread* workerThread = new QThread(); workerThread->setObjectName("ThreadedAssignment Worker"); - connect(workerThread, &QThread::started, _currentAssignment.data(), &ThreadedAssignment::run); + connect(workerThread, &QThread::started, _currentAssignment.data(), [this] { + setThreadName("ThreadedAssignment Worker"); + _currentAssignment->run(); + }); // Once the ThreadedAssignment says it is finished - we ask it to deleteLater // This is a queued connection so that it is put into the event loop to be processed by the worker diff --git a/assignment-client/src/audio/AudioMixerSlavePool.cpp b/assignment-client/src/audio/AudioMixerSlavePool.cpp index 5f6936cb2d..e8a2909acb 100644 --- a/assignment-client/src/audio/AudioMixerSlavePool.cpp +++ b/assignment-client/src/audio/AudioMixerSlavePool.cpp @@ -11,9 +11,13 @@ #include "AudioMixerSlavePool.h" +#include + #include #include +#include + void AudioMixerSlaveThread::run() { while (true) { wait(); @@ -157,6 +161,7 @@ void AudioMixerSlavePool::resize(int numThreads) { // start new slaves for (int i = 0; i < numThreads - _numThreads; ++i) { auto slave = new AudioMixerSlaveThread(*this, _workerSharedData); + QObject::connect(slave, &QThread::started, [] { setThreadName("AudioMixerSlaveThread"); }); slave->start(); _slaves.emplace_back(slave); } diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index f72ab0ac05..4ef220df29 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -34,6 +34,7 @@ #include #include +#include Q_LOGGING_CATEGORY(octree_server, "hifi.octree-server") @@ -1192,7 +1193,10 @@ void OctreeServer::domainSettingsRequestComplete() { _persistAsFileType); _persistManager->moveToThread(&_persistThread); connect(&_persistThread, &QThread::finished, _persistManager, &QObject::deleteLater); - connect(&_persistThread, &QThread::started, _persistManager, &OctreePersistThread::start); + connect(&_persistThread, &QThread::started, _persistManager, [this] { + setThreadName("OctreePersistThread"); + _persistManager->start(); + }); connect(_persistManager, &OctreePersistThread::loadCompleted, this, [this]() { beginRunning(); }); diff --git a/cmake/installer/installer-header.bmp b/cmake/installer/installer-header.bmp index de8448ed44..99862ffdb4 100644 Binary files a/cmake/installer/installer-header.bmp and b/cmake/installer/installer-header.bmp differ diff --git a/cmake/installer/uninstaller-header.bmp b/cmake/installer/uninstaller-header.bmp index de8448ed44..99862ffdb4 100644 Binary files a/cmake/installer/uninstaller-header.bmp and b/cmake/installer/uninstaller-header.bmp differ diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 0442df55cf..640cc1720f 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -31,7 +31,7 @@ macro(GENERATE_INSTALLERS) set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME}) set(CPACK_PACKAGE_VENDOR "Vircadia") set(CPACK_PACKAGE_VERSION ${BUILD_VERSION}) - set(CPACK_PACKAGE_FILE_NAME "Vircadia-Alpha${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}") + set(CPACK_PACKAGE_FILE_NAME "Vircadia${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}") set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME}) if (PR_BUILD) diff --git a/cmake/macros/OptionalWinExecutableSigning.cmake b/cmake/macros/OptionalWinExecutableSigning.cmake index cbefdaea8f..8f9fde672c 100644 --- a/cmake/macros/OptionalWinExecutableSigning.cmake +++ b/cmake/macros/OptionalWinExecutableSigning.cmake @@ -10,7 +10,7 @@ # macro(optional_win_executable_signing) - if (WIN32 AND PRODUCTION_BUILD) + if (WIN32 AND PRODUCTION_BUILD AND NOT BYPASS_SIGNING) if (DEFINED ENV{HF_PFX_FILE}) if (DEFINED ENV{HF_PFX_PASSPHRASE}) message(STATUS "Executable for ${TARGET_NAME} will be signed with SignTool.") diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 1e05cad109..9311594938 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -23,8 +23,10 @@ macro(SET_PACKAGING_PARAMETERS) set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV") set_from_env(RELEASE_NUMBER RELEASE_NUMBER "") + set_from_env(RELEASE_NAME RELEASE_NAME "") set_from_env(STABLE_BUILD STABLE_BUILD 0) set_from_env(INITIAL_STARTUP_LOCATION INITIAL_STARTUP_LOCATION "") + set_from_env(BYPASS_SIGNING BYPASS_SIGNING 0) message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}") diff --git a/cmake/macros/TargetOculusMobile.cmake b/cmake/macros/TargetOculusMobile.cmake index f5229845a9..34d5e33058 100644 --- a/cmake/macros/TargetOculusMobile.cmake +++ b/cmake/macros/TargetOculusMobile.cmake @@ -1,6 +1,6 @@ macro(target_oculus_mobile) - set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus_1.22/VrApi) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/ovr_sdk_mobile_1.37.0/VrApi) # Mobile SDK set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include) @@ -12,7 +12,7 @@ macro(target_oculus_mobile) target_link_libraries(${TARGET_NAME} ${OVR_MOBILE_LIBRARIES}) # Platform SDK - set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculusPlatform) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/ovr_platform_sdk_23.0.0) set(OVR_PLATFORM_INCLUDE_DIRS ${INSTALL_DIR}/Include) target_include_directories(${TARGET_NAME} PRIVATE ${OVR_PLATFORM_INCLUDE_DIRS}) set(OVR_PLATFORM_LIBRARIES ${INSTALL_DIR}/Android/libs/arm64-v8a/libovrplatformloader.so) diff --git a/cmake/ports/glad/portfile.cmake b/cmake/ports/glad/portfile.cmake index 54b1d91c89..6e3118b31e 100644 --- a/cmake/ports/glad/portfile.cmake +++ b/cmake/ports/glad/portfile.cmake @@ -2,13 +2,22 @@ include(vcpkg_common_functions) vcpkg_check_linkage(ONLY_STATIC_LIBRARY) file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS) +file(READ "${VCPKG_ROOT_DIR}/_env/USE_GLES.txt" USE_GLES) +# GitHub Actions Android builds fail with `FILENAME` set while desktop builds with GLES fail without a set `FILENAME`. if (ANDROID) vcpkg_download_distfile( SOURCE_ARCHIVE URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad32es.zip SHA512 2e02ac633eed8f2ba2adbf96ea85d08998f48dd2e9ec9a88ec3c25f48eaf1405371d258066327c783772fcb3793bdb82bd7375fdabb2ba5e2ce0835468b17f65 ) +elseif (USE_GLES) + vcpkg_download_distfile( + SOURCE_ARCHIVE + URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad32es.zip + SHA512 2e02ac633eed8f2ba2adbf96ea85d08998f48dd2e9ec9a88ec3c25f48eaf1405371d258066327c783772fcb3793bdb82bd7375fdabb2ba5e2ce0835468b17f65 + FILENAME glad32es.zip + ) else() # else Linux desktop vcpkg_download_distfile( diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in index 02f6a50919..7f3a63d4b4 100644 --- a/cmake/templates/BuildInfo.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 1/14/16. // Copyright 2015 High Fidelity, Inc. +// Copyright 2021 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -24,6 +25,7 @@ namespace BuildInfo { const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@"; const QString ORGANIZATION_DOMAIN = "vircadia.com"; const QString VERSION = "@BUILD_VERSION@"; + const QString RELEASE_NAME = "@RELEASE_NAME@"; const QString BUILD_NUMBER = "@BUILD_NUMBER@"; const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@"; const QString BUILD_TIME = "@BUILD_TIME@"; diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 752fb25d20..4c44ed1fbd 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -837,16 +837,24 @@ Function PostInstallOptionsPage !insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} + ; FIXME: Re-enable or permanently remove system tray "Console" for Interface-only installs. + ;${If} @SERVER_COMPONENT_CONDITIONAL@ + ; ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @SANDBOX_HF_SHORTCUT_NAME@" + ;${Else} + ; ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" + ;${EndIf} + ;Pop $DesktopConsoleCheckbox + ;IntOp $CurrentOffset $CurrentOffset + 15 + ; + ;; set the checkbox state depending on what is present in the registry + ;!insertmacro SetInstallOption $DesktopConsoleCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${If} @SERVER_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @SANDBOX_HF_SHORTCUT_NAME@" - ${Else} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" + Pop $DesktopConsoleCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $DesktopConsoleCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${EndIf} - - Pop $DesktopConsoleCheckbox - IntOp $CurrentOffset $CurrentOffset + 15 - ; set the checkbox state depending on what is present in the registry - !insertmacro SetInstallOption $DesktopConsoleCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${If} @CLIENT_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" @@ -861,31 +869,54 @@ Function PostInstallOptionsPage ${EndIf} ${EndIf} + ; FIXME: Re-enable or permanently remove system tray "Console" for Interface-only installs. + ;${If} @SERVER_COMPONENT_CONDITIONAL@ + ; ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @SANDBOX_HF_SHORTCUT_NAME@ after install" + ;${Else} + ; ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install" + ;${EndIf} + ;Pop $LaunchConsoleNowCheckbox + ; + ;; set the checkbox state depending on what is present in the registry + ;!insertmacro SetInstallOption $LaunchConsoleNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_UNCHECKED} + ;${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE + ;${IfNot} $substringResult == "" + ; ${NSD_SetState} $LaunchConsoleNowCheckbox ${BST_UNCHECKED} + ;${EndIf} + ;IntOp $CurrentOffset $CurrentOffset + 15 ${If} @SERVER_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @SANDBOX_HF_SHORTCUT_NAME@ after install" - ${Else} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install" - ${EndIf} - Pop $LaunchConsoleNowCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetInstallOption $LaunchConsoleNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_UNCHECKED} - ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE - ${IfNot} $substringResult == "" + Pop $LaunchConsoleNowCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $LaunchConsoleNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_UNCHECKED} + ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE + ${IfNot} $substringResult == "" ${NSD_SetState} $LaunchConsoleNowCheckbox ${BST_UNCHECKED} + ${EndIf} + IntOp $CurrentOffset $CurrentOffset + 15 ${EndIf} - IntOp $CurrentOffset $CurrentOffset + 30 - - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @SANDBOX_HF_SHORTCUT_NAME@ on startup" - ${Else} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" - ${EndIf} - Pop $ConsoleStartupCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - ; set the checkbox state depending on what is present in the registry - !insertmacro SetInstallOption $ConsoleStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_UNCHECKED} + ; FIXME: Re-enable or permanently remove system tray "Console" for Interface-only installs. + ;${If} @SERVER_COMPONENT_CONDITIONAL@ + ; ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @SANDBOX_HF_SHORTCUT_NAME@ on startup" + ;${Else} + ; ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" + ;${EndIf} + ;Pop $ConsoleStartupCheckbox + ;IntOp $CurrentOffset $CurrentOffset + 15 + ; + ;; set the checkbox state depending on what is present in the registry + ;!insertmacro SetInstallOption $ConsoleStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_UNCHECKED} + ${If} @SERVER_COMPONENT_CONDITIONAL@ + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @SANDBOX_HF_SHORTCUT_NAME@ on startup" + Pop $ConsoleStartupCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $ConsoleStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_UNCHECKED} + ${EndIf} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" Pop $CleanInstallCheckbox @@ -957,13 +988,29 @@ FunctionEnd Function ReadPostInstallOptions - ; check if the user asked for a desktop shortcut to console - ${NSD_GetState} $DesktopConsoleCheckbox $DesktopConsoleState - ${LogText} "Option: Start Desktop Console: $DesktopConsoleState" + ; FIXME: Re-enable or permanently remove system tray "Console" for Interface-only installs. + ;; check if the user asked for a desktop shortcut to console + ;${NSD_GetState} $DesktopConsoleCheckbox $DesktopConsoleState + ;${LogText} "Option: Start Desktop Console: $DesktopConsoleState" + ${If} @SERVER_COMPONENT_CONDITIONAL@ + ; check if the user asked for a desktop shortcut to console + ${NSD_GetState} $DesktopConsoleCheckbox $DesktopConsoleState + ${LogText} "Option: Start Desktop Console: $DesktopConsoleState" + ${Else} + StrCpy $DesktopConsoleState ${BST_UNCHECKED} + ${EndIf} - ; check if the user asked to have console launched every startup - ${NSD_GetState} $ConsoleStartupCheckbox $ConsoleStartupState - ${LogText} "Option: Start Desktop Console On Startup: $ConsoleStartupState" + ; FIXME: Re-enable or permanently remove system tray "Console" for Interface-only installs. + ;; check if the user asked to have console launched every startup + ;${NSD_GetState} $ConsoleStartupCheckbox $ConsoleStartupState + ;${LogText} "Option: Start Desktop Console On Startup: $ConsoleStartupState" + ${If} @SERVER_COMPONENT_CONDITIONAL@ + ; check if the user asked to have console launched every startup + ${NSD_GetState} $ConsoleStartupCheckbox $ConsoleStartupState + ${LogText} "Option: Start Desktop Console On Startup: $ConsoleStartupState" + ${Else} + StrCpy $ConsoleStartupState ${BST_UNCHECKED} + ${EndIf} ${If} @SERVER_COMPONENT_CONDITIONAL@ ${LogText} "Option: Install Server" @@ -1308,10 +1355,13 @@ Section "-Core installation" ${Else} ; handling for interface only console shortcut Delete "$SMPROGRAMS\$STARTMENU_FOLDER\@SANDBOX_HF_SHORTCUT_NAME@.lnk" - CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ - "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - ; Set appUserModelId - ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" + + ; FIXME: Re-enable or permanently remove system tray "Console" for Interface-only installs. + ;CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ + ; "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + ;; Set appUserModelId + ;ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" + ${EndIf} CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@" diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 42b15adefe..bb7acf344c 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -2013,6 +2013,23 @@ } ] }, + { + "name": "domain_server", + "label": "Setup Domain Server", + "restart": false, + "hidden": true, + "settings": [ + { + "name": "network_address", + "default": "" + }, + { + "name": "network_port", + "type": "int", + "default": 0 + } + ] + }, { "name": "installed_content", "label": "Installed Content", diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index bffd512890..bec8d19119 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -129,9 +129,10 @@ function getCurrentDomainIDType() { return DOMAIN_ID_TYPE_UNKNOWN; } if (DomainInfo !== null) { - if (DomainInfo.name !== undefined) { - return DOMAIN_ID_TYPE_TEMP; - } + // Disabled because detecting as temp domain... and we're not even using temp domains right now. + // if (DomainInfo.name !== undefined) { + // return DOMAIN_ID_TYPE_TEMP; + // } return DOMAIN_ID_TYPE_FULL; } return DOMAIN_ID_TYPE_UNKNOWN; @@ -504,7 +505,7 @@ function createDomainIDPrompt(callback) { swal({ title: 'Finish Registering Domain', type: 'input', - text: 'Enter a label for this machine.

This will help you identify which domain ID belongs to which machine.

This is a required step for registration.

', + text: 'Enter a label for this Domain Server.

This will help you identify which domain ID belongs to which server.

This is a required step for registration.

Acceptable characters are [A-Z][a-z0-9]+-_. #include +#include using namespace std::chrono; @@ -68,6 +69,9 @@ Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice") const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace"; +const QString PUBLIC_SOCKET_ADDRESS_KEY = "network_address"; +const QString PUBLIC_SOCKET_PORT_KEY = "network_port"; +const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; const int MIN_PORT = 1; const int MAX_PORT = 65535; @@ -827,9 +831,11 @@ void DomainServer::setupNodeListAndAssignments() { // set a custom packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); - _assetClientThread.setObjectName("AssetClient Thread"); + QString name = "AssetClient Thread"; + _assetClientThread.setObjectName(name); auto assetClient = DependencyManager::set(); assetClient->moveToThread(&_assetClientThread); + connect(&_assetClientThread, &QThread::started, [name] { setThreadName(name.toStdString()); }); _assetClientThread.start(); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); @@ -901,14 +907,13 @@ void DomainServer::setupAutomaticNetworking() { qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); + auto nodeList = DependencyManager::get(); + + // send any public socket changes to the data server so nodes can find us at our new IP + connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, + &DomainServer::performIPAddressPortUpdate); + if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { - - auto nodeList = DependencyManager::get(); - - // send any public socket changes to the data server so nodes can find us at our new IP - connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, - this, &DomainServer::performIPAddressUpdate); - // have the LNL enable public socket updating via STUN nodeList->startSTUNPublicSocketUpdate(); } @@ -1504,13 +1509,23 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { return socketObject; } -const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; +void DomainServer::performIPAddressPortUpdate(const HifiSockAddr& newPublicSockAddr) { + const QString& DOMAIN_SERVER_SETTINGS_KEY = "domain_server"; + const QString& publicSocketAddress = newPublicSockAddr.getAddress().toString(); + const int publicSocketPort = newPublicSockAddr.getPort(); -void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { - sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString()); + sendHeartbeatToMetaverse(publicSocketAddress, publicSocketPort); + + QJsonObject rootObject; + QJsonObject domainServerObject; + domainServerObject.insert(PUBLIC_SOCKET_ADDRESS_KEY, publicSocketAddress); + domainServerObject.insert(PUBLIC_SOCKET_PORT_KEY, publicSocketPort); + rootObject.insert(DOMAIN_SERVER_SETTINGS_KEY, domainServerObject); + QJsonDocument doc(rootObject); + _settingsManager.recurseJSONObjectAndOverwriteSettings(rootObject, DomainSettings); } -void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { +void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress, const int port) { // Setup the domain object to send to the data server QJsonObject domainObject; @@ -1520,10 +1535,20 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { static const QString PROTOCOL_VERSION_KEY = "protocol"; domainObject[PROTOCOL_VERSION_KEY] = protocolVersionsSignatureBase64(); - // add networking + static const QString NETWORK_ADDRESS_SETTINGS_KEY = "domain_server." + PUBLIC_SOCKET_ADDRESS_KEY; + const QString networkAddressFromSettings = _settingsManager.valueForKeyPath(NETWORK_ADDRESS_SETTINGS_KEY).toString(); if (!networkAddress.isEmpty()) { - static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; - domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress; + domainObject[PUBLIC_SOCKET_ADDRESS_KEY] = networkAddress; + } else if (!networkAddressFromSettings.isEmpty()) { + domainObject[PUBLIC_SOCKET_ADDRESS_KEY] = networkAddressFromSettings; + } + + static const QString PORT_SETTINGS_KEY = "domain_server." + PUBLIC_SOCKET_PORT_KEY; + const int portFromSettings = _settingsManager.valueForKeyPath(PORT_SETTINGS_KEY).toInt(); + if (port != NULL) { + domainObject[PUBLIC_SOCKET_PORT_KEY] = port; + } else if (portFromSettings != NULL) { + domainObject[PUBLIC_SOCKET_PORT_KEY] = portFromSettings; } static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 7a09141db6..24d26540c1 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -112,8 +112,8 @@ private slots: void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); - void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); - void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); } + void performIPAddressPortUpdate(const HifiSockAddr& newPublicSockAddr); + void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString(), int()); } void sendHeartbeatToIceServer(); void nodePingMonitor(); @@ -176,7 +176,7 @@ private: void setupAutomaticNetworking(); void setupICEHeartbeatForFullNetworking(); void setupHeartbeatToMetaverse(); - void sendHeartbeatToMetaverse(const QString& networkAddress); + void sendHeartbeatToMetaverse(const QString& networkAddress, const int port); void randomizeICEServerAddress(bool shouldTriggerHostLookup); diff --git a/hifi_android.py b/hifi_android.py index 06640390d4..07ea00d270 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -39,15 +39,15 @@ ANDROID_PACKAGES = { 'sharedLibFolder': 'lib', 'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'] }, - 'oculus_1.22': { - 'file': 'ovr_sdk_mobile_1.22.zip', - 'checksum': '1ac3c5b0521e5406f287f351015daff8', + 'ovr_sdk_mobile_1.37.0': { + 'file': 'ovr_sdk_mobile_1.37.0.zip', + 'checksum': '6040e1966f335a3e5015295154cd7383', 'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release', 'includeLibs': ['libvrapi.so'] }, - 'oculusPlatform': { - 'file': 'OVRPlatformSDK_v1.34.0.zip', - 'checksum': '16e4c5f39520f122bc49cb6d5bb88289', + 'ovr_platform_sdk_23.0.0': { + 'file': 'ovr_platform_sdk_23.0.0.zip', + 'checksum': '29d02b560f60d0fa7b8a64cd965dd55b', 'sharedLibFolder': 'Android/libs/arm64-v8a', 'includeLibs': ['libovrplatformloader.so'] }, diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py index 1b9976da6e..ebdfb6c972 100644 --- a/hifi_vcpkg.py +++ b/hifi_vcpkg.py @@ -90,8 +90,8 @@ endif() if 'Windows' == system: self.exe = os.path.join(self.path, 'vcpkg.exe') self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat'), '-disableMetrics' ] - self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-win32-client.zip%3FversionId=tSFzbw01VkkVFeRQ6YuAY4dro2HxJR9U' - self.vcpkgHash = 'a650db47a63ccdc9904b68ddd16af74772e7e78170b513ea8de5a3b47d032751a3b73dcc7526d88bcb500753ea3dd9880639ca842bb176e2bddb1710f9a58cd3' + self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-win32-client-20210122.zip' + self.vcpkgHash = '3df86b7d58c827bf08b3b7744f456f414b86a6d9bd58a507924103bc5a88f01ee495ce1f0fbf2f5b27f1ef6bfb1526e580ec13d3b9f87a89a462b3c50589fd6a' self.hostTriplet = 'x64-windows' if usePrebuilt: self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx" diff --git a/interface/resources/fonts/vircadia_glyphs.ttf b/interface/resources/fonts/vircadia_glyphs.ttf index 7d3fe9d913..ed89c2719a 100644 Binary files a/interface/resources/fonts/vircadia_glyphs.ttf and b/interface/resources/fonts/vircadia_glyphs.ttf differ diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index e9c8d119d6..3bee87d669 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -19,16 +19,17 @@ Item { property string url: "" property string scriptUrl: null property bool useBackground: true + property string userAgent: "" onUrlChanged: { - load(root.url, root.scriptUrl, root.useBackground); + load(root.url, root.scriptUrl, root.useBackground, root.userAgent); } onScriptUrlChanged: { if (root.item) { root.item.scriptUrl = root.scriptUrl; } else { - load(root.url, root.scriptUrl, root.useBackground); + load(root.url, root.scriptUrl, root.useBackground, root.userAgent); } } @@ -36,13 +37,21 @@ Item { if (root.item) { root.item.useBackground = root.useBackground; } else { - load(root.url, root.scriptUrl, root.useBackground); + load(root.url, root.scriptUrl, root.useBackground, root.userAgent); + } + } + + onUserAgentChanged: { + if (root.item) { + root.item.userAgent = root.userAgent; + } else { + load(root.url, root.scriptUrl, root.useBackground, root.userAgent); } } property var item: null - function load(url, scriptUrl, useBackground) { + function load(url, scriptUrl, useBackground, userAgent) { // Ensure we reset any existing item to "about:blank" to ensure web audio stops: DEV-2375 if (root.item != null) { root.item.url = "about:blank" @@ -54,11 +63,12 @@ Item { root.item.url = url root.item.scriptUrl = scriptUrl root.item.useBackground = useBackground + root.item.userAgent = userAgent }) } Component.onCompleted: { - load(root.url, root.scriptUrl, root.useBackground); + load(root.url, root.scriptUrl, root.useBackground, root.userAgent); } signal sendToScript(var message); diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index a0585ae053..931233622a 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -13,10 +13,13 @@ Item { property alias url: webViewCore.url property alias canGoBack: webViewCore.canGoBack property alias webViewCore: webViewCore - property alias webViewCoreProfile: webViewCore.profile + // FIXME - This was commented out to allow for manual setting of the userAgent. + // + // property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent property bool useBackground: webViewCore.useBackground + property string userAgent: webViewCore.profile.httpUserAgent property string userScriptUrl: "" property string urlTag: "noDownload=false"; @@ -34,6 +37,10 @@ Item { permissionPopupBackground.visible = false; } + onUserAgentChanged: { + webViewCore.profile.httpUserAgent = flick.userAgent; + } + StylesUIt.HifiConstants { id: hifi } @@ -74,7 +81,7 @@ Item { function onLoadingChanged(loadRequest) { if (WebEngineView.LoadStartedStatus === loadRequest.status) { - + webViewCore.profile.httpUserAgent = flick.userAgent; // Required to support clicking on "hifi://" links var url = loadRequest.url.toString(); url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag; @@ -101,7 +108,6 @@ Item { height: parent.height backgroundColor: (flick.useBackground) ? "white" : "transparent" - profile: HFWebEngineProfile; settings.pluginsEnabled: true settings.touchIconsEnabled: true settings.allowRunningInsecureContent: true @@ -136,8 +142,10 @@ Item { webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - if (webViewCoreUserAgent !== undefined) { - webViewCore.profile.httpUserAgent = webViewCoreUserAgent + if (flick.userAgent !== undefined) { + webViewCore.profile.httpUserAgent = flick.userAgent; + webViewCore.profile.offTheRecord = false; + webViewCore.profile.storageName = "qmlWebEngine"; } else { webViewCore.profile.httpUserAgent += " (VircadiaInterface)"; } diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 35f3182f98..8fdaf5feb4 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -11,12 +11,14 @@ Item { property alias url: webViewCore.url property alias canGoBack: webViewCore.canGoBack property alias webViewCore: webViewCore - property alias webViewCoreProfile: webViewCore.profile - property string webViewCoreUserAgent + // FIXME - This was commented out to allow for manual setting of the userAgent. + // + // property alias webViewCoreProfile: webViewCore.profile - property bool useBackground: webViewCore.useBackground + property alias useBackground: webViewCore.useBackground + property alias userAgent: webViewCore.userAgent property string userScriptUrl: "" - property string urlTag: "noDownload=false"; + property string urlTag: "noDownload=false" signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index 8aaafbd0fe..78292b8bcd 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -27,7 +27,11 @@ Item { } */ - property alias viewProfile: webroot.webViewCoreProfile + // FIXME - Reimplement profiles for... why? Was it so that new windows opened share the same profile? + // Are profiles written to by the webengine during the session? + // Removed in PR Feature/web entity user agent #988 + // + // property alias viewProfile: webroot.webViewCoreProfile FlickableWebViewCore { id: webroot diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 9cbbd48a22..0984f09141 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -25,7 +25,11 @@ Item { property bool isDesktop: false property alias url: web.url property alias webView: web.webViewCore - property alias profile: web.webViewCoreProfile + // FIXME - Reimplement profiles for... why? Was it so that new windows opened share the same profile? + // Are profiles written to by the webengine during the session? + // Removed in PR Feature/web entity user agent #988 + // + // property alias profile: web.webViewCoreProfile property bool remove: false property bool closeButtonVisible: true diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index b46c8c904d..a90824f3ad 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -24,6 +24,7 @@ Item { property alias flickable: webroot.interactive property alias blurOnCtrlShift: webroot.blurOnCtrlShift property alias useBackground: webroot.useBackground + property alias userAgent: webroot.userAgent function stop() { webroot.stop(); @@ -37,7 +38,11 @@ Item { } */ - property alias viewProfile: webroot.webViewCoreProfile + // FIXME - Reimplement profiles for... why? Was it so that new windows opened share the same profile? + // Are profiles written to by the webengine during the session? + // Removed in PR Feature/web entity user agent #988 + // + // property alias viewProfile: webroot.webViewCoreProfile FlickableWebViewCore { id: webroot diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 2cf07e32bf..0dd29b9e0f 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -244,7 +244,7 @@ Item { color: hifi.colors.darkGray; MouseArea { anchors.fill: parent - enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; + enabled: selected && pal.activeTab == "nearbyTab" && isPresent; hoverEnabled: enabled onClicked: { goToUserInDomain(thisNameCard.uuid); diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index f7a4061f45..d8f157c813 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -28,13 +28,18 @@ Rectangle { fillMode: Image.PreserveAspectFit source: "../../../images/vircadia-banner.svg" } - Item { height: 30; width: 1 } + Item { height: 25; width: 1 } Column { - id: buildColumm + id: buildColumn anchors.left: parent.left - anchors.leftMargin: 70 + anchors.leftMargin: 0 RalewayRegular { - text: "Build " + About.buildVersion + text: "Interface" + size: 16 + color: "white" + } + RalewayRegular { + text: "Build " + About.buildVersion + " " + About.releaseName size: 16 color: "white" } @@ -54,14 +59,25 @@ Rectangle { textFormat: Text.StyledText linkColor: "#00B4EF" color: "white" - text: "Vircadia Github." + text: "Website" size: 20 onLinkActivated: { - About.openUrl("https:/github.com/vircadia/vircadia"); + About.openUrl("https://vircadia.com"); } } - Item { height: 40; width: 1 } + RalewayRegular { + textFormat: Text.StyledText + linkColor: "#00B4EF" + color: "white" + text: "Source" + size: 20 + onLinkActivated: { + About.openUrl("https://github.com/vircadia/vircadia"); + } + + } + Item { height: 25; width: 1 } Row { spacing: 5 Image { @@ -117,7 +133,7 @@ Rectangle { Item { height: 20; width: 1 } RalewayRegular { color: "white" - text: "© 2019-2020 Vircadia contributors." + text: "© 2019 - 2021 Vircadia contributors." size: 14 } RalewayRegular { @@ -135,5 +151,23 @@ Rectangle { About.openUrl("http://www.apache.org/licenses/LICENSE-2.0.html"); } } + Item { height: 35; width: 1 } + RalewayRegular { + color: "white" + text: "In memoriam," + size: 14 + } + RalewayRegular { + color: "white" + text: "2012 - 2019 the High Fidelity virtual reality project." + size: 14 + } + Item { height: 5; width: 1 } + Image { + id: hifiLogo + width: 200; height: 50 + fillMode: Image.PreserveAspectFit + source: "../../../images/about-highfidelity.png" + } } } diff --git a/interface/src/AboutUtil.cpp b/interface/src/AboutUtil.cpp index b9bea2d85c..d2a00854b5 100644 --- a/interface/src/AboutUtil.cpp +++ b/interface/src/AboutUtil.cpp @@ -4,6 +4,7 @@ // // Created by Vlad Stelmahovsky on 15/5/2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2021 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -40,6 +41,10 @@ QString AboutUtil::getBuildVersion() const { return BuildInfo::VERSION; } +QString AboutUtil::getReleaseName() const { + return BuildInfo::RELEASE_NAME; +} + QString AboutUtil::getQtVersion() const { return qVersion(); } @@ -57,15 +62,15 @@ void AboutUtil::openUrl(const QString& url) const { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - auto offscreenUi = DependencyManager::get(); + auto offscreenUI = DependencyManager::get(); - if (tablet->getToolbarMode()) { - offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { + if (tablet->getToolbarMode() && offscreenUI) { + offscreenUI->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { newObject->setProperty("url", url); }); } else { - if (!hmd->getShouldShowTablet() && !qApp->isHMDMode()) { - offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { + if (!hmd->getShouldShowTablet() && !qApp->isHMDMode() && offscreenUI) { + offscreenUI->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { newObject->setProperty("url", url); }); } else { diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index 8cc76dad1e..f072ae8b4a 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -30,12 +30,14 @@ * Read-only. * @property {string} buildDate - The build date of Interface that is currently running. Read-only. * @property {string} buildVersion - The build version of Interface that is currently running. Read-only. + * @property {string} releaseName - The release codename of the version that Interface is currently running. Read-only. * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. * * @example Report information on the version of Interface currently running. * print("Interface platform: " + About.platform); * print("Interface build date: " + About.buildDate); * print("Interface version: " + About.buildVersion); + * print("Interface release name: " + About.releaseName); * print("Qt version: " + About.qtVersion); */ @@ -66,6 +68,7 @@ class AboutUtil : public QObject { Q_PROPERTY(QString platform READ getPlatformName CONSTANT) Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT) Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT) + Q_PROPERTY(QString releaseName READ getReleaseName CONSTANT) Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT) public: static AboutUtil* getInstance(); @@ -74,6 +77,7 @@ public: QString getPlatformName() const { return "Vircadia"; } QString getBuildDate() const; QString getBuildVersion() const; + QString getReleaseName() const; QString getQtVersion() const; public slots: diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc46d8591b..06ce6c3d6c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -254,6 +254,7 @@ #include "AboutUtil.h" #include "ExternalResource.h" +#include #if defined(Q_OS_WIN) #include @@ -1168,6 +1169,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (!DISABLE_WATCHDOG) { auto deadlockWatchdogThread = new DeadlockWatchdogThread(); deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId()); + connect(deadlockWatchdogThread, &QThread::started, [] { setThreadName("DeadlockWatchdogThread"); }); deadlockWatchdogThread->start(); // Pause the deadlock watchdog when we sleep, or it might @@ -2455,13 +2457,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); - EntityItem::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) { + BillboardModeHelpers::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, + BillboardMode billboardMode, const glm::vec3& frustumPos, bool rotate90x) { + const glm::quat ROTATE_90X = glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT); if (billboardMode == BillboardMode::YAW) { //rotate about vertical to face the camera glm::vec3 dPosition = frustumPos - position; // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); - return glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); + glm::quat result = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)) * rotation; + if (rotate90x) { + result *= ROTATE_90X; + } + return result; } else if (billboardMode == BillboardMode::FULL) { // use the referencial from the avatar, y isn't always up glm::vec3 avatarUP = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; @@ -2470,12 +2478,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // make sure s is not NaN for any component if (glm::length2(s) > 0.0f) { - return glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP))); + glm::quat result = glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP))) * rotation; + if (rotate90x) { + result *= ROTATE_90X; + } + return result; } } return rotation; }); - EntityItem::setPrimaryViewFrustumPositionOperator([this]() { + BillboardModeHelpers::setPrimaryViewFrustumPositionOperator([this]() { ViewFrustum viewFrustum; copyViewFrustum(viewFrustum); return viewFrustum.getPosition(); @@ -5196,6 +5208,7 @@ void getCpuUsage(vec3& systemAndUser) { void setupCpuMonitorThread() { initCpuUsage(); auto cpuMonitorThread = QThread::currentThread(); + setThreadName("CPU Monitor Thread"); QTimer* timer = new QTimer(); timer->setInterval(50); @@ -7180,6 +7193,10 @@ void Application::updateWindowTitle() const { QString buildVersion = " - Vircadia - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + " " + applicationVersion(); + + if (BuildInfo::RELEASE_NAME != "") { + buildVersion += " - " + BuildInfo::RELEASE_NAME; + } QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; diff --git a/interface/src/Bookmarks.cpp b/interface/src/Bookmarks.cpp index 9a8d8eb279..263723ebe0 100644 --- a/interface/src/Bookmarks.cpp +++ b/interface/src/Bookmarks.cpp @@ -61,7 +61,6 @@ void Bookmarks::deleteBookmark(const QString& bookmarkName) { void Bookmarks::addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark) { Menu* menubar = Menu::getInstance(); if (contains(bookmarkName)) { - auto offscreenUi = DependencyManager::get(); ModalDialogListener* dlg = OffscreenUi::asyncWarning("Duplicate Bookmark", "The bookmark name you entered already exists in your list.", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 19cc7eacaa..471645e342 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -509,7 +509,7 @@ Menu::Menu() { action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::MaterialProceduralShaders, 0, false); connect(action, &QAction::triggered, [action] { - MeshPartPayload::enableMaterialProceduralShaders = action->isChecked(); + ModelMeshPartPayload::enableMaterialProceduralShaders = action->isChecked(); }); { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 32e725388c..91d339a38d 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -767,6 +767,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic glm::vec3 rayDirectionInv = { rayDirection.x != 0.0f ? 1.0f / rayDirection.x : INFINITY, rayDirection.y != 0.0f ? 1.0f / rayDirection.y : INFINITY, rayDirection.z != 0.0f ? 1.0f / rayDirection.z : INFINITY }; + glm::vec3 viewFrustumPos = BillboardModeHelpers::getPrimaryViewFrustumPosition(); for (auto &hit : physicsResults) { auto avatarID = hit._intersectWithAvatar; @@ -842,7 +843,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic BoxFace subMeshFace = BoxFace::UNKNOWN_FACE; glm::vec3 subMeshSurfaceNormal; QVariantMap subMeshExtraInfo; - if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, subMeshDistance, subMeshFace, subMeshSurfaceNormal, subMeshExtraInfo, true, false)) { + if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, viewFrustumPos, subMeshDistance, + subMeshFace, subMeshSurfaceNormal, subMeshExtraInfo, true, false)) { rayAvatarResult._distance = subMeshDistance; rayAvatarResult._intersectionPoint = ray.origin + subMeshDistance * rayDirection; rayAvatarResult._intersectionNormal = subMeshSurfaceNormal; @@ -932,6 +934,7 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator); } + glm::vec3 viewFrustumPos = BillboardModeHelpers::getPrimaryViewFrustumPosition(); for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) { const SortedAvatar& sortedAvatar = *it; // We can exit once avatarCapsuleDistance > bestDistance @@ -944,7 +947,7 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector glm::vec3 surfaceNormal; QVariantMap extraInfo; SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel(); - if (avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal, extraInfo, true)) { + if (avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, viewFrustumPos, parabolicDistance, face, surfaceNormal, extraInfo, true)) { if (parabolicDistance < result.parabolicDistance) { result.intersects = true; result.avatarID = sortedAvatar.second->getID(); diff --git a/interface/src/avatar/AvatarPackager.cpp b/interface/src/avatar/AvatarPackager.cpp index 90def7ad43..d43b7d9575 100644 --- a/interface/src/avatar/AvatarPackager.cpp +++ b/interface/src/avatar/AvatarPackager.cpp @@ -58,7 +58,9 @@ bool AvatarPackager::open() { if (tablet->getToolbarMode()) { static const QUrl url{ "hifi/AvatarPackagerWindow.qml" }; - DependencyManager::get()->show(url, "AvatarPackager", packageModelDialogCreated); + if (auto offscreenUI = DependencyManager::get()) { + offscreenUI->show(url, "AvatarPackager", packageModelDialogCreated); + } return true; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b3231b906d..0f66f3bb41 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -96,10 +96,8 @@ const float CENTIMETERS_PER_METER = 100.0f; const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" }; -static const QString USER_RECENTER_MODEL_FORCE_SIT = QStringLiteral("ForceSit"); -static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStand"); -static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto"); -static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean"); +static const QString ALLOW_AVATAR_STANDING_ALWAYS = QStringLiteral("Always"); +static const QString ALLOW_AVATAR_STANDING_WHEN_USER_IS_STANDING = QStringLiteral("UserStanding"); const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha"; const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha"; @@ -111,30 +109,38 @@ const QString POINT_BLEND_LINEAR_ALPHA_NAME = "pointBlendAlpha"; const QString POINT_REF_JOINT_NAME = "RightShoulder"; const float POINT_ALPHA_BLENDING = 1.0f; -MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) { - if (str == USER_RECENTER_MODEL_FORCE_SIT) { - return MyAvatar::ForceSit; - } else if (str == USER_RECENTER_MODEL_FORCE_STAND) { - return MyAvatar::ForceStand; - } else if (str == USER_RECENTER_MODEL_DISABLE_HMD_LEAN) { - return MyAvatar::DisableHMDLean; - } else { - return MyAvatar::Auto; +const std::array(MyAvatar::AllowAvatarStandingPreference::Count)> + MyAvatar::allowAvatarStandingPreferenceStrings = { + QStringLiteral("WhenUserIsStanding"), + QStringLiteral("Always") +}; + +const std::array(MyAvatar::AllowAvatarLeaningPreference::Count)> + MyAvatar::allowAvatarLeaningPreferenceStrings = { + QStringLiteral("WhenUserIsStanding"), + QStringLiteral("Always"), + QStringLiteral("Never"), + QStringLiteral("AlwaysNoRecenter") +}; + +MyAvatar::AllowAvatarStandingPreference stringToAllowAvatarStandingPreference(const QString& str) { + for (uint stringIndex = 0; stringIndex < static_cast(MyAvatar::AllowAvatarStandingPreference::Count); stringIndex++) { + if (MyAvatar::allowAvatarStandingPreferenceStrings[stringIndex] == str) { + return static_cast(stringIndex); + } } + + return MyAvatar::AllowAvatarStandingPreference::Default; } -QString userRecenterModelToString(MyAvatar::SitStandModelType model) { - switch (model) { - case MyAvatar::ForceSit: - return USER_RECENTER_MODEL_FORCE_SIT; - case MyAvatar::ForceStand: - return USER_RECENTER_MODEL_FORCE_STAND; - case MyAvatar::DisableHMDLean: - return USER_RECENTER_MODEL_DISABLE_HMD_LEAN; - case MyAvatar::Auto: - default: - return USER_RECENTER_MODEL_AUTO; +MyAvatar::AllowAvatarLeaningPreference stringToAllowAvatarLeaningPreference(const QString& str) { + for (uint stringIndex = 0; stringIndex < static_cast(MyAvatar::AllowAvatarLeaningPreference::Count); stringIndex++) { + if (MyAvatar::allowAvatarLeaningPreferenceStrings[stringIndex] == str) { + return static_cast(stringIndex); + } } + + return MyAvatar::AllowAvatarLeaningPreference::Default; } static const QStringList TRIGGER_REACTION_NAMES = { @@ -166,7 +172,7 @@ MyAvatar::MyAvatar(QThread* thread) : _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE), _motionBehaviors(AVATAR_MOTION_DEFAULTS), - _characterController(std::shared_ptr(this)), + _characterController(std::shared_ptr(this), _follow._timeRemaining), _eyeContactTarget(LEFT_EYE), _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), @@ -214,8 +220,12 @@ MyAvatar::MyAvatar(QThread* thread) : _analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()), _analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()), _controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex), - _userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO) -{ + _allowAvatarStandingPreferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "allowAvatarStandingPreference", + allowAvatarStandingPreferenceStrings[static_cast( + AllowAvatarStandingPreference::Default)]), + _allowAvatarLeaningPreferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "allowAvatarLeaningPreference", + allowAvatarLeaningPreferenceStrings[static_cast( + AllowAvatarLeaningPreference::Default)]) { _clientTraitsHandler.reset(new ClientTraitsHandler(this)); // give the pointer to our head to inherited _headData variable from AvatarData @@ -493,8 +503,15 @@ void MyAvatar::centerBody() { return; } + centerBodyInternal(false); +} + +// forceFollowYPos (default false): true to force the body matrix to be affected by the HMD's +// vertical position, even if crouch recentering is disabled. +void MyAvatar::centerBodyInternal(const bool forceFollowYPos) { // derive the desired body orientation from the current hmd orientation, before the sensor reset. - auto newBodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + auto newBodySensorMatrix = + deriveBodyFromHMDSensor(forceFollowYPos); // Based on current cached HMD position/rotation.. // transform this body into world space auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; @@ -571,64 +588,63 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { } } +// Determine if the user is sitting or standing in the real world. void MyAvatar::updateSitStandState(float newHeightReading, float dt) { const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; const float SITTING_TIMEOUT = 4.0f; // 4 seconds const float STANDING_TIMEOUT = 0.3333f; // 1/3 second const float SITTING_UPPER_BOUND = 1.52f; - if (!getIsSitStandStateLocked()) { - if (!getIsAway() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { - if (getIsInSittingState()) { - if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateTimer += dt; - if (_sitStandStateTimer > STANDING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - setIsInSittingState(false); - } - } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we are mis labelled as sitting but we are standing in the real world this will - // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state - _sitStandStateTimer += dt; - if (_sitStandStateTimer > SITTING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - // here we stay in sit state but reset the average height - setIsInSittingState(true); - } - } else { - // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) - if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { - setIsInSittingState(false); - } else { - // tipping point is average height when sitting. - _tippingPoint = _averageUserHeightSensorSpace; - _sitStandStateTimer = 0.0f; - } + if (!getIsAway() && _isBodyPartTracked._head) { + if (getIsInSittingState()) { + if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > STANDING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(false); + } + } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we are mis labelled as sitting but we are standing in the real world this will + // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + // here we stay in sit state but reset the average height + setIsInSittingState(true); } } else { - // in the standing state - if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - _sitStandStateTimer += dt; - if (_sitStandStateTimer > SITTING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - setIsInSittingState(true); - } + // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) + if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { + setIsInSittingState(false); } else { - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); + // tipping point is average height when sitting. + _tippingPoint = _averageUserHeightSensorSpace; _sitStandStateTimer = 0.0f; } } } else { - //if you are away then reset the average and set state to standing. - _averageUserHeightSensorSpace = _userHeight.get(); - _tippingPoint = _userHeight.get(); - setIsInSittingState(false); + // in the standing state + if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(true); + } + } else { + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateTimer = 0.0f; + } } + } else { + //if you are away then reset the average and set state to standing. + _averageUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); + setIsInSittingState(false); } } @@ -636,17 +652,37 @@ void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders - const float COSINE_THIRTY_DEGREES = 0.866f; - const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds const float HEIGHT_FILTER_COEFFICIENT = 0.01f; float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); + // Determine which body parts are under direct control (tracked). + { + _isBodyPartTracked._leftHand = getControllerPoseInSensorFrame(controller::Action::LEFT_HAND).isValid(); + _isBodyPartTracked._rightHand = getControllerPoseInSensorFrame(controller::Action::RIGHT_HAND).isValid(); + _isBodyPartTracked._head = getControllerPoseInSensorFrame(controller::Action::HEAD).isValid(); + + // Check for either foot so that if one foot loses tracking, we don't break out of foot-tracking behaviour + // (in terms of avatar recentering for example). + _isBodyPartTracked._feet = _isBodyPartTracked._head && // Feet can't be tracked unless head is tracked. + (getControllerPoseInSensorFrame(controller::Action::LEFT_FOOT).isValid() || + getControllerPoseInSensorFrame(controller::Action::RIGHT_FOOT).isValid()); + + _isBodyPartTracked._hips = _isBodyPartTracked._feet && // Hips can't be tracked unless feet are tracked. + getControllerPoseInSensorFrame(controller::Action::HIPS).isValid(); + } + + // Recenter the body when foot tracking starts or ends. + if (_isBodyPartTracked._feet != _isBodyPartTracked._feetPreviousUpdate) { + centerBodyInternal(false); + _isBodyPartTracked._feetPreviousUpdate = _isBodyPartTracked._feet; + } + // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); - if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && !(spine2Index < 0)) { + if (_isBodyPartTracked._leftHand && _isBodyPartTracked._rightHand && !(spine2Index < 0)) { // use the spine for the azimuth origin. glm::quat spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index); @@ -682,29 +718,6 @@ void MyAvatar::update(float deltaTime) { setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); } - // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. - const float SQUAT_THRESHOLD = 0.05f; - glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); - glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); - glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); - if (glm::length(upSpine2) > 0.0f) { - upSpine2 = glm::normalize(upSpine2); - } - float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - - if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && - (angleSpine2 > COSINE_THIRTY_DEGREES) && - (getUserRecenterModel() != MyAvatar::SitStandModelType::ForceStand)) { - - _squatTimer += deltaTime; - if (_squatTimer > SQUATTY_TIMEOUT) { - _squatTimer = 0.0f; - _follow._squatDetected = true; - } - } else { - _squatTimer = 0.0f; - } - // put update sit stand state counts here updateSitStandState(newHeightReading.getTranslation().y, deltaTime); @@ -832,7 +845,7 @@ void MyAvatar::recalculateChildCauterization() const { _cauterizationNeedsUpdate = true; } -bool MyAvatar::isFollowActive(FollowHelper::FollowType followType) const { +bool MyAvatar::isFollowActive(CharacterController::FollowType followType) const { return _follow.isActive(followType); } @@ -1277,6 +1290,10 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { void MyAvatar::saveData() { _dominantHandSetting.set(getDominantHand()); + _allowAvatarStandingPreferenceSetting.set( + allowAvatarStandingPreferenceStrings[static_cast(getAllowAvatarStandingPreference())]); + _allowAvatarLeaningPreferenceSetting.set( + allowAvatarLeaningPreferenceStrings[static_cast(getAllowAvatarLeaningPreference())]); _strafeEnabledSetting.set(getStrafeEnabled()); _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); @@ -1311,7 +1328,10 @@ void MyAvatar::saveData() { _analogWalkSpeedSetting.set(getAnalogWalkSpeed()); _analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed()); _controlSchemeIndexSetting.set(getControlSchemeIndex()); - _userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel())); + _allowAvatarStandingPreferenceSetting.set( + allowAvatarStandingPreferenceStrings[static_cast(getAllowAvatarStandingPreference())]); + _allowAvatarLeaningPreferenceSetting.set( + allowAvatarLeaningPreferenceStrings[static_cast(getAllowAvatarLeaningPreference())]); auto hmdInterface = DependencyManager::get(); saveAvatarEntityDataToSettings(); @@ -2004,7 +2024,10 @@ void MyAvatar::loadData() { setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setTargetScale(_scaleSetting.get()); - setUserRecenterModel(stringToUserRecenterModel(_userRecenterModelSetting.get(USER_RECENTER_MODEL_AUTO))); + setAllowAvatarStandingPreference(stringToAllowAvatarStandingPreference(_allowAvatarStandingPreferenceSetting.get( + allowAvatarStandingPreferenceStrings[static_cast(AllowAvatarStandingPreference::Default)]))); + setAllowAvatarLeaningPreference(stringToAllowAvatarLeaningPreference(_allowAvatarLeaningPreferenceSetting.get( + allowAvatarLeaningPreferenceStrings[static_cast(AllowAvatarLeaningPreference::Default)]))); setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); @@ -2666,15 +2689,8 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act } } -glm::quat MyAvatar::getOffHandRotation() const { - auto hand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; - auto pose = getControllerPoseInAvatarFrame(hand); - return pose.rotation; -} - void MyAvatar::updateMotors() { _characterController.clearMotors(); - glm::quat motorRotation; const float FLYING_MOTOR_TIMESCALE = 0.05f; const float WALKING_MOTOR_TIMESCALE = 0.2f; @@ -2693,35 +2709,17 @@ void MyAvatar::updateMotors() { } if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { - if (_characterController.getState() == CharacterController::State::Hover || - _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { - CameraMode mode = qApp->getCamera().getMode(); - if (!qApp->isHMDMode() && (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE)) { - motorRotation = getLookAtRotation(); - } else { - motorRotation = getMyHead()->getHeadOrientation(); - } - } else { - // non-hovering = walking: follow camera twist about vertical but not lift - // we decompose camera's rotation and store the twist part in motorRotation - // however, we need to perform the decomposition in the avatar-frame - // using the local UP axis and then transform back into world-frame - glm::quat orientation = getWorldOrientation(); - glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame - glm::quat liftRotation; - swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); - motorRotation = orientation * motorRotation; - } - if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); + _characterController.addMotor(_actionMotorVelocity, Quaternions::IDENTITY, horizontalMotorTimescale, + verticalMotorTimescale); } else { // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts - _characterController.addMotor(_actionMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, Quaternions::IDENTITY, INVALID_MOTOR_TIMESCALE); } } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { + glm::quat motorRotation; if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { @@ -2759,8 +2757,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setScaleFactor(getSensorToWorldScale()); _characterController.setPositionAndOrientation(getWorldPosition(), getWorldOrientation()); - auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); - if (headPose.isValid()) { + if (_isBodyPartTracked._head) { _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); @@ -3757,15 +3754,15 @@ glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 rig if (length > EPSILON) { direction /= length; } - return getSensorToWorldScale() * direction * getSprintSpeed() * _walkSpeedScalar; + return direction * getSprintSpeed() * _walkSpeedScalar; } else { return Vectors::ZERO; } case LocomotionControlsMode::CONTROLS_ANALOG: case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: if (zSpeed || xSpeed) { - glm::vec3 scaledForward = getSensorToWorldScale() * calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward; - glm::vec3 scaledRight = getSensorToWorldScale() * calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right; + glm::vec3 scaledForward = calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward; + glm::vec3 scaledRight = calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right; direction = scaledForward + scaledRight; return direction; } else { @@ -3793,54 +3790,114 @@ glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 rig } } -glm::vec3 MyAvatar::calculateScaledDirection(){ +// Calculate the world-space motor velocity for the avatar. +glm::vec3 MyAvatar::calculateScaledDirection() { CharacterController::State state = _characterController.getState(); // compute action input // Determine if we're head or controller relative... glm::vec3 forward, right; - if (qApp->isHMDMode()) { - auto handRotation = getOffHandRotation(); - glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); - glm::vec3 controllerRight(0.0f, 0.0f, (getDominantHand() == DOMINANT_RIGHT_HAND ? 1.0f : -1.0f)); - glm::vec3 transform; - switch (getMovementReference()) { - case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE: - forward = (handRotation * controllerForward); - right = (handRotation * controllerRight); - break; - case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED: - forward = (handRotation * controllerForward); - transform = forward - (glm::dot(forward, Vectors::UNIT_Y) * Vectors::UNIT_Y); - if (glm::length(transform) > EPSILON) { - forward = glm::normalize(transform); - } else { - forward = Vectors::ZERO; - } - right = (handRotation * controllerRight); - transform = right - (glm::dot(right, Vectors::UNIT_Y) * Vectors::UNIT_Y); - if (glm::length(transform) > EPSILON) { - right = glm::normalize(transform); - } else { - right = Vectors::ZERO; - } - break; - case LocomotionRelativeMovementMode::MOVEMENT_HMD_RELATIVE: - default: - forward = IDENTITY_FORWARD; - right = IDENTITY_RIGHT; + int movementReference = getMovementReference(); + CameraMode cameraMode = qApp->getCamera().getMode(); + + bool vectorsAreInAvatarFrame = true; + bool removeLocalYComponent = false; + + bool HMDHandRelativeMovement = + qApp->isHMDMode() && (movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE || + movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED); + + bool desktopLookatOrSelfieMode = + !qApp->isHMDMode() && (cameraMode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || cameraMode == CAMERA_MODE_LOOK_AT || + cameraMode == CAMERA_MODE_SELFIE); + + bool hoveringOrCollisionless = _characterController.getState() == CharacterController::State::Hover || + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS; + + if (HMDHandRelativeMovement) { + controller::Action directionHand = + (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; + controller::Pose handPoseInAvatarFrame = getControllerPoseInAvatarFrame(directionHand); + + if (handPoseInAvatarFrame.isValid()) { + glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); + glm::vec3 controllerRight(0.0f, 0.0f, (directionHand == controller::Action::LEFT_HAND) ? 1.0f : -1.0f); + + forward = (handPoseInAvatarFrame.rotation * controllerForward); + right = (handPoseInAvatarFrame.rotation * controllerRight); + + removeLocalYComponent = (movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED); + } + } else { // MOVEMENT_HMD_RELATIVE or desktop mode + if (qApp->isHMDMode()) { + forward = -IDENTITY_FORWARD; + right = -IDENTITY_RIGHT; + } else { + forward = IDENTITY_FORWARD; + right = IDENTITY_RIGHT; + } + + glm::quat rotation = Quaternions::IDENTITY; + + if (hoveringOrCollisionless && desktopLookatOrSelfieMode) { + rotation = getLookAtRotation(); + removeLocalYComponent = false; + vectorsAreInAvatarFrame = false; + } else { + controller::Pose headPoseLocal = getControllerPoseInAvatarFrame(controller::Action::HEAD); + if (headPoseLocal.isValid()) { + rotation = headPoseLocal.rotation; + } + removeLocalYComponent = !hoveringOrCollisionless; + } + + forward = rotation * forward; + right = rotation * right; + } + + if (removeLocalYComponent) { + assert(vectorsAreInAvatarFrame); + + auto removeYAndNormalize = [](glm::vec3& vector) { + vector.y = 0.f; + // Normalize if the remaining components are large enough to get a reliable direction. + float length = glm::length(vector); + const float MIN_LENGTH_FOR_NORMALIZE = 0.061f; // sin(3.5 degrees) + if (length > MIN_LENGTH_FOR_NORMALIZE) { + vector /= length; + } else { + vector = Vectors::ZERO; + } + }; + + removeYAndNormalize(forward); + removeYAndNormalize(right); + } + + // In HMD, we combine the head pitch into the flying direction even when using hand-relative movement. + // Todo: Option to ignore head pitch in hand-relative flying (MOVEMENT_HAND_RELATIVE_LEVELED would then act like MOVEMENT_HAND_RELATIVE when flying). + if (HMDHandRelativeMovement && hoveringOrCollisionless) { + controller::Pose headPoseLocal = getControllerPoseInAvatarFrame(controller::Action::HEAD); + + if (headPoseLocal.isValid()) { + glm::quat headLocalPitchRotation; + glm::quat headLocalYawRotation_unused; + swingTwistDecomposition(headPoseLocal.rotation, Vectors::UP, headLocalPitchRotation, headLocalYawRotation_unused); + + forward = headLocalPitchRotation * forward; + right = headLocalPitchRotation * right; } - } else { - forward = IDENTITY_FORWARD; - right = IDENTITY_RIGHT; } glm::vec3 direction = scaleMotorSpeed(forward, right); - if (state == CharacterController::State::Hover || - _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { - glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; + if (vectorsAreInAvatarFrame) { + direction = getWorldOrientation() * direction; + } + + if (hoveringOrCollisionless) { + glm::vec3 up = getDriveKey(TRANSLATE_Y) * IDENTITY_UP; direction += up; } @@ -4562,7 +4619,7 @@ bool MyAvatar::getFlyingHMDPref() { } // Public interface for targetscale -float MyAvatar::getAvatarScale() { +float MyAvatar::getAvatarScale() const { return getTargetScale(); } @@ -4723,8 +4780,11 @@ void MyAvatar::triggerRotationRecenter() { _follow.setForceActivateRotation(true); } +// Derive the sensor-space matrix for the body, based on the pose of the HMD and hips tracker. // old school meat hook style -glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { +// forceFollowYPos (default false): true to force the body matrix to be affected by the HMD's +// vertical position, even if crouch recentering is disabled. +glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { glm::vec3 headPosition(0.0f, _userHeight.get(), 0.0f); glm::quat headOrientation; auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -4758,10 +4818,33 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; - float invSensorToWorldScale = getUserEyeHeight() / getEyeHeight(); - glm::vec3 bodyPos = headPosition + invSensorToWorldScale * (headToNeck + neckToRoot); + float worldToSensorScale = getUserEyeHeight() / getEyeHeight(); + glm::vec3 bodyPos = headPosition + worldToSensorScale * (headToNeck + neckToRoot); + glm::quat bodyQuat; - return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); + controller::Pose hipsControllerPose = getControllerPoseInSensorFrame(controller::Action::HIPS); + if (hipsControllerPose.isValid()) { + glm::quat hipsOrientation = hipsControllerPose.rotation * Quaternions::Y_180; + glm::quat hipsOrientationYawOnly = cancelOutRollAndPitch(hipsOrientation); + + glm::vec3 hipsPos = hipsControllerPose.getTranslation(); + bodyPos.x = hipsPos.x; + bodyPos.z = hipsPos.z; + + bodyQuat = hipsOrientationYawOnly; + } else { + bodyQuat = headOrientationYawOnly; + } + + if (!forceFollowYPos && !getHMDCrouchRecenterEnabled()) { + // Set the body's vertical position as if it were standing in its T-pose. + float rigToUserScale = getUserEyeHeight() / getUnscaledEyeHeight(); + bodyPos.y = rigToUserScale * rig.getUnscaledHipsHeight(); + } + + glm::mat4 bodyMat = createMatFromQuatAndPos(bodyQuat, bodyPos); + + return bodyMat; } glm::mat4 MyAvatar::getSpine2RotationRigSpace() const { @@ -4893,11 +4976,11 @@ glm::vec3 MyAvatar::computeCounterBalance() { glm::vec3 currentCg = (1.0f / totalMass) * sumOfMoments; currentCg.y = 0.0f; // dampening the center of gravity, in effect, limits the value to the perimeter of the base of support - float baseScale = 1.0f; + float baseAndAvatarScale = getAvatarScale(); if (getUserEyeHeight() > 0.0f) { - baseScale = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; + baseAndAvatarScale *= getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; } - glm::vec3 desiredCg = dampenCgMovement(currentCg, baseScale); + glm::vec3 desiredCg = dampenCgMovement(currentCg, baseAndAvatarScale); // compute hips position to maintain desiredCg glm::vec3 counterBalancedForHead = (totalMass + DEFAULT_AVATAR_HIPS_MASS) * desiredCg; @@ -4916,9 +4999,10 @@ glm::vec3 MyAvatar::computeCounterBalance() { // this is to be sure that the feet don't lift off the floor. // add 5 centimeters to allow for going up on the toes. - if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { + float maxCounterBalancedCGY = (tposeHips.y + 0.05f) * baseAndAvatarScale; + if (counterBalancedCg.y > maxCounterBalancedCGY) { // if the height is higher than default hips, clamp to default hips - counterBalancedCg.y = tposeHips.y + 0.05f; + counterBalancedCg.y = maxCounterBalancedCGY; } return counterBalancedCg; } @@ -4949,7 +5033,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma float clampBack = DEFAULT_AVATAR_SUPPORT_BASE_BACK * baseOfSupportScale; float clampLeft = DEFAULT_AVATAR_SUPPORT_BASE_LEFT * baseOfSupportScale; float clampRight = DEFAULT_AVATAR_SUPPORT_BASE_RIGHT * baseOfSupportScale; - float floor = footLocal + 0.05f; + float floor = footLocal; // transform the base of support corners to world space glm::vec3 frontRight = transformPoint(avatarToWorld, { clampRight, floor, clampFront }); @@ -4980,7 +5064,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() { glm::mat4 avatarHeadMat = glm::inverse(avatarToWorldMat) * sensorToWorldMat * sensorHeadMat; if (_enableDebugDrawBaseOfSupport) { - float scaleBaseOfSupport = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; + float scaleBaseOfSupport = (getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT) * getAvatarScale(); glm::vec3 rightFootPositionLocal = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot")); drawBaseOfSupport(scaleBaseOfSupport, rightFootPositionLocal.y, avatarToWorldMat); } @@ -4996,26 +5080,16 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() { return worldToSensorMat * avatarToWorldMat * avatarHipsMat; } -static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) { - return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0); -} - -static bool withinBaseOfSupport(const controller::Pose& head) { - float userScale = 1.0f; - - glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); - glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); - glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); - glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); - - bool isWithinSupport = false; - if (head.isValid()) { - bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation()); - bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation()); - bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) && - isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation())); - isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase); +static bool withinBaseOfSupport(const controller::Pose& head, const float avatarScale) { + if (!head.isValid()) { + return false; } + + vec3 headPosScaled = head.getTranslation() / avatarScale; + bool isWithinSupport = (headPosScaled.x > -DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD) && + (headPosScaled.x < DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD) && + (headPosScaled.z > -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD) && + (headPosScaled.z < DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); return isWithinSupport; } @@ -5031,10 +5105,10 @@ static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { return isBelowThreshold; } -static bool isWithinThresholdHeightMode(const controller::Pose& head, const float& newMode, const float& scale) { +static bool isWithinThresholdHeightMode(const controller::Pose& head, const float newMode, const float avatarScale) { bool isWithinThreshold = true; if (head.isValid()) { - isWithinThreshold = (head.getTranslation().y - newMode) > (DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD * scale); + isWithinThreshold = head.getTranslation().y > ((DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD + newMode) * avatarScale); } return isWithinThreshold; } @@ -5058,7 +5132,7 @@ float MyAvatar::computeStandingHeightMode(const controller::Pose& head) { modeInMeters = ((float)mode) / CENTIMETERS_PER_METER; if (!(modeInMeters > getCurrentStandingHeight())) { // if not greater check for a reset - if (getResetMode() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { + if (getResetMode() && _isBodyPartTracked._head) { setResetMode(false); float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER); modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER); @@ -5115,12 +5189,12 @@ static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, (rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD)); } -static bool headVelocityGreaterThanThreshold(const controller::Pose& head) { +static bool headVelocityGreaterThanThreshold(const controller::Pose& head, const float avatarScale) { float headVelocityMagnitude = 0.0f; if (head.isValid()) { headVelocityMagnitude = glm::length(head.getVelocity()); } - return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD; + return headVelocityMagnitude > (DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD * avatarScale); } glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) { @@ -5144,6 +5218,7 @@ float MyAvatar::getUserHeight() const { void MyAvatar::setUserHeight(float value) { _userHeight.set(value); + centerBodyInternal(false); float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); emit sensorToWorldScaleChanged(sensorToWorldScale); @@ -5159,16 +5234,45 @@ bool MyAvatar::getIsInWalkingState() const { return _isInWalkingState; } +// Determine if the user is sitting in the real world. bool MyAvatar::getIsInSittingState() const { return _isInSittingState.get(); } +// Deprecated, will be removed. MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { - return _userRecenterModel.get(); + qCDebug(interfaceapp) + << "MyAvatar.getUserRecenterModel is deprecated and will be removed."; + + // The legacy SitStandModelType corresponding to each AllowAvatarLeaningPreference. + std::array(AllowAvatarLeaningPreference::Count)> legacySitStandModels = { + SitStandModelType::Auto, // AllowAvatarLeaningPreference::WhenUserIsStanding + SitStandModelType::ForceStand, // AllowAvatarLeaningPreference::Always + SitStandModelType::ForceSit, // AllowAvatarLeaningPreference::Never + SitStandModelType::DisableHMDLean // AllowAvatarLeaningPreference::AlwaysNoRecenter + }; + + return legacySitStandModels[static_cast(_allowAvatarLeaningPreference.get())]; } +// Deprecated, will be removed. bool MyAvatar::getIsSitStandStateLocked() const { - return _lockSitStandState.get(); + qCDebug(interfaceapp) << "MyAvatar.getIsSitStandStateLocked is deprecated and will be removed."; + + // In the old code, the record of the user's sit/stand state was locked except when using + // SitStandModelType::Auto or SitStandModelType::DisableHMDLean. + return (_allowAvatarStandingPreference.get() != AllowAvatarStandingPreference::WhenUserIsStanding) && + (_allowAvatarLeaningPreference.get() != AllowAvatarLeaningPreference::AlwaysNoRecenter); +} + +// Get the user preference of when MyAvatar may stand. +MyAvatar::AllowAvatarStandingPreference MyAvatar::getAllowAvatarStandingPreference() const { + return _allowAvatarStandingPreference.get(); +} + +// Get the user preference of when MyAvatar may lean. +MyAvatar::AllowAvatarLeaningPreference MyAvatar::getAllowAvatarLeaningPreference() const { + return _allowAvatarLeaningPreference.get(); } float MyAvatar::getWalkSpeed() const { @@ -5221,59 +5325,61 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { _isInWalkingState = isWalking; } +// Specify whether the user is sitting or standing in the real world. void MyAvatar::setIsInSittingState(bool isSitting) { + // In updateSitStandState, we only change state if this timer is above a threshold (STANDING_TIMEOUT, SITTING_TIMEOUT). + // This avoids changing state if the user sits and stands up quickly. _sitStandStateTimer = 0.0f; - _squatTimer = 0.0f; - // on reset height we need the count to be more than one in case the user sits and stands up quickly. + _isInSittingState.set(isSitting); setResetMode(true); - if (isSitting) { - setCenterOfGravityModelEnabled(false); - } else { - setCenterOfGravityModelEnabled(true); - } setSitStandStateChange(true); } +// Deprecated, will be removed. void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { - - _userRecenterModel.set(modelName); + qCDebug(interfaceapp) + << "MyAvatar.setUserRecenterModel is deprecated and will be removed."; switch (modelName) { - case MyAvatar::SitStandModelType::ForceSit: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(true); - setIsSitStandStateLocked(true); + case SitStandModelType::ForceSit: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::Always); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::Never); break; - case MyAvatar::SitStandModelType::ForceStand: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(false); - setIsSitStandStateLocked(true); + case SitStandModelType::ForceStand: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::Always); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::Always); break; - case MyAvatar::SitStandModelType::Auto: + case SitStandModelType::Auto: default: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(false); - setIsSitStandStateLocked(false); + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::Always); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::WhenUserIsStanding); break; - case MyAvatar::SitStandModelType::DisableHMDLean: - setHMDLeanRecenterEnabled(false); - setIsInSittingState(false); - setIsSitStandStateLocked(false); + case SitStandModelType::DisableHMDLean: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::WhenUserIsStanding); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::AlwaysNoRecenter); break; } } +// Set the user preference of when the avatar may stand. +void MyAvatar::setAllowAvatarStandingPreference(const MyAvatar::AllowAvatarStandingPreference preference) { + _allowAvatarStandingPreference.set(preference); + + // Set the correct vertical position for the avatar body relative to the HMD, + // according to the newly-selected avatar standing preference. + centerBodyInternal(false); +} + +// Deprecated, will be removed. void MyAvatar::setIsSitStandStateLocked(bool isLocked) { - _lockSitStandState.set(isLocked); - _sitStandStateTimer = 0.0f; - _squatTimer = 0.0f; - _averageUserHeightSensorSpace = _userHeight.get(); - _tippingPoint = _userHeight.get(); - if (!isLocked) { - // always start the auto transition mode in standing state. - setIsInSittingState(false); - } + Q_UNUSED(isLocked); + qCDebug(interfaceapp) << "MyAvatar.setIsSitStandStateLocked is deprecated and will be removed."; +} + +// Set the user preference of when the avatar may lean. +void MyAvatar::setAllowAvatarLeaningPreference(const MyAvatar::AllowAvatarLeaningPreference preference) { + _allowAvatarLeaningPreference.set(preference); } void MyAvatar::setWalkSpeed(float value) { @@ -5402,10 +5508,12 @@ float MyAvatar::getAnalogPlusSprintSpeed() const { return _analogPlusSprintSpeed.get(); } +// Indicate whether the user's real-world sit/stand state has changed or not. void MyAvatar::setSitStandStateChange(bool stateChanged) { _sitStandStateChange = stateChanged; } +// Determine if the user's real-world sit/stand state has changed. float MyAvatar::getSitStandStateChange() const { return _sitStandStateChange; } @@ -5499,65 +5607,84 @@ MyAvatar::FollowHelper::FollowHelper() { } void MyAvatar::FollowHelper::deactivate() { - for (int i = 0; i < NumFollowTypes; i++) { - deactivate((FollowType)i); + for (uint i = 0; i < static_cast(CharacterController::FollowType::Count); i++) { + deactivate(static_cast(i)); } } -void MyAvatar::FollowHelper::deactivate(FollowType type) { - assert(type >= 0 && type < NumFollowTypes); +void MyAvatar::FollowHelper::deactivate(CharacterController::FollowType type) { + assert(static_cast(type) >= 0 && type < CharacterController::FollowType::Count); _timeRemaining[(int)type] = 0.0f; } -void MyAvatar::FollowHelper::activate(FollowType type) { - assert(type >= 0 && type < NumFollowTypes); +// snapFollow: true to snap immediately to the desired transform with regard to 'type', +// eg. activate(FollowType::Rotation, true) snaps the FollowHelper's rotation immediately +// to the rotation of its _followDesiredBodyTransform. +void MyAvatar::FollowHelper::activate(CharacterController::FollowType type, const bool snapFollow) { + assert(static_cast(type) >= 0 && type < CharacterController::FollowType::Count); + // TODO: Perhaps, the follow time should be proportional to the displacement. - _timeRemaining[(int)type] = FOLLOW_TIME; + _timeRemaining[(int)type] = snapFollow ? CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP : FOLLOW_TIME; } -bool MyAvatar::FollowHelper::isActive(FollowType type) const { - assert(type >= 0 && type < NumFollowTypes); +bool MyAvatar::FollowHelper::isActive(CharacterController::FollowType type) const { + assert(static_cast(type) >= 0 && type < CharacterController::FollowType::Count); return _timeRemaining[(int)type] > 0.0f; } bool MyAvatar::FollowHelper::isActive() const { - for (int i = 0; i < NumFollowTypes; i++) { - if (isActive((FollowType)i)) { + for (uint i = 0; i < static_cast(CharacterController::FollowType::Count); i++) { + if (isActive(static_cast(i))) { return true; } } return false; } -float MyAvatar::FollowHelper::getMaxTimeRemaining() const { - float max = 0.0f; - for (int i = 0; i < NumFollowTypes; i++) { - if (_timeRemaining[i] > max) { - max = _timeRemaining[i]; +void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { + for (auto& time : _timeRemaining) { + if (time == CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP) { + time = 0.0f; + } else { + time -= dt; } } - return max; } -void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { - for (int i = 0; i < NumFollowTypes; i++) { - _timeRemaining[i] -= dt; +// shouldSnapOut: (out) true if the FollowHelper should snap immediately to its desired rotation. +bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& shouldSnapOut) const { + // If hips are under direct control (tracked), they give our desired body rotation and we snap to it every frame. + if (myAvatar.areHipsTracked()) { + shouldSnapOut = true; + return true; + } else { + shouldSnapOut = false; } -} -bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold()); glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } -bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +// Determine if the horizontal following should activate, for a user who is sitting in the real world. +bool MyAvatar::FollowHelper::shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const { + if (!myAvatar.isAllowedToLean()) { + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + if (!withinBaseOfSupport(currentHeadPose, myAvatar.getAvatarScale())) { + return true; + } + } + // -z axis of currentBodyMatrix in world space. glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2])); // x axis of currentBodyMatrix in world space. glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0])); glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); float forwardLeanAmount = glm::dot(forward, offset); float lateralLeanAmount = glm::dot(right, offset); @@ -5567,11 +5694,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const float MAX_BACKWARD_LEAN = 0.1f; bool stepDetected = false; - if (myAvatar.getIsInSittingState()) { - if (!withinBaseOfSupport(currentHeadPose)) { - stepDetected = true; - } - } else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { + if (forwardLeanAmount > MAX_FORWARD_LEAN) { stepDetected = true; } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { stepDetected = true; @@ -5581,52 +5704,82 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { - - // get the current readings - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); - controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); - controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); - controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); - - bool stepDetected = false; - float myScale = myAvatar.getAvatarScale(); +// Determine if the horizontal following should activate, for a user who is standing in the real world. +// resetModeOut: (out) true if setResetMode(true) should be called if this function returns true. +// goToWalkingStateOut: (out) true if setIsInWalkingState(true) should be called if this function returns true. +bool MyAvatar::FollowHelper::shouldActivateHorizontal_userStanding( + const MyAvatar& myAvatar, + bool& resetModeOut, + bool& goToWalkingStateOut) const { if (myAvatar.getIsInWalkingState()) { - stepDetected = true; - } else { - if (!withinBaseOfSupport(currentHeadPose) && - headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && - handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && - handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && - headVelocityGreaterThanThreshold(currentHeadPose) && - isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { - // a step is detected + return true; + } + + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + bool stepDetected = false; + float avatarScale = myAvatar.getAvatarScale(); + + if (!withinBaseOfSupport(currentHeadPose, avatarScale)) { + if (!myAvatar.isAllowedToLean()) { stepDetected = true; - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { - myAvatar.setIsInWalkingState(true); - } } else { - glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); - glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); - float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); - if (!isActive(Horizontal) && - (!isActive(Vertical)) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { - myAvatar.setResetMode(true); + // get the current readings + controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); + controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); + + if (headAngularVelocityBelowThreshold(currentHeadPose) && + isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), avatarScale) && + handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && + handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && + headVelocityGreaterThanThreshold(currentHeadPose, avatarScale) && + isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { + // a step is detected stepDetected = true; - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { - myAvatar.setIsInWalkingState(true); + } + } + } + + if (!stepDetected) { + glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); + float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); + if (!isActive(CharacterController::FollowType::Horizontal) && (!isActive(CharacterController::FollowType::Vertical)) && + (glm::length(currentHeadPosition - defaultHipsPosition) > + (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { + resetModeOut = true; + stepDetected = true; + if (currentHeadPose.isValid()) { + if (glm::length(currentHeadPose.velocity) > (DEFAULT_AVATAR_WALK_SPEED_THRESHOLD * avatarScale)) { + goToWalkingStateOut = true; } } } } + return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +// Determine if the horizontal following should activate. +// resetModeOut: (out) true if setResetMode(true) should be called if this function returns true. +// goToWalkingStateOut: (out) true if setIsInWalkingState(true) should be called if this function returns true. +bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& resetModeOut, + bool& goToWalkingStateOut) const { + if (myAvatar.getIsInSittingState()) { + return shouldActivateHorizontal_userSitting(myAvatar, desiredBodyMatrix, currentBodyMatrix); + } else { + return shouldActivateHorizontal_userStanding(myAvatar, resetModeOut, goToWalkingStateOut); + } +} + +bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const { const float CYLINDER_TOP = 2.0f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; @@ -5638,9 +5791,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co returnValue = true; } else { if (myAvatar.getIsInSittingState()) { - if (myAvatar.getIsSitStandStateLocked()) { - returnValue = (offset.y > CYLINDER_TOP); - } if (offset.y < SITTING_BOTTOM) { // we recenter more easily when in sitting state. returnValue = true; @@ -5648,60 +5798,89 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co } else { // in the standing state returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); - // finally check for squats in standing - if (_squatDetected) { - returnValue = true; - } } } return returnValue; } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, - const glm::mat4& currentBodyMatrix, bool hasDriveInput) { +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool hasDriveInput) { + if (myAvatar.getHMDLeanRecenterEnabled()) { - if (myAvatar.getHMDLeanRecenterEnabled() && - qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { - if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } - if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } + // Rotation recenter + + { + bool snapFollow = false; + if (!isActive(CharacterController::FollowType::Rotation) && + (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix, snapFollow) || hasDriveInput)) { + activate(CharacterController::FollowType::Rotation, snapFollow); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } + } + + // Lean recenter + + if ((myAvatar.areFeetTracked() || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) { + activate(CharacterController::FollowType::Horizontal, myAvatar.areFeetTracked()); + setForceActivateHorizontal(false); } else { - // center of gravity model is not enabled - if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + if ((myAvatar.getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && + qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + + bool resetModeOut = false; + bool goToWalkingStateOut = false; + + // True if the user can turn their body while sitting (eg. swivel chair). + // Todo?: We could expose this as an option. + // (Regardless, rotation recentering does kick-in if they turn too far). + constexpr bool USER_CAN_TURN_BODY_WHILE_SITTING = false; + + if (!isActive(CharacterController::FollowType::Horizontal) && + (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix, resetModeOut, + goToWalkingStateOut) || + hasDriveInput)) { + activate(CharacterController::FollowType::Horizontal, false); + if (myAvatar.getEnableStepResetRotation() && + (USER_CAN_TURN_BODY_WHILE_SITTING || !myAvatar.getIsInSittingState())) { + activate(CharacterController::FollowType::Rotation, false); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } + + if (resetModeOut) { + myAvatar.setResetMode(true); + } + + if (goToWalkingStateOut) { + myAvatar.setIsInWalkingState(true); + } } } } - if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Vertical); - if (_squatDetected) { - _squatDetected = false; + + // Vertical recenter + + if (myAvatar.getHMDCrouchRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + if (!isActive(CharacterController::FollowType::Vertical) && + (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(CharacterController::FollowType::Vertical, false); } } } else { - if (!isActive(Rotation) && getForceActivateRotation()) { - activate(Rotation); + // Forced activations can be requested by MyAvatar::triggerVerticalRecenter, callable from scripts. + + if (!isActive(CharacterController::FollowType::Rotation) && getForceActivateRotation()) { + activate(CharacterController::FollowType::Rotation, true); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); setForceActivateRotation(false); } - if (!isActive(Horizontal) && getForceActivateHorizontal()) { - activate(Horizontal); + if (!isActive(CharacterController::FollowType::Horizontal) && getForceActivateHorizontal()) { + activate(CharacterController::FollowType::Horizontal, true); setForceActivateHorizontal(false); } - if (!isActive(Vertical) && getForceActivateVertical()) { - activate(Vertical); + if (!isActive(CharacterController::FollowType::Vertical) && getForceActivateVertical()) { + activate(CharacterController::FollowType::Vertical, true); setForceActivateVertical(false); } } @@ -5721,21 +5900,21 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat // remove scale present from sensorToWorldMatrix followWorldPose.scale() = glm::vec3(1.0f); - if (isActive(Rotation)) { - //use the hmd reading for the hips follow - followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); + if (isActive(CharacterController::FollowType::Rotation)) { + //use the hmd reading for the hips follow + followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); } - if (isActive(Horizontal)) { + if (isActive(CharacterController::FollowType::Horizontal)) { glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); followWorldPose.trans().x = desiredTranslation.x; followWorldPose.trans().z = desiredTranslation.z; } - if (isActive(Vertical)) { + if (isActive(CharacterController::FollowType::Vertical)) { glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); followWorldPose.trans().y = desiredTranslation.y; } - myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining()); + myAvatar.getCharacterController()->setFollowParameters(followWorldPose); } glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { @@ -5755,10 +5934,13 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const gl glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); - if (myAvatar.getSitStandStateChange()) { - myAvatar.setSitStandStateChange(false); - deactivate(Vertical); - setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); + + if (myAvatar.getHMDCrouchRecenterEnabled()) { + if (myAvatar.getSitStandStateChange()) { + myAvatar.setSitStandStateChange(false); + deactivate(CharacterController::FollowType::Vertical); + setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); + } } return newBodyMat; } else { @@ -6127,7 +6309,7 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& } bool MyAvatar::isRecenteringHorizontally() const { - return _follow.isActive(FollowHelper::Horizontal); + return _follow.isActive(CharacterController::FollowType::Horizontal); } const MyHead* MyAvatar::getMyHead() const { @@ -6583,7 +6765,7 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { setHMDLeanRecenterEnabled(false); // Disable movement setSitDriveKeysStatus(false); - centerBody(); + centerBodyInternal(true); int hipIndex = getJointIndex("Hips"); clearPinOnJoint(hipIndex); pinJoint(hipIndex, position, rotation); @@ -6601,7 +6783,7 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { _characterController.setSeated(false); setCollisionsEnabled(true); setHMDLeanRecenterEnabled(true); - centerBody(); + centerBodyInternal(false); slamPosition(position); setWorldOrientation(rotation); @@ -6906,6 +7088,19 @@ bool MyAvatar::isJumping() { _characterController.getState() == CharacterController::State::Takeoff) && !isFlying(); } +// Determine if the avatar is allowed to lean in its current situation. +bool MyAvatar::isAllowedToLean() const { + return (getAllowAvatarLeaningPreference() == MyAvatar::AllowAvatarLeaningPreference::Always) || + ((getAllowAvatarLeaningPreference() == MyAvatar::AllowAvatarLeaningPreference::WhenUserIsStanding) && + !getIsInSittingState()); +} + +// Determine if crouch recentering is enabled (making the avatar stand when the user is sitting in the real world). +bool MyAvatar::getHMDCrouchRecenterEnabled() const { + return (!_characterController.getSeated() && + (_allowAvatarStandingPreference.get() == AllowAvatarStandingPreference::Always) && !_isBodyPartTracked._feet); +} + bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) { if (QThread::currentThread() != thread()) { bool result = false; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3140c68f88..3d278cf983 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -283,15 +283,16 @@ class MyAvatar : public Avatar { * the value.

* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. - * @property {number} isInSittingState - true if the user wearing the HMD is determined to be sitting - * (avatar leaning is disabled, recentering is enabled), false if the user wearing the HMD is - * determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). - * If userRecenterModel == 2 (i.e., "auto") the property value automatically updates as the user sits - * or stands, unless isSitStandStateLocked == true. Setting the property value overrides the current - * sitting / standing state, which is updated when the user next sits or stands unless - * isSitStandStateLocked == true. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} isInSittingState - true if the user wearing the HMD is determined to be sitting; + * false if the user wearing the HMD is determined to be standing. This can affect whether the avatar + * is allowed to stand, lean or recenter its footing, depending on user preferences. + * The property value automatically updates as the user sits or stands. Setting the property value overrides the current + * sitting / standing state, which is updated when the user next sits or stands. * @property {boolean} isSitStandStateLocked - true to lock the avatar sitting/standing state, i.e., use this * to disable automatically changing state. + *

Deprecated: This property is deprecated and will be removed. + * See also: getUserRecenterModel and setUserRecenterModel.

* @property {boolean} allowTeleporting - true if teleporting is enabled in the Interface settings, * false if it isn't. Read-only. * @@ -413,8 +414,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); - Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); - Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); + Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); // Deprecated + Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); // Deprecated Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting) const QString DOMINANT_LEFT_HAND = "left"; @@ -519,6 +520,7 @@ public: /**jsdoc *

Specifies different avatar leaning and recentering behaviors.

+ *

Deprecated: This type is deprecated and will be removed.

* * * @@ -549,6 +551,29 @@ public: }; Q_ENUM(SitStandModelType) + // Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order. + enum class AllowAvatarStandingPreference : uint { + WhenUserIsStanding, + Always, + Count, + Default = Always + }; + Q_ENUM(AllowAvatarStandingPreference) + + // Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order. + enum class AllowAvatarLeaningPreference : uint { + WhenUserIsStanding, + Always, + Never, + AlwaysNoRecenter, // experimental + Count, + Default = WhenUserIsStanding + }; + Q_ENUM(AllowAvatarLeaningPreference) + + static const std::array allowAvatarStandingPreferenceStrings; + static const std::array allowAvatarLeaningPreferenceStrings; + explicit MyAvatar(QThread* thread); virtual ~MyAvatar(); @@ -1417,7 +1442,6 @@ public: controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; controller::Pose getControllerPoseInWorldFrame(controller::Action action) const; controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const; - glm::quat getOffHandRotation() const; bool hasDriveInput() const; @@ -1596,7 +1620,7 @@ public: * @function MyAvatar.getAvatarScale * @returns {number} The target scale for the avatar, range 0.0051000.0. */ - Q_INVOKABLE float getAvatarScale(); + Q_INVOKABLE float getAvatarScale() const; /**jsdoc * Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on @@ -1709,7 +1733,7 @@ public: // derive avatar body position and orientation from the current HMD Sensor location. // results are in sensor frame (-z forward) - glm::mat4 deriveBodyFromHMDSensor() const; + glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const; glm::mat4 getSpine2RotationRigSpace() const; @@ -1753,10 +1777,14 @@ public: bool getIsInWalkingState() const; void setIsInSittingState(bool isSitting); bool getIsInSittingState() const; - void setUserRecenterModel(MyAvatar::SitStandModelType modelName); - MyAvatar::SitStandModelType getUserRecenterModel() const; - void setIsSitStandStateLocked(bool isLocked); - bool getIsSitStandStateLocked() const; + void setUserRecenterModel(MyAvatar::SitStandModelType modelName); // Deprecated, will be removed. + MyAvatar::SitStandModelType getUserRecenterModel() const; // Deprecated, will be removed. + void setIsSitStandStateLocked(bool isLocked); // Deprecated, will be removed. + bool getIsSitStandStateLocked() const; // Deprecated, will be removed. + void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference); + AllowAvatarStandingPreference getAllowAvatarStandingPreference() const; + void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference); + AllowAvatarLeaningPreference getAllowAvatarLeaningPreference() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); @@ -1989,6 +2017,10 @@ public: glm::vec3 getLookAtPivotPoint(); glm::vec3 getCameraEyesPosition(float deltaTime); bool isJumping(); + bool getHMDCrouchRecenterEnabled() const; + bool isAllowedToLean() const; + bool areFeetTracked() const { return _isBodyPartTracked._feet; }; // Determine if the feet are under direct control. + bool areHipsTracked() const { return _isBodyPartTracked._hips; }; // Determine if the hips are under direct control. public slots: @@ -2709,6 +2741,16 @@ private: bool _isBraking { false }; bool _isAway { false }; + // Indicates which parts of the body are under direct control (tracked). + struct { + bool _feet { false }; // Left or right foot. + bool _feetPreviousUpdate{ false };// Value of _feet on the previous update. + bool _hips{ false }; + bool _leftHand{ false }; + bool _rightHand{ false }; + bool _head{ false }; + } _isBodyPartTracked; + float _boomLength { ZOOM_DEFAULT }; float _yawSpeed; // degrees/sec float _pitchSpeed; // degrees/sec @@ -2791,6 +2833,7 @@ private: void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation); void resetPointAt(); static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation); + void centerBodyInternal(const bool forceFollowYPos = false); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; @@ -2841,26 +2884,21 @@ private: struct FollowHelper { FollowHelper(); - enum FollowType { - Rotation = 0, - Horizontal, - Vertical, - NumFollowTypes - }; - float _timeRemaining[NumFollowTypes]; + CharacterController::FollowTimePerType _timeRemaining; void deactivate(); - void deactivate(FollowType type); - void activate(); - void activate(FollowType type); + void deactivate(CharacterController::FollowType type); + void activate(CharacterController::FollowType type, const bool snapFollow); bool isActive() const; - bool isActive(FollowType followType) const; - float getMaxTimeRemaining() const; + bool isActive(CharacterController::FollowType followType) const; void decrementTimeRemaining(float dt); - bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const; bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; + bool shouldActivateHorizontal(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& resetModeOut, + bool& goToWalkingStateOut) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -2871,16 +2909,23 @@ private: void setForceActivateHorizontal(bool val); bool getToggleHipsFollowing() const; void setToggleHipsFollowing(bool followHead); - bool _squatDetected { false }; std::atomic _forceActivateRotation { false }; std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; + + private: + bool shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const; + bool shouldActivateHorizontal_userStanding(const MyAvatar& myAvatar, + bool& resetModeOut, + bool& goToWalkingStateOut) const; }; FollowHelper _follow; - bool isFollowActive(FollowHelper::FollowType followType) const; + bool isFollowActive(CharacterController::FollowType followType) const; bool _goToPending { false }; bool _physicsSafetyPending { false }; @@ -2922,6 +2967,9 @@ private: bool _centerOfGravityModelEnabled { true }; bool _hmdLeanRecenterEnabled { true }; + bool _hmdCrouchRecenterEnabled { + true + }; // Is MyAvatar allowed to recenter vertically (stand) when the user is sitting in the real world. bool _sprint { false }; AnimPose _prePhysicsRoomPose; @@ -2953,7 +3001,6 @@ private: ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; float _averageUserHeightSensorSpace { _userHeight.get() }; bool _sitStandStateChange { false }; - ThreadSafeValueCache _lockSitStandState { false }; // max unscaled forward movement speed ThreadSafeValueCache _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; @@ -2969,9 +3016,13 @@ private: float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; - ThreadSafeValueCache _userRecenterModel { MyAvatar::SitStandModelType::Auto }; + ThreadSafeValueCache _allowAvatarStandingPreference{ + MyAvatar::AllowAvatarStandingPreference::Default + }; // The user preference of when MyAvatar may stand. + ThreadSafeValueCache _allowAvatarLeaningPreference{ + MyAvatar::AllowAvatarLeaningPreference::Default + }; // The user preference of when MyAvatar may lean. float _sitStandStateTimer { 0.0f }; - float _squatTimer { 0.0f }; float _tippingPoint { _userHeight.get() }; // load avatar scripts once when rig is ready @@ -3012,7 +3063,8 @@ private: Setting::Handle _controlSchemeIndexSetting; std::vector> _avatarEntityIDSettings; std::vector> _avatarEntityDataSettings; - Setting::Handle _userRecenterModelSetting; + Setting::Handle _allowAvatarStandingPreferenceSetting; + Setting::Handle _allowAvatarLeaningPreferenceSetting; // AvatarEntities stuff: // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 997dcfe685..0d382934b8 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -26,7 +26,9 @@ void MyCharacterController::RayShotgunResult::reset() { walkable = true; } -MyCharacterController::MyCharacterController(std::shared_ptr avatar) { +MyCharacterController::MyCharacterController(std::shared_ptr avatar, + const FollowTimePerType& followTimeRemainingPerType) : + CharacterController(followTimeRemainingPerType) { assert(avatar); _avatar = avatar; diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index eefcc92637..b25c2412a0 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -23,7 +23,7 @@ class DetailedMotionState; class MyCharacterController : public CharacterController { public: - explicit MyCharacterController(std::shared_ptr avatar); + explicit MyCharacterController(std::shared_ptr avatar, const FollowTimePerType& followTimeRemainingPerType); ~MyCharacterController (); void addToWorld() override; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6fe199aaba..4984c1d335 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -65,13 +65,21 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { return result; } + // Use the center-of-gravity model if the user and the avatar are standing, unless flying or walking. + // If artificial standing is disabled, use center-of-gravity regardless of the user's sit/stand state. + bool useCenterOfGravityModel = + myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() && + (!myAvatar->getHMDCrouchRecenterEnabled() || !myAvatar->getIsInSittingState()) && + myAvatar->getHMDLeanRecenterEnabled() && + (myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter); + glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) { + if (useCenterOfGravityModel) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { // otherwise use the default of putting the hips under the head - hipsMat = myAvatar->deriveBodyFromHMDSensor(); + hipsMat = myAvatar->deriveBodyFromHMDSensor(true); } glm::vec3 hipsPos = extractTranslation(hipsMat); glm::quat hipsRot = glmExtractRotation(hipsMat); @@ -82,7 +90,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { // dampen hips rotation, by mixing it with the avatar orientation in sensor space // turning this off for center of gravity model because it is already mixed in there - if (!(myAvatar->getCenterOfGravityModelEnabled())) { + if (!useCenterOfGravityModel) { const float MIX_RATIO = 0.5f; hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); } diff --git a/interface/src/graphics/WorldBox.cpp b/interface/src/graphics/WorldBox.cpp index 0e15d9da86..a627cf75e4 100644 --- a/interface/src/graphics/WorldBox.cpp +++ b/interface/src/graphics/WorldBox.cpp @@ -16,7 +16,7 @@ render::ItemID WorldBoxRenderData::_item{ render::Item::INVALID_ITEM_ID }; namespace render { template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1); } - template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } + template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { return Item::Bound(); } template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { PerformanceTimer perfTimer("worldBox"); diff --git a/interface/src/graphics/WorldBox.h b/interface/src/graphics/WorldBox.h index 4d53652c0e..0d697eb133 100644 --- a/interface/src/graphics/WorldBox.h +++ b/interface/src/graphics/WorldBox.h @@ -32,7 +32,7 @@ public: namespace render { template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff); - template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff); + template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args); template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 216248f8b5..959125ba7c 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -415,7 +415,7 @@ gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabo for (auto& key : keys) { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL); if (std::get<0>(key)) { PrepareStencil::testMask(*state); } else { @@ -462,9 +462,9 @@ namespace render { template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { return payload->getKey(); } - template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) { if (payload) { - return payload->getBound(); + return payload->getBound(args); } return Item::Bound(); } diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 85d09adbdb..1701e4dcfa 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -31,7 +31,7 @@ public: void render(RenderArgs* args); render::Item::Bound& editBound() { return _bound; } - const render::Item::Bound& getBound() { return _bound; } + const render::Item::Bound& getBound(RenderArgs* args) { return _bound; } render::ItemKey getKey() const { return _key; } void setVisible(bool visible); @@ -128,7 +128,7 @@ private: namespace render { template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); - template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args); template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); } diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index c5769ef4bb..5b90474d23 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -76,8 +76,13 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, "Use the field below to place your file in a specific folder or to rename it. " "Specifying a new folder name will automatically create that folder for you."; - auto offscreenUi = DependencyManager::get(); - auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path", + auto offscreenUI = DependencyManager::get(); + if (!offscreenUI) { + completedCallback.call({ -1 }); + return; + } + + auto result = offscreenUI->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path", dropEvent ? dropHelpText : helpText, mapping); if (!result.isValid() || result.toString() == "") { @@ -94,7 +99,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, // Check for override if (isKnownMapping(mapping)) { auto message = mapping + "\n" + "This file already exists. Do you want to overwrite it?"; - auto button = offscreenUi->messageBox(OffscreenUi::ICON_QUESTION, "Overwrite File", message, + auto button = offscreenUI->messageBox(OffscreenUi::ICON_QUESTION, "Overwrite File", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (button == QMessageBox::No) { completedCallback.call({ -1 }); diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index f78f7853ca..e527561b05 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -99,11 +99,16 @@ void DesktopScriptingInterface::setHUDAlpha(float alpha) { } void DesktopScriptingInterface::show(const QString& path, const QString& title) { + auto offscreenUI = DependencyManager::get(); + if (!offscreenUI) { + return; + } + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "show", Qt::QueuedConnection, Q_ARG(QString, path), Q_ARG(QString, title)); return; } - DependencyManager::get()->show(path, title); + offscreenUI->show(path, title); } InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) { diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 8f7ae7c4dc..79c0452a45 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -96,8 +96,9 @@ bool HMDScriptingInterface::shouldShowHandControllers() const { void HMDScriptingInterface::activateHMDHandMouse() { QWriteLocker lock(&_hmdHandMouseLock); - auto offscreenUi = DependencyManager::get(); - offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", true); + if (auto offscreenUI = DependencyManager::get()) { + offscreenUI->getDesktop()->setProperty("hmdHandMouseActive", true); + } _hmdHandMouseCount++; } @@ -105,8 +106,9 @@ void HMDScriptingInterface::deactivateHMDHandMouse() { QWriteLocker lock(&_hmdHandMouseLock); _hmdHandMouseCount = std::max(_hmdHandMouseCount - 1, 0); if (_hmdHandMouseCount == 0) { - auto offscreenUi = DependencyManager::get(); - offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", false); + if (auto offscreenUI = DependencyManager::get()) { + offscreenUI->getDesktop()->setProperty("hmdHandMouseActive", false); + } } } diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index 9adf514718..c1f325237e 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -217,7 +217,7 @@ PlatformInfoScriptingInterface::PlatformTier PlatformInfoScriptingInterface::get } QStringList PlatformInfoScriptingInterface::getPlatformTierNames() { - static const QStringList platformTierNames = { "UNKNWON", "LOW", "MID", "HIGH" }; + static const QStringList platformTierNames = { "UNKNOWN", "LOW", "MID", "HIGH" }; return platformTierNames; } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 14a0d04023..98335e5d3a 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -199,9 +199,9 @@ void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitia DependencyManager::get()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode); } -bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { - auto offscreenUi = DependencyManager::get(); - return offscreenUi->isPointOnDesktopWindow(point); +bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { + auto offscreenUI = DependencyManager::get(); + return offscreenUI ? offscreenUI->isPointOnDesktopWindow(point) : false; } /// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and @@ -553,12 +553,14 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu * @typedef {number} Window.MessageBoxButton */ int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) { - auto messageBox = DependencyManager::get()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text, - static_cast>(buttons), static_cast(defaultButton)); - connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int))); + if (auto offscreenUI = DependencyManager::get()) { + auto messageBox = offscreenUI->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text, + static_cast>(buttons), static_cast(defaultButton)); + connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int))); - _lastMessageBoxID += 1; - _messageBoxes.insert(_lastMessageBoxID, messageBox); + _lastMessageBoxID += 1; + _messageBoxes.insert(_lastMessageBoxID, messageBox); + } return _lastMessageBoxID; } @@ -646,13 +648,17 @@ void WindowScriptingInterface::setActiveDisplayPlugin(int index) { } void WindowScriptingInterface::openWebBrowser(const QString& url) { + auto offscreenUI = DependencyManager::get(); + if (!offscreenUI) { + return; + } + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "openWebBrowser", Q_ARG(const QString&, url)); return; } - auto offscreenUi = DependencyManager::get(); - offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { + offscreenUI->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { if (!url.isEmpty()) { newObject->setProperty("url", url); } diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp index 2a355e48d1..fff69cb1c0 100644 --- a/interface/src/ui/AnimStats.cpp +++ b/interface/src/ui/AnimStats.cpp @@ -67,13 +67,13 @@ void AnimStats::updateStats(bool force) { // print if we are recentering or not. _recenterText = "Recenter: "; - if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Rotation)) { + if (myAvatar->isFollowActive(CharacterController::FollowType::Rotation)) { _recenterText += "Rotation "; } - if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Horizontal)) { + if (myAvatar->isFollowActive(CharacterController::FollowType::Horizontal)) { _recenterText += "Horizontal "; } - if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Vertical)) { + if (myAvatar->isFollowActive(CharacterController::FollowType::Vertical)) { _recenterText += "Vertical "; } emit recenterTextChanged(); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index e91b1d725c..144f64c385 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -100,10 +100,10 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { // threads, we need to use a sync object to deteremine when // the current UI texture is no longer being read from, and only // then release it back to the UI for re-use - auto offscreenUi = DependencyManager::get(); + auto offscreenUI = DependencyManager::get(); OffscreenQmlSurface::TextureAndFence newTextureAndFence; - bool newTextureAvailable = offscreenUi->fetchTexture(newTextureAndFence); + bool newTextureAvailable = offscreenUI ? offscreenUI->fetchTexture(newTextureAndFence) : false; if (newTextureAvailable) { _uiTexture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second); } diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index 0ac1f05737..daf80acf00 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -362,10 +362,11 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap object->setObjectName("InteractiveWindow"); object->setProperty(SOURCE_PROPERTY, sourceURL); }; - auto offscreenUi = DependencyManager::get(); - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, objectInitLambda, contextInitLambda); + if (auto offscreenUI = DependencyManager::get()) { + // Build the event bridge and wrapper on the main thread + offscreenUI->loadInNewContext(CONTENT_WINDOW_QML, objectInitLambda, contextInitLambda); + } } } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 79d9ebaa5c..9c53060f31 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -422,40 +422,40 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [myAvatar]()->int { - switch (myAvatar->getUserRecenterModel()) { - case MyAvatar::SitStandModelType::Auto: - default: - return 0; - case MyAvatar::SitStandModelType::ForceSit: - return 1; - case MyAvatar::SitStandModelType::ForceStand: - return 2; - case MyAvatar::SitStandModelType::DisableHMDLean: - return 3; - } + IntPreference::Getter getter = [myAvatar]() -> int { + return static_cast(myAvatar->getAllowAvatarStandingPreference()); }; - auto setter = [myAvatar](int value) { - switch (value) { - case 0: - default: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); - break; - case 1: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); - break; - case 2: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceStand); - break; - case 3: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); - break; - } + + IntPreference::Setter setter = [myAvatar](const int& value) { + myAvatar->setAllowAvatarStandingPreference(static_cast(value)); }; - auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Force Stand / Disable Recenter", getter, setter); + + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to stand", getter, setter); QStringList items; - items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Standing - enables avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; - preference->setHeading("Avatar leaning behavior"); + items << "When I'm standing" + << "Always"; // Must match the order in MyAvatar::AllowAvatarStandingPreference. + assert(items.size() == static_cast(MyAvatar::AllowAvatarStandingPreference::Count)); + preference->setHeading("Allow my avatar to stand:"); + preference->setItems(items); + preferences->addPreference(preference); + } + { + IntPreference::Getter getter = [myAvatar]() -> int { + return static_cast(myAvatar->getAllowAvatarLeaningPreference()); + }; + + IntPreference::Setter setter = [myAvatar](const int& value) { + myAvatar->setAllowAvatarLeaningPreference(static_cast(value)); + }; + + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to lean", getter, setter); + QStringList items; + items << "When I'm standing" + << "Always" + << "Never" + << "Always, no recenter (Experimental)"; // Must match the order in MyAvatar::AllowAvatarLeaningPreference. + assert(items.size() == static_cast(MyAvatar::AllowAvatarLeaningPreference::Count)); + preference->setHeading("Allow my avatar to lean:"); preference->setItems(items); preferences->addPreference(preference); } diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 27ab375a42..ebd65de612 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -248,9 +248,9 @@ private: \ * Read-only. * @property {string} lodStatus - Description of the current LOD. * Read-only. - * @property {string} numEntityUpdates - The number of entity updates that happened last frame. + * @property {number} numEntityUpdates - The number of entity updates that happened last frame. * Read-only. - * @property {string} numNeededEntityUpdates - The total number of entity updates scheduled for last frame. + * @property {number} numNeededEntityUpdates - The total number of entity updates scheduled for last frame. * Read-only. * @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of * the code. Provided only if timingExpanded is true. Only the top 10 items are provided if @@ -547,8 +547,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, lodAngle, 0) STATS_PROPERTY(int, lodTargetFramerate, 0) STATS_PROPERTY(QString, lodStatus, QString()) - STATS_PROPERTY(int, numEntityUpdates, 0) - STATS_PROPERTY(int, numNeededEntityUpdates, 0) + STATS_PROPERTY(quint64, numEntityUpdates, 0) + STATS_PROPERTY(quint64, numNeededEntityUpdates, 0) STATS_PROPERTY(QString, timingStats, QString()) STATS_PROPERTY(QString, gameUpdateStats, QString()) STATS_PROPERTY(int, serverElements, 0) diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 72373d2d20..5c92cfce00 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -66,7 +66,7 @@ private: namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay); - template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay); + template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay, RenderArgs* args); template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay); template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5e43c5df8d..e9e310e68b 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -1212,8 +1212,8 @@ float Overlays::width() { return result; } - auto offscreenUi = DependencyManager::get(); - return offscreenUi->getWindow()->size().width(); + auto offscreenUI = DependencyManager::get(); + return offscreenUI ? offscreenUI->getWindow()->size().width() : -1.0f; } float Overlays::height() { @@ -1224,8 +1224,8 @@ float Overlays::height() { return result; } - auto offscreenUi = DependencyManager::get(); - return offscreenUi->getWindow()->size().height(); + auto offscreenUI = DependencyManager::get(); + return offscreenUI ? offscreenUI->getWindow()->size().height() : -1.0f; } void Overlays::mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event) { diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 0d1bcdd071..fdcd4f7205 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -14,7 +14,7 @@ namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) { return overlay->getKey(); } - template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay) { + template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay, RenderArgs* args) { return overlay->getBounds(); } template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) { diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index 2afb29bb91..c097e7dd97 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -24,13 +24,17 @@ QmlOverlay::QmlOverlay(const QUrl& url, const QmlOverlay* overlay) } void QmlOverlay::buildQmlElement(const QUrl& url) { + auto offscreenUI = DependencyManager::get(); + if (!offscreenUI) { + return; + } + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "buildQmlElement", Q_ARG(QUrl, url)); return; } - auto offscreenUi = DependencyManager::get(); - offscreenUi->load(url, [=](QQmlContext* context, QObject* object) { + offscreenUI->load(url, [=](QQmlContext* context, QObject* object) { _qmlElement = dynamic_cast(object); connect(_qmlElement, &QObject::destroyed, this, &QmlOverlay::qmlElementDestroyed); }); diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp index f65bf88754..1310baf4b3 100644 --- a/interface/src/workload/GameWorkloadRenderer.cpp +++ b/interface/src/workload/GameWorkloadRenderer.cpp @@ -82,9 +82,9 @@ namespace render { template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload) { return payload->getKey(); } - template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload) { + template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args) { if (payload) { - return payload->getBound(); + return payload->getBound(args); } return Item::Bound(); } diff --git a/interface/src/workload/GameWorkloadRenderer.h b/interface/src/workload/GameWorkloadRenderer.h index a25598821e..ad33425774 100644 --- a/interface/src/workload/GameWorkloadRenderer.h +++ b/interface/src/workload/GameWorkloadRenderer.h @@ -57,7 +57,7 @@ public: void render(RenderArgs* args); render::Item::Bound& editBound() { return _bound; } - const render::Item::Bound& getBound() { return _bound; } + const render::Item::Bound& getBound(RenderArgs* args) { return _bound; } void setVisible(bool visible); void showProxies(bool show); @@ -96,7 +96,7 @@ protected: namespace render { template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload); - template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args); template <> void payloadRender(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const GameWorkloadRenderItem::Pointer& payload); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 06fe558964..6dc378a32f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin return position; } +// Get the scale factor to convert distances in the geometry frame into the unscaled rig frame. +// Typically it will be the unit conversion from cm to m. +float Rig::GetScaleFactorGeometryToUnscaledRig() const { + // Normally the model offset transform will contain the avatar scale factor; we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); + + return geomToRigWithoutAvatarScale.scale().x; // in practice this is always a uniform scale factor. +} + void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, @@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule( Extents totalExtents; totalExtents.reset(); - // HACK by convention our Avatars are always modeled such that y=0 is the ground plane. - // add the zero point so that our avatars will always have bounding volumes that are flush with the ground + // HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane. + // add the ground point so that our avatars will always have bounding volumes that are flush with the ground // even if they do not have legs (default robot) - totalExtents.addPoint(glm::vec3(0.0f)); + totalExtents.addPoint(glm::vec3(0.0f, GEOMETRY_GROUND_Y, 0.0f)); // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. @@ -2747,24 +2757,20 @@ void Rig::initFlow(bool isActive) { } } +// Get the vertical position of eye joints, in the rig coordinate frame, ignoring the avatar scale. float Rig::getUnscaledEyeHeight() const { // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); - // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. - // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + // Factor to scale distances in the geometry frame into the unscaled rig frame. + float scaleFactor = GetScaleFactorGeometryToUnscaledRig(); int headTopJoint = indexOfJoint("HeadTop_End"); int headJoint = indexOfJoint("Head"); int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - const float GROUND_Y = 0.0f; - // Values from the skeleton are in the geometry coordinate frame. auto skeleton = getAnimSkeleton(); if (eyeJoint >= 0 && toeJoint >= 0) { @@ -2772,8 +2778,8 @@ float Rig::getUnscaledEyeHeight() const { float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; return scaleFactor * eyeHeight; } else if (eyeJoint >= 0) { - // Measure Eye joint to y = 0 plane. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + // Measure Eye joint to ground plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y; return scaleFactor * eyeHeight; } else if (headTopJoint >= 0 && toeJoint >= 0) { // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. @@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const { } else if (headTopJoint >= 0) { // Measure from HeadTop_End joint to the ground, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GEOMETRY_GROUND_Y; return scaleFactor * (headHeight - headHeight * ratio); } else if (headJoint >= 0) { // Measure Head joint to the ground, then add in distance from neck to eye. const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GEOMETRY_GROUND_Y; return scaleFactor * (neckHeight + neckHeight * ratio); } else { return DEFAULT_AVATAR_EYE_HEIGHT; } } +// Get the vertical position of the hips joint, in the rig coordinate frame, ignoring the avatar scale. +float Rig::getUnscaledHipsHeight() const { + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + float scaleFactor = GetScaleFactorGeometryToUnscaledRig(); + + int hipsJoint = indexOfJoint("Hips"); + + // Values from the skeleton are in the geometry coordinate frame. + if (hipsJoint >= 0) { + // Measure hip joint to ground plane. + float hipsHeight = getAnimSkeleton()->getAbsoluteDefaultPose(hipsJoint).trans().y - GEOMETRY_GROUND_Y; + return scaleFactor * hipsHeight; + } else { + return DEFAULT_AVATAR_HIPS_HEIGHT; + } +} + void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) { _animVars.set(targetName, blendingTarget); _animVars.set(alphaName, alpha); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 60a2602316..c58be799cf 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -251,6 +251,7 @@ public: Flow& getFlow() { return _internalFlow; } float getUnscaledEyeHeight() const; + float getUnscaledHipsHeight() const; void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const; int getOverrideJointCount() const; @@ -287,6 +288,11 @@ protected: glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; + // Get the scale factor to convert distances in the geometry frame into the unscaled rig frame. + float GetScaleFactorGeometryToUnscaledRig() const; + + // The ground plane Y position in geometry space. + static constexpr float GEOMETRY_GROUND_Y = 0.0f; AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp index 04e5666106..9494a84fab 100644 --- a/libraries/audio/src/AudioInjectorManager.cpp +++ b/libraries/audio/src/AudioInjectorManager.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "AudioConstants.h" #include "AudioInjector.h" @@ -54,11 +55,14 @@ AudioInjectorManager::~AudioInjectorManager() { } void AudioInjectorManager::createThread() { - _thread = new QThread; + _thread = new QThread(); _thread->setObjectName("Audio Injector Thread"); // when the thread is started, have it call our run to handle injection of audio - connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection); + connect(_thread, &QThread::started, this, [this] { + setThreadName("AudioInjectorManager"); + run(); + }, Qt::DirectConnection); moveToThread(_thread); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d859454a99..f361e15999 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -63,7 +63,7 @@ namespace render { } return keyBuilder.build(); } - template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) { + template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar, RenderArgs* args) { auto avatarPtr = static_pointer_cast(avatar); if (avatarPtr) { return avatarPtr->getRenderBounds(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 25dd347484..4fc9c25595 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -39,7 +39,7 @@ namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar); - template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar); + template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar, RenderArgs* args); template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args); template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 4950b86f75..5ac808d3fb 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -156,17 +156,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { updateAttitude(_owningAvatar->getWorldOrientation()); setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients()); + Parent::simulate(deltaTime, fullUpdate); if (fullUpdate) { - - Parent::simulate(deltaTime, fullUpdate); - // let rig compute the model offset glm::vec3 registrationPoint; if (_rig.getModelRegistrationPoint(registrationPoint)) { setOffset(registrationPoint); } - } else { - Parent::simulate(deltaTime, fullUpdate); } // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem, diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index f601b99779..ba83ddff55 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -50,6 +50,7 @@ #include "CompositorHelper.h" #include "Logging.h" #include "RefreshRateController.h" +#include using namespace shader::gpu::program; @@ -285,6 +286,7 @@ bool OpenGLDisplayPlugin::activate() { widget->context()->doneCurrent(); presentThread->setContext(widget->context()); + connect(presentThread.data(), &QThread::started, [] { setThreadName("OpenGL Present Thread"); }); // Start execution presentThread->start(); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index f7623aad10..009e5f6c4f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -136,8 +136,8 @@ public: static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName); static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName); - int getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; } - int getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; } + size_t getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; } + size_t getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; } signals: void enterEntity(const EntityItemID& entityItemID); @@ -253,8 +253,8 @@ private: ReadWriteLockable _changedEntitiesGuard; std::unordered_set _changedEntities; - int _prevNumEntityUpdates { 0 }; - int _prevTotalNeededEntityUpdates { 0 }; + size_t _prevNumEntityUpdates { 0 }; + size_t _prevTotalNeededEntityUpdates { 0 }; std::unordered_set _renderablesToUpdate; std::unordered_map _entitiesInScene; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index b116423b75..3e13b301df 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -137,8 +137,15 @@ EntityRenderer::~EntityRenderer() {} // Smart payload proxy members, implementing the payload interface // -Item::Bound EntityRenderer::getBound() { - return _bound; +Item::Bound EntityRenderer::getBound(RenderArgs* args) { + auto bound = _bound; + if (_billboardMode != BillboardMode::NONE) { + glm::vec3 dimensions = bound.getScale(); + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); + } + return bound; } ShapeKey EntityRenderer::getShapeKey() { @@ -198,12 +205,9 @@ uint32_t EntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) const { } bool EntityRenderer::passesZoneOcclusionTest(const std::unordered_set& containingZones) const { - auto renderWithZones = resultWithReadLock>([&] { - return _renderWithZones; - }); - if (!renderWithZones.isEmpty()) { + if (!_renderWithZones.isEmpty()) { if (!containingZones.empty()) { - for (auto renderWithZone : renderWithZones) { + for (auto renderWithZone : _renderWithZones) { if (containingZones.find(renderWithZone) != containingZones.end()) { return true; } @@ -364,6 +368,11 @@ bool EntityRenderer::needsRenderUpdate() const { return needsRenderUpdateFromEntity(_entity); } +Transform EntityRenderer::getTransformToCenterWithMaybeOnlyLocalRotation(const EntityItemPointer& entity, bool& success) const { + return entity->getBillboardMode() == BillboardMode::NONE ? entity->getTransformToCenter(success) : + entity->getTransformToCenterWithOnlyLocalRotation(success); +} + // Returns true if the item in question needs to have updateInScene called because of changes in the entity bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity) const { if (entity->needsRenderUpdate()) { @@ -375,12 +384,12 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity } bool success = false; - auto bound = _entity->getAABox(success); + auto bound = entity->getAABox(success); if (success && _bound != bound) { return true; } - auto newModelTransform = _entity->getTransformToCenter(success); + auto newModelTransform = getTransformToCenterWithMaybeOnlyLocalRotation(entity, success); // FIXME can we use a stale model transform here? if (success && newModelTransform != _modelTransform) { return true; @@ -397,15 +406,15 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return false; } -void EntityRenderer::updateModelTransformAndBound() { +void EntityRenderer::updateModelTransformAndBound(const EntityItemPointer& entity) { bool success = false; - auto newModelTransform = _entity->getTransformToCenter(success); + auto newModelTransform = getTransformToCenterWithMaybeOnlyLocalRotation(entity, success); if (success) { _modelTransform = newModelTransform; } success = false; - auto bound = _entity->getAABox(success); + auto bound = entity->getAABox(success); if (success) { _bound = bound; } @@ -425,24 +434,28 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _prevIsTransparent = transparent; - updateModelTransformAndBound(); + updateModelTransformAndBound(entity); _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); - setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera()); - setRenderLayer(entity->getRenderLayer()); - _primitiveMode = entity->getPrimitiveMode(); - _canCastShadow = entity->getCanCastShadow(); - setCullWithParent(entity->getCullWithParent()); - _cauterized = entity->getCauterized(); - if (entity->needsZoneOcclusionUpdate()) { - entity->resetNeedsZoneOcclusionUpdate(); - _renderWithZones = entity->getRenderWithZones(); - } entity->setNeedsRenderUpdate(false); }); } +void EntityRenderer::doRenderUpdateAsynchronous(const EntityItemPointer& entity) { + setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera()); + setRenderLayer(entity->getRenderLayer()); + _billboardMode = entity->getBillboardMode(); + _primitiveMode = entity->getPrimitiveMode(); + _canCastShadow = entity->getCanCastShadow(); + setCullWithParent(entity->getCullWithParent()); + _cauterized = entity->getCauterized(); + if (entity->needsZoneOcclusionUpdate()) { + entity->resetNeedsZoneOcclusionUpdate(); + _renderWithZones = entity->getRenderWithZones(); + } +} + void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { QObject::connect(this, &EntityRenderer::requestRenderUpdate, this, [this] { auto renderer = DependencyManager::get(); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index ca3e024338..9392d61e75 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -64,7 +64,7 @@ public: static glm::vec3 calculatePulseColor(const glm::vec3& color, const PulsePropertyGroup& pulseProperties, quint64 start); virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override; - virtual Item::Bound getBound() override; + virtual Item::Bound getBound(RenderArgs* args) override; bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const override; protected: @@ -95,14 +95,13 @@ protected: // Will be called by the lambda posted to the scene in updateInScene. // This function will execute on the rendering thread, so you cannot use network caches to fetch // data in this method if using multi-threaded rendering - - virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity) { } + virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity); // Called by the `render` method after `needsRenderUpdate` virtual void doRender(RenderArgs* args) = 0; virtual bool isFading() const { return _isFading; } - virtual void updateModelTransformAndBound(); + virtual void updateModelTransformAndBound(const EntityItemPointer& entity); virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; } inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; } @@ -110,16 +109,14 @@ protected: virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; } virtual void setCullWithParent(bool value) { _cullWithParent = value; } -signals: - void requestRenderUpdate(); - -protected: template std::shared_ptr asTypedEntity() { return std::static_pointer_cast(_entity); } static void makeStatusGetters(const EntityItemPointer& entity, Item::Status::Getters& statusGetters); const Transform& getModelTransform() const; + Transform getTransformToCenterWithMaybeOnlyLocalRotation(const EntityItemPointer& entity, bool& success) const; + Item::Bound _bound; SharedSoundPointer _collisionSound; QUuid _changeHandlerId; @@ -135,6 +132,7 @@ protected: RenderLayer _renderLayer { RenderLayer::WORLD }; PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; QVector _renderWithZones; + BillboardMode _billboardMode; bool _cauterized { false }; bool _moving { false }; Transform _renderTransform; @@ -154,6 +152,9 @@ protected: const EntityItemPointer _entity; QUuid _entityID; + +signals: + void requestRenderUpdate(); }; template diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp index 6928454eb0..bf005ae2e5 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp @@ -195,8 +195,8 @@ void GizmoEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint } } -Item::Bound GizmoEntityRenderer::getBound() { - auto bound = Parent::getBound(); +Item::Bound GizmoEntityRenderer::getBound(RenderArgs* args) { + auto bound = Parent::getBound(args); if (_ringProperties.getHasTickMarks()) { glm::vec3 scale = bound.getScale(); for (int i = 0; i < 3; i += 2) { @@ -242,18 +242,20 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) { bool hasTickMarks = _ringProperties.getHasTickMarks(); glm::vec4 tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(), _ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength()); - bool forward; - bool wireframe; + bool transparent; withReadLock([&] { transform = _renderTransform; transparent = isTransparent(); - wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES; - forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD; }); + bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES; + bool forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD; + geometryCache->bindSimpleProgram(batch, false, transparent, wireframe, true, true, forward, graphics::MaterialKey::CULL_NONE); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), true)); batch.setModelTransform(transform); // Background circle diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.h b/libraries/entities-renderer/src/RenderableGizmoEntityItem.h index 8593348bde..6a09d1a047 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.h +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.h @@ -23,7 +23,7 @@ public: ~GizmoEntityRenderer(); protected: - Item::Bound getBound() override; + Item::Bound getBound(RenderArgs* args) override; ShapeKey getShapeKey() override; bool isTransparent() const override; diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index 35702c63e4..e374fe29c0 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -49,13 +49,13 @@ void GridEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe _minorGridEvery = entity->getMinorGridEvery(); } -Item::Bound GridEntityRenderer::getBound() { +Item::Bound GridEntityRenderer::getBound(RenderArgs* args) { if (_followCamera) { // This is a UI element that should always be in view, lie to the octree to avoid culling const AABox DOMAIN_BOX = AABox(glm::vec3(-TREE_SCALE / 2), TREE_SCALE); return DOMAIN_BOX; } - return Parent::getBound(); + return Parent::getBound(args); } ShapeKey GridEntityRenderer::getShapeKey() { @@ -77,17 +77,17 @@ void GridEntityRenderer::doRender(RenderArgs* args) { color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); glm::vec3 dimensions; Transform renderTransform; - bool forward; withReadLock([&] { dimensions = _dimensions; renderTransform = _renderTransform; - forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD; }); if (!_visible || color.a == 0.0f) { return; } + bool forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD; + auto batch = args->_batch; Transform transform; @@ -103,6 +103,8 @@ void GridEntityRenderer::doRender(RenderArgs* args) { } else { transform.setTranslation(renderTransform.getTranslation()); } + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch->setModelTransform(transform); auto minCorner = glm::vec2(-0.5f, -0.5f); diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.h b/libraries/entities-renderer/src/RenderableGridEntityItem.h index 1958d17136..fd5b397424 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.h +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.h @@ -23,7 +23,7 @@ public: ~GridEntityRenderer(); protected: - Item::Bound getBound() override; + Item::Bound getBound(RenderArgs* args) override; ShapeKey getShapeKey() override; bool isTransparent() const override; diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index b9b30ea9c7..e03655f09c 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -58,7 +58,6 @@ void ImageEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint _color = entity->getColor(); _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); - _billboardMode = entity->getBillboardMode(); if (!_textureIsLoaded) { emit requestRenderUpdate(); @@ -66,17 +65,6 @@ void ImageEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint _textureIsLoaded = _texture && (_texture->isLoaded() || _texture->isFailed()); } -Item::Bound ImageEntityRenderer::getBound() { - auto bound = Parent::getBound(); - if (_billboardMode != BillboardMode::NONE) { - glm::vec3 dimensions = bound.getScale(); - float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); - const float SQRT_2 = 1.41421356237f; - bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); - } - return bound; -} - ShapeKey ImageEntityRenderer::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); if (isTransparent()) { @@ -109,7 +97,8 @@ void ImageEntityRenderer::doRender(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch* batch = args->_batch; - transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->getViewFrustum().getPosition())); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch->setModelTransform(transform); batch->setResourceTexture(0, _texture->getGPUTexture()); diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.h b/libraries/entities-renderer/src/RenderableImageEntityItem.h index 35d60a230f..2359dcc6d1 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.h +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.h @@ -23,7 +23,6 @@ public: ~ImageEntityRenderer(); protected: - Item::Bound getBound() override; ShapeKey getShapeKey() override; bool isTransparent() const override; @@ -44,7 +43,6 @@ private: glm::u8vec3 _color; float _alpha; PulsePropertyGroup _pulseProperties; - BillboardMode _billboardMode; int _geometryId { 0 }; }; diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index 9eb2e8428d..2ab2051fcf 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -60,8 +60,8 @@ ItemKey LightEntityRenderer::getKey() { return payloadGetKey(_lightPayload); } -Item::Bound LightEntityRenderer::getBound() { - return payloadGetBound(_lightPayload); +Item::Bound LightEntityRenderer::getBound(RenderArgs* args) { + return payloadGetBound(_lightPayload, args); } void LightEntityRenderer::doRender(RenderArgs* args) { diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.h b/libraries/entities-renderer/src/RenderableLightEntityItem.h index 323f280795..bbb5ec346a 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.h +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.h @@ -29,7 +29,7 @@ protected: virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual ItemKey getKey() override; - virtual Item::Bound getBound() override; + virtual Item::Bound getBound(RenderArgs* args) override; virtual void doRender(RenderArgs* args) override; private: diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 6e2be1b41e..a36cdde212 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -47,7 +47,8 @@ void LineEntityRenderer::doRender(RenderArgs* args) { const auto& modelTransform = getModelTransform(); Transform transform = Transform(); transform.setTranslation(modelTransform.getTranslation()); - transform.setRotation(modelTransform.getRotation()); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); if (_linePoints.size() > 1) { DependencyManager::get()->bindSimpleProgram(batch, false, false, false, false, true, diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 3d2e59518c..714defe817 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -259,9 +259,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot))); textureTransform.setScale(glm::vec3(_materialMappingScale, 1)); - Transform renderTransform; + Transform transform; withReadLock([&] { - renderTransform = _renderTransform; + transform = _renderTransform; }); if (!drawMaterial) { @@ -272,7 +272,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { proceduralRender = true; } - batch.setModelTransform(renderTransform); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + batch.setModelTransform(transform); if (!proceduralRender) { drawMaterial->setTextureTransforms(textureTransform, MaterialMappingMode::UV, true); @@ -287,8 +289,8 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { auto proceduralDrawMaterial = std::static_pointer_cast(drawMaterial); glm::vec4 outColor = glm::vec4(drawMaterial->getAlbedo(), drawMaterial->getOpacity()); outColor = proceduralDrawMaterial->getColor(outColor); - proceduralDrawMaterial->prepare(batch, renderTransform.getTranslation(), renderTransform.getScale(), - renderTransform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f)); + proceduralDrawMaterial->prepare(batch, transform.getTranslation(), transform.getScale(), + transform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f)); if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) { DependencyManager::get()->renderWireSphere(batch, outColor); } else { @@ -374,7 +376,7 @@ void MaterialEntityRenderer::applyMaterial(const TypedEntityPointer& entity) { if (material->isProcedural()) { auto procedural = std::static_pointer_cast(material); - procedural->setBoundOperator([this] { return getBound(); }); + procedural->setBoundOperator([this](RenderArgs* args) { return getBound(args); }); entity->setHasVertexShader(procedural->hasVertexShader()); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e13ab6f837..3c245e5d4f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -115,11 +115,12 @@ bool RenderableModelEntityItem::needsUpdateModelBounds() const { } bool success; - auto transform = getTransform(success); + auto transform = getBillboardMode() == BillboardMode::NONE ? getTransform(success) : getTransformWithOnlyLocalRotation(success); if (success) { if (model->getTranslation() != transform.getTranslation()) { return true; } + if (model->getRotation() != transform.getRotation()) { return true; } @@ -149,10 +150,12 @@ void RenderableModelEntityItem::updateModelBounds() { bool overridingModelTransform = model->isOverridingModelTransformAndOffset(); glm::vec3 scaledDimensions = getScaledDimensions(); glm::vec3 registrationPoint = getRegistrationPoint(); + bool needsSimulate = false; if (!overridingModelTransform && (model->getScaleToFitDimensions() != scaledDimensions || - model->getRegistrationPoint() != registrationPoint || - !model->getIsScaledToFit() || _needsToRescaleModel)) { + model->getRegistrationPoint() != registrationPoint || + !model->getIsScaledToFit() || _needsToRescaleModel || + _useOriginalPivot == model->getSnapModelToRegistrationPoint())) { // The machinery for updateModelBounds will give existing models the opportunity to fix their // translation/rotation/scale/registration. The first two are straightforward, but the latter two // have guards to make sure they don't happen after they've already been set. Here we reset those guards. @@ -162,24 +165,25 @@ void RenderableModelEntityItem::updateModelBounds() { // now recalculate the bounds and registration model->setScaleToFit(true, scaledDimensions); - model->setSnapModelToRegistrationPoint(true, registrationPoint); + model->setSnapModelToRegistrationPoint(!_useOriginalPivot, registrationPoint); updateRenderItems = true; - model->scaleToFit(); + needsSimulate = true; _needsToRescaleModel = false; } bool success; - auto transform = getTransform(success); + auto transform = getBillboardMode() == BillboardMode::NONE ? getTransform(success) : getTransformWithOnlyLocalRotation(success); if (success && (model->getTranslation() != transform.getTranslation() || model->getRotation() != transform.getRotation())) { model->setTransformNoUpdateRenderItems(transform); updateRenderItems = true; } - if (_needsInitialSimulation || _needsJointSimulation || isAnimatingSomething()) { + if (_needsInitialSimulation || _needsJointSimulation || needsSimulate || isAnimatingSomething()) { // NOTE: on isAnimatingSomething() we need to call Model::simulate() which calls Rig::updateRig() // TODO: there is opportunity to further optimize the isAnimatingSomething() case. model->simulate(0.0f); + locationChanged(); _needsInitialSimulation = false; _needsJointSimulation = false; updateRenderItems = true; @@ -189,9 +193,6 @@ void RenderableModelEntityItem::updateModelBounds() { glm::vec3 scale = model->getScale(); model->setUseDualQuaternionSkinning(!isNonUniformScale(scale)); model->updateRenderItems(); - - markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); - locationChanged(); } } @@ -222,31 +223,41 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper return properties; } +glm::vec3 RenderableModelEntityItem::getPivot() const { + auto model = getModel(); + auto pivot = EntityItem::getPivot(); + if (!model || !model->isLoaded() || !_useOriginalPivot) { + return pivot; + } + + return pivot + model->getOriginalOffset(); +} + bool RenderableModelEntityItem::supportsDetailedIntersection() const { return true; } bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, BoxFace& face, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { auto model = getModel(); if (!model || !model->isLoaded()) { return false; } - return model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, + return model->findRayIntersectionAgainstSubMeshes(origin, direction, viewFrustumPos, distance, face, surfaceNormal, extraInfo, precisionPicking, false); } bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, - glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { auto model = getModel(); if (!model || !model->isLoaded()) { return false; } - return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, + return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, viewFrustumPos, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking, false); } @@ -446,14 +457,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); + glm::vec3 offset = model->getSnapModelToRegistrationPoint() ? model->getOffset() : glm::vec3(0.0f); for (int32_t i = 0; i < pointCollection.size(); i++) { for (int32_t j = 0; j < pointCollection[i].size(); j++) { // back compensate for registration so we can apply that offset to the shapeInfo later - pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset; + pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + offset) - registrationOffset; } } - shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL()); - adjustShapeInfoByRegistration(shapeInfo); + shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL() + model->getSnapModelToRegistrationPoint()); + adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint()); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { updateModelBounds(); model->updateGeometry(); @@ -685,8 +697,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } } - shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL()); - adjustShapeInfoByRegistration(shapeInfo); + shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL() + model->getSnapModelToRegistrationPoint()); + adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint()); } else { EntityItem::computeShapeInfo(shapeInfo); } @@ -1257,6 +1269,7 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint _model->setTagMask(getTagMask(), scene); _model->setHifiRenderLayer(getHifiRenderLayer(), scene); _model->setPrimitiveMode(_primitiveMode, scene); + _model->setBillboardMode(_billboardMode, scene); _model->setCullWithParent(_cullWithParent, scene); _model->setRenderWithZones(_renderWithZones, scene); }); @@ -1267,6 +1280,9 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint entity->_originalTexturesRead = false; entity->_needsJointSimulation = true; entity->_needsToRescaleModel = true; + + entity->markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); + entity->locationChanged(); emit requestRenderUpdate(); }); scene->enqueueTransaction(transaction); @@ -1332,6 +1348,7 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint model->setTagMask(getTagMask(), scene); model->setHifiRenderLayer(getHifiRenderLayer(), scene); model->setPrimitiveMode(_primitiveMode, scene); + model->setBillboardMode(_billboardMode, scene); model->setCullWithParent(_cullWithParent, scene); model->setRenderWithZones(_renderWithZones, scene); }); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 4501f6d88c..f394d389f5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -42,7 +42,7 @@ protected: void setModel(const ModelPointer& model); ModelPointer getModel() const; - bool _needsInitialSimulation{ true }; + bool _needsInitialSimulation { true }; private: ModelPointer _model; }; @@ -63,14 +63,15 @@ public: virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; void updateModelBounds(); + glm::vec3 getPivot() const override; virtual bool supportsDetailedIntersection() const override; virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setShapeType(ShapeType type) override; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index e53e52d105..e2a57840d9 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -161,7 +161,7 @@ ShapeKey ParticleEffectEntityRenderer::getShapeKey() { return builder.build(); } -Item::Bound ParticleEffectEntityRenderer::getBound() { +Item::Bound ParticleEffectEntityRenderer::getBound(RenderArgs* args) { return _bound; } @@ -456,6 +456,7 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created); batch.setModelTransform(transform); + batch.setUniformBuffer(0, _uniformBuffer); batch.setInputFormat(_vertexFormat); batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle)); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index ca6bc859fb..547d654486 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -30,7 +30,7 @@ protected: virtual ItemKey getKey() override; virtual ShapeKey getShapeKey() override; - virtual Item::Bound getBound() override; + virtual Item::Bound getBound(RenderArgs* args) override; virtual void doRender(RenderArgs* args) override; private: diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index f98a54d594..e4bb6952a7 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -41,13 +41,13 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) } } -void PolyLineEntityRenderer::updateModelTransformAndBound() { +void PolyLineEntityRenderer::updateModelTransformAndBound(const EntityItemPointer& entity) { bool success = false; - auto newModelTransform = _entity->getTransformToCenter(success); + auto newModelTransform = getTransformToCenterWithMaybeOnlyLocalRotation(entity, success); if (success) { _modelTransform = newModelTransform; - auto lineEntity = std::static_pointer_cast(_entity); + auto lineEntity = std::static_pointer_cast(entity); AABox bound; lineEntity->computeTightLocalBoundingBox(bound); bound.transform(newModelTransform); @@ -325,8 +325,11 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { buildPipelines(); } - batch.setPipeline(_pipelines[{args->_renderMethod, isTransparent()}]); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); + + batch.setPipeline(_pipelines[{args->_renderMethod, isTransparent()}]); batch.setResourceTexture(0, texture); batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * _numVertices), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index c4fbb9a776..6e5068c24f 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,7 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer { public: PolyLineEntityRenderer(const EntityItemPointer& entity); - void updateModelTransformAndBound() override; + void updateModelTransformAndBound(const EntityItemPointer& entity) override; virtual bool isTransparent() const override; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index f0a6684654..5c8374b937 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -238,53 +238,6 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel startUpdates(); } -glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { - glm::vec3 result; - withReadLock([&] { - glm::vec3 scale = getScaledDimensions() / _voxelVolumeSize; // meters / voxel-units - if (isEdged(_voxelSurfaceStyle)) { - result = scale / -2.0f; - } - return scale / 2.0f; - }); - return result; -} - -glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { - glm::vec3 voxelVolumeSize; - withReadLock([&] { - voxelVolumeSize = _voxelVolumeSize; - }); - - glm::vec3 dimensions = getScaledDimensions(); - glm::vec3 scale = dimensions / voxelVolumeSize; // meters / voxel-units - bool success; // TODO -- Does this actually have to happen in world space? - glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes - glm::vec3 position = getWorldPosition(success); - glm::vec3 positionToCenter = center - position; - - positionToCenter -= dimensions * Vectors::HALF - getSurfacePositionAdjustment(); - glm::mat4 centerToCorner = glm::translate(glm::mat4(), positionToCenter); - glm::mat4 scaled = glm::scale(centerToCorner, scale); - return scaled; -} - -glm::mat4 RenderablePolyVoxEntityItem::localToVoxelMatrix() const { - glm::mat4 localToModelMatrix = glm::inverse(voxelToLocalMatrix()); - return localToModelMatrix; -} - -glm::mat4 RenderablePolyVoxEntityItem::voxelToWorldMatrix() const { - glm::mat4 rotation = glm::mat4_cast(getWorldOrientation()); - glm::mat4 translation = glm::translate(getWorldPosition()); - return translation * rotation * voxelToLocalMatrix(); -} - -glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { - glm::mat4 worldToModelMatrix = glm::inverse(voxelToWorldMatrix()); - return worldToModelMatrix; -} - bool RenderablePolyVoxEntityItem::setVoxel(const ivec3& v, uint8_t toValue) { if (_locked) { return false; @@ -573,7 +526,7 @@ public: #endif bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes @@ -582,7 +535,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return true; } - glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::mat4 wtvMatrix = worldToVoxelMatrix(true); glm::vec3 normDirection = glm::normalize(direction); // the PolyVox ray intersection code requires a near and far point. @@ -614,7 +567,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes @@ -623,7 +576,7 @@ bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::ve return true; } - glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::mat4 wtvMatrix = worldToVoxelMatrix(true); glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); glm::vec4 velocityInVoxel = wtvMatrix * glm::vec4(velocity, 0.0f); glm::vec4 accelerationInVoxel = wtvMatrix * glm::vec4(acceleration, 0.0f); @@ -723,8 +676,10 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const { } void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) { - if (value != _registrationPoint) { - _shapeReady = false; + if (value != getRegistrationPoint()) { + withWriteLock([&] { + _shapeReady = false; + }); EntityItem::setRegistrationPoint(value); startUpdates(); } @@ -1803,7 +1758,7 @@ ShapeKey PolyVoxEntityRenderer::getShapeKey() { bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (resultWithReadLock([&] { - if (entity->voxelToWorldMatrix() != _lastVoxelToWorldMatrix) { + if (entity->voxelToLocalMatrix() != _lastVoxelToLocalMatrix) { return true; } @@ -1829,7 +1784,10 @@ void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& s } void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - _lastVoxelToWorldMatrix = entity->voxelToWorldMatrix(); + _lastVoxelToLocalMatrix = entity->voxelToLocalMatrix(); + bool success; + _position = entity->getCenterPosition(success); + _orientation = entity->getBillboardMode() == BillboardMode::NONE ? entity->getWorldOrientation() : entity->getLocalOrientation(); _lastVoxelVolumeSize = entity->getVoxelVolumeSize(); _params->setSubData(0, vec4(_lastVoxelVolumeSize, 0.0)); graphics::MeshPointer newMesh; @@ -1862,17 +1820,18 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { return; } - PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); gpu::Batch& batch = *args->_batch; - Transform transform(_lastVoxelToWorldMatrix); + glm::mat4 rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(_position, _orientation, _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + Transform transform(glm::translate(_position) * rotation * _lastVoxelToLocalMatrix); batch.setModelTransform(transform); + batch.setInputFormat(_vertexFormat); batch.setInputBuffer(gpu::Stream::POSITION, _mesh->getVertexBuffer()._buffer, 0, sizeof(PolyVox::PositionMaterialNormal)); - // TODO -- should we be setting this? // batch.setInputBuffer(gpu::Stream::NORMAL, mesh->getVertexBuffer()._buffer, // 12, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 825b4429cd..c1c35a21c8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -71,24 +71,18 @@ public: virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const vec3& accleration, - OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setVoxelData(const QByteArray& voxelData) override; virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override; virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) override; - glm::vec3 getSurfacePositionAdjustment() const; - glm::mat4 voxelToWorldMatrix() const; - glm::mat4 worldToVoxelMatrix() const; - glm::mat4 voxelToLocalMatrix() const; - glm::mat4 localToVoxelMatrix() const; - virtual ShapeType getShapeType() const override; virtual bool isReadyToComputeShape() const override; virtual void computeShapeInfo(ShapeInfo& info) override; @@ -226,7 +220,9 @@ private: gpu::BufferPointer _params; std::array _xyzTextures; glm::vec3 _lastVoxelVolumeSize; - glm::mat4 _lastVoxelToWorldMatrix; + glm::mat4 _lastVoxelToLocalMatrix; + glm::vec3 _position; + glm::quat _orientation; PolyVoxEntityItem::PolyVoxSurfaceStyle _lastSurfaceStyle { PolyVoxEntityItem::SURFACE_MARCHING_CUBES }; std::array _xyzTextureUrls; }; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 06ccdef753..35739c2430 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -55,21 +55,18 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce void* key = (void*)this; AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { withWriteLock([&] { - _position = entity->getWorldPosition(); - _dimensions = entity->getUnscaledDimensions(); // get unscaled to avoid scaling twice - _orientation = entity->getWorldOrientation(); + _shape = entity->getShape(); _renderTransform = getModelTransform(); // contains parent scale, if this entity scales with its parent if (_shape == entity::Sphere) { _renderTransform.postScale(SPHERE_ENTITY_SCALE); } - _renderTransform.postScale(_dimensions); + _renderTransform.postScale(entity->getUnscaledDimensions()); }); }); } void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - _shape = entity->getShape(); _pulseProperties = entity->getPulseProperties(); bool materialChanged = false; @@ -200,16 +197,16 @@ ShapeKey ShapeEntityRenderer::getShapeKey() { return builder.build(); } -Item::Bound ShapeEntityRenderer::getBound() { +Item::Bound ShapeEntityRenderer::getBound(RenderArgs* args) { auto mat = _materials.find("0"); if (mat != _materials.end() && mat->second.top().material && mat->second.top().material->isProcedural() && mat->second.top().material->isReady()) { auto procedural = std::static_pointer_cast(mat->second.top().material); if (procedural->hasVertexShader() && procedural->hasBoundOperator()) { - return procedural->getBound(); + return procedural->getBound(args); } } - return Parent::getBound(); + return Parent::getBound(args); } void ShapeEntityRenderer::doRender(RenderArgs* args) { @@ -221,14 +218,11 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { graphics::MultiMaterial materials; auto geometryCache = DependencyManager::get(); GeometryCache::Shape geometryShape = geometryCache->getShapeForEntityShape(_shape); - PrimitiveMode primitiveMode; - RenderLayer renderLayer; glm::vec4 outColor; Pipeline pipelineType; + Transform transform; withReadLock([&] { - primitiveMode = _primitiveMode; - renderLayer = _renderLayer; - batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation + transform = _renderTransform; materials = _materials["0"]; pipelineType = getPipelineType(materials); auto& schema = materials.getSchemaBuffer().get(); @@ -241,15 +235,20 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { return; } + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), + _shape < entity::Shape::Cube || _shape > entity::Shape::Icosahedron)); + batch.setModelTransform(transform); + if (pipelineType == Pipeline::PROCEDURAL) { auto procedural = std::static_pointer_cast(materials.top().material); outColor = procedural->getColor(outColor); outColor.a *= procedural->isFading() ? Interpolate::calculateFadeRatio(procedural->getFadeStartTime()) : 1.0f; withReadLock([&] { - procedural->prepare(batch, _position, _dimensions, _orientation, _created, ProceduralProgramKey(outColor.a < 1.0f)); + procedural->prepare(batch, transform.getTranslation(), transform.getScale(), transform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f)); }); - if (render::ShapeKey(args->_globalShapeKey).isWireframe() || primitiveMode == PrimitiveMode::LINES) { + if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) { geometryCache->renderWireShape(batch, geometryShape, outColor); } else { geometryCache->renderShape(batch, geometryShape, outColor); @@ -258,8 +257,8 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { // FIXME, support instanced multi-shape rendering using multidraw indirect outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; render::ShapePipelinePointer pipeline = geometryCache->getShapePipelinePointer(outColor.a < 1.0f, false, - renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD, materials.top().material->getCullFaceMode()); - if (render::ShapeKey(args->_globalShapeKey).isWireframe() || primitiveMode == PrimitiveMode::LINES) { + _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD, materials.top().material->getCullFaceMode()); + if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) { geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, pipeline); } else { geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 9fb2fa8f23..403d389378 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -26,7 +26,7 @@ public: protected: ShapeKey getShapeKey() override; - Item::Bound getBound() override; + Item::Bound getBound(RenderArgs* args) override; private: virtual bool needsRenderUpdate() const override; @@ -45,10 +45,6 @@ private: std::shared_ptr _material { std::make_shared() }; glm::vec3 _color { NAN }; float _alpha { NAN }; - - glm::vec3 _position; - glm::vec3 _dimensions; - glm::quat _orientation; }; } } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 04ad86c7e1..4248d3f2cd 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -54,17 +54,6 @@ bool TextEntityRenderer::isTextTransparent() const { }); } -Item::Bound TextEntityRenderer::getBound() { - auto bound = Parent::getBound(); - if (_billboardMode != BillboardMode::NONE) { - glm::vec3 dimensions = bound.getScale(); - float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); - const float SQRT_2 = 1.41421356237f; - bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); - } - return bound; -} - ItemKey TextEntityRenderer::getKey() { return ItemKey::Builder(Parent::getKey()).withMetaCullGroup(); } @@ -111,7 +100,6 @@ void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe _textAlpha = entity->getTextAlpha(); _backgroundColor = toGlm(entity->getBackgroundColor()); _backgroundAlpha = entity->getBackgroundAlpha(); - _billboardMode = entity->getBillboardMode(); _leftMargin = entity->getLeftMargin(); _rightMargin = entity->getRightMargin(); _topMargin = entity->getTopMargin(); @@ -121,6 +109,7 @@ void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe _effect = entity->getTextEffect(); _effectColor = toGlm(entity->getTextEffectColor()); _effectThickness = entity->getTextEffectThickness(); + _alignment = entity->getAlignment(); updateTextRenderItem(); } @@ -130,13 +119,9 @@ void TextEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; glm::vec4 backgroundColor; - Transform modelTransform; - PrimitiveMode primitiveMode; - RenderLayer renderLayer; + Transform transform; withReadLock([&] { - modelTransform = _renderTransform; - primitiveMode = _primitiveMode; - renderLayer = _renderLayer; + transform = _renderTransform; float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); @@ -147,14 +132,15 @@ void TextEntityRenderer::doRender(RenderArgs* args) { return; } - modelTransform.setRotation(EntityItem::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), _billboardMode, args->getViewFrustum().getPosition())); - batch.setModelTransform(modelTransform); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + batch.setModelTransform(transform); auto geometryCache = DependencyManager::get(); // FIXME: we want to use instanced rendering here, but if textAlpha < 1 and backgroundAlpha < 1, the transparency sorting will be wrong //render::ShapePipelinePointer pipeline = geometryCache->getShapePipelinePointer(backgroundColor.a < 1.0f, _unlit, - // renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD); - //if (render::ShapeKey(args->_globalShapeKey).isWireframe() || primitiveMode == PrimitiveMode::LINES) { + // _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD); + //if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) { // geometryCache->renderWireShapeInstance(args, batch, GeometryCache::Quad, backgroundColor, pipeline); //} else { // geometryCache->renderSolidShapeInstance(args, batch, GeometryCache::Quad, backgroundColor, pipeline); @@ -242,12 +228,12 @@ ItemKey entities::TextPayload::getKey() const { return ItemKey::Builder::opaqueShape(); } -Item::Bound entities::TextPayload::getBound() const { +Item::Bound entities::TextPayload::getBound(RenderArgs* args) const { auto entityTreeRenderer = DependencyManager::get(); if (entityTreeRenderer) { auto renderable = entityTreeRenderer->renderableForEntityId(_entityID); if (renderable) { - return std::static_pointer_cast(renderable)->getBound(); + return std::static_pointer_cast(renderable)->getBound(args); } } return Item::Bound(); @@ -307,23 +293,20 @@ void entities::TextPayload::render(RenderArgs* args) { } auto textRenderable = std::static_pointer_cast(renderable); - Transform modelTransform; + Transform transform; glm::vec3 dimensions; - BillboardMode billboardMode; glm::vec4 textColor; - bool forward; textRenderable->withReadLock([&] { - modelTransform = textRenderable->_renderTransform; + transform = textRenderable->_renderTransform; dimensions = textRenderable->_dimensions; - billboardMode = textRenderable->_billboardMode; float fadeRatio = textRenderable->_isFading ? Interpolate::calculateFadeRatio(textRenderable->_fadeStartTime) : 1.0f; textColor = glm::vec4(textRenderable->_textColor, fadeRatio * textRenderable->_textAlpha); - - forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; }); + bool forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; + textColor = EntityRenderer::calculatePulseColor(textColor, textRenderable->_pulseProperties, textRenderable->_created); glm::vec3 effectColor = EntityRenderer::calculatePulseColor(textRenderable->_effectColor, textRenderable->_pulseProperties, textRenderable->_created); @@ -331,17 +314,18 @@ void entities::TextPayload::render(RenderArgs* args) { return; } - modelTransform.setRotation(EntityItem::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), billboardMode, args->getViewFrustum().getPosition())); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), textRenderable->_billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); float scale = textRenderable->_lineHeight / textRenderer->getFontSize(); - modelTransform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z)); - modelTransform.setScale(scale); - batch.setModelTransform(modelTransform); + transform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z)); + transform.setScale(scale); + batch.setModelTransform(transform); glm::vec2 bounds = glm::vec2(dimensions.x - (textRenderable->_leftMargin + textRenderable->_rightMargin), dimensions.y - (textRenderable->_topMargin + textRenderable->_bottomMargin)); textRenderer->draw(batch, textRenderable->_leftMargin / scale, -textRenderable->_topMargin / scale, bounds / scale, scale, textRenderable->_text, textRenderable->_font, textColor, effectColor, textRenderable->_effectThickness, textRenderable->_effect, - textRenderable->_unlit, forward); + textRenderable->_alignment, textRenderable->_unlit, forward); } namespace render { @@ -352,9 +336,9 @@ template <> const ItemKey payloadGetKey(const TextPayload::Pointer& payload) { return ItemKey::Builder::opaqueShape(); } -template <> const Item::Bound payloadGetBound(const TextPayload::Pointer& payload) { +template <> const Item::Bound payloadGetBound(const TextPayload::Pointer& payload, RenderArgs* args) { if (payload) { - return payload->getBound(); + return payload->getBound(args); } return Item::Bound(); } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 51651d98ba..c4cfaab9a2 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -33,7 +33,6 @@ public: protected: bool isTransparent() const override; bool isTextTransparent() const; - Item::Bound getBound() override; ShapeKey getShapeKey() override; ItemKey getKey() override; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override; @@ -63,10 +62,10 @@ private: float _topMargin; float _bottomMargin; - BillboardMode _billboardMode; glm::vec3 _dimensions; QString _font { "" }; + TextAlignment _alignment { TextAlignment::LEFT }; TextEffect _effect { TextEffect::NO_EFFECT }; glm::vec3 _effectColor { 0 }; float _effectThickness { 0.0f }; @@ -90,7 +89,7 @@ public: typedef Payload::DataPointer Pointer; ItemKey getKey() const; - Item::Bound getBound() const; + Item::Bound getBound(RenderArgs* args) const; ShapeKey getShapeKey() const; void render(RenderArgs* args); bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const; @@ -106,7 +105,7 @@ protected: namespace render { template <> const ItemKey payloadGetKey(const entities::TextPayload::Pointer& payload); - template <> const Item::Bound payloadGetBound(const entities::TextPayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const entities::TextPayload::Pointer& payload, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const entities::TextPayload::Pointer& payload); template <> void payloadRender(const entities::TextPayload::Pointer& payload, RenderArgs* args); template <> bool payloadPassesZoneOcclusionTest(const entities::TextPayload::Pointer& payload, const std::unordered_set& containingZones); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 8b41a85cf8..7932a19110 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -39,6 +39,7 @@ const char* WebEntityRenderer::URL_PROPERTY = "url"; const char* WebEntityRenderer::SCRIPT_URL_PROPERTY = "scriptURL"; const char* WebEntityRenderer::GLOBAL_POSITION_PROPERTY = "globalPosition"; const char* WebEntityRenderer::USE_BACKGROUND_PROPERTY = "useBackground"; +const char* WebEntityRenderer::USER_AGENT_PROPERTY = "userAgent"; std::function&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr; std::function&, bool&, std::vector&)> WebEntityRenderer::_releaseWebSurfaceOperator = nullptr; @@ -167,7 +168,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _color = entity->getColor(); _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); - _billboardMode = entity->getBillboardMode(); if (_contentType == ContentType::NoContent) { _tryingToBuildURL = newSourceURL; @@ -194,6 +194,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); _webSurface->getRootItem()->setProperty(SCRIPT_URL_PROPERTY, _scriptURL); _webSurface->getRootItem()->setProperty(USE_BACKGROUND_PROPERTY, _useBackground); + _webSurface->getRootItem()->setProperty(USER_AGENT_PROPERTY, _userAgent); _webSurface->getSurfaceContext()->setContextProperty(GLOBAL_POSITION_PROPERTY, vec3toVariant(_contextPosition)); _webSurface->setMaxFps((QUrl(newSourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) ? YOUTUBE_MAX_FPS : _maxFPS); ::hifi::scripting::setLocalAccessSafeThread(false); @@ -231,6 +232,14 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _useBackground = useBackground; } } + + { + auto userAgent = entity->getUserAgent(); + if (_userAgent != userAgent) { + _webSurface->getRootItem()->setProperty(USER_AGENT_PROPERTY, userAgent); + _userAgent = userAgent; + } + } { auto contextPosition = entity->getWorldPosition(); @@ -257,17 +266,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene }); } -Item::Bound WebEntityRenderer::getBound() { - auto bound = Parent::getBound(); - if (_billboardMode != BillboardMode::NONE) { - glm::vec3 dimensions = bound.getScale(); - float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); - const float SQRT_2 = 1.41421356237f; - bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); - } - return bound; -} - void WebEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("WebEntityRenderer::render"); withWriteLock([&] { @@ -297,14 +295,12 @@ void WebEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; glm::vec4 color; Transform transform; - bool forward; bool transparent; withReadLock([&] { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; color = glm::vec4(toGlm(_color), _alpha * fadeRatio); color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); transform = _renderTransform; - forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; transparent = isTransparent(); }); @@ -312,9 +308,12 @@ void WebEntityRenderer::doRender(RenderArgs* args) { return; } + bool forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; + batch.setResourceTexture(0, _texture); - transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->getViewFrustum().getPosition())); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); batch.setModelTransform(transform); // Turn off jitter for these entities diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 63ec722811..1ac415e5e5 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -36,6 +36,7 @@ public: static const char* SCRIPT_URL_PROPERTY; static const char* GLOBAL_POSITION_PROPERTY; static const char* USE_BACKGROUND_PROPERTY; + static const char* USER_AGENT_PROPERTY; static void setAcquireWebSurfaceOperator(std::function&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; } static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { @@ -59,7 +60,6 @@ protected: virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; - Item::Bound getBound() override; virtual bool wantsHandControllerPointerEvents() const override { return true; } virtual bool wantsKeyboardFocus() const override { return true; } @@ -90,13 +90,13 @@ private: glm::u8vec3 _color; float _alpha { 1.0f }; PulsePropertyGroup _pulseProperties; - BillboardMode _billboardMode; QString _sourceURL; uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; bool _useBackground; + QString _userAgent; WebInputMode _inputMode; glm::vec3 _contextPosition; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d7a5e992e1..6e761698c9 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -51,9 +51,6 @@ int EntityItem::_maxActionsDataSize = 800; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; QString EntityItem::_marketplacePublicKey; -std::function EntityItem::_getBillboardRotationOperator = [](const glm::vec3&, const glm::quat& rotation, BillboardMode, const glm::vec3&) { return rotation; }; -std::function EntityItem::_getPrimaryViewFrustumPositionOperator = []() { return glm::vec3(0.0f); }; - EntityItem::EntityItem(const EntityItemID& entityItemID) : SpatiallyNestable(NestableType::Entity, entityItemID) { @@ -107,6 +104,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_PRIMITIVE_MODE; requestedProperties += PROP_IGNORE_PICK_INTERSECTION; requestedProperties += PROP_RENDER_WITH_ZONES; + requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += _grabProperties.getEntityProperties(params); // Physics @@ -303,6 +301,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)getPrimitiveMode()); APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, getIgnorePickIntersection()); APPEND_ENTITY_PROPERTY(PROP_RENDER_WITH_ZONES, getRenderWithZones()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); withReadLock([&] { _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -875,6 +874,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode); READ_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection); READ_ENTITY_PROPERTY(PROP_RENDER_WITH_ZONES, QVector, setRenderWithZones); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); withWriteLock([&] { int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, @@ -1358,6 +1358,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(primitiveMode, getPrimitiveMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignorePickIntersection, getIgnorePickIntersection); COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderWithZones, getRenderWithZones); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); withReadLock([&] { _grabProperties.getProperties(properties); }); @@ -1508,6 +1509,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(primitiveMode, setPrimitiveMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignorePickIntersection, setIgnorePickIntersection); SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderWithZones, setRenderWithZones); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); withWriteLock([&] { bool grabPropertiesChanged = _grabProperties.setProperties(properties); somethingChanged |= grabPropertiesChanged; @@ -1607,8 +1609,26 @@ void EntityItem::recordCreationTime() { const Transform EntityItem::getTransformToCenter(bool& success) const { Transform result = getTransform(success); - if (getRegistrationPoint() != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center - result.postTranslate((ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()) * getScaledDimensions()); // Position to center + glm::vec3 pivot = getPivot(); + if (pivot != ENTITY_ITEM_ZERO_VEC3) { + result.postTranslate(pivot); + } + glm::vec3 registrationPoint = getRegistrationPoint(); + if (registrationPoint != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center + result.postTranslate((ENTITY_ITEM_HALF_VEC3 - registrationPoint) * getScaledDimensions()); // Position to center + } + return result; +} + +const Transform EntityItem::getTransformToCenterWithOnlyLocalRotation(bool& success) const { + Transform result = getTransformWithOnlyLocalRotation(success); + glm::vec3 pivot = getPivot(); + if (pivot != ENTITY_ITEM_ZERO_VEC3) { + result.postTranslate(pivot); + } + glm::vec3 registrationPoint = getRegistrationPoint(); + if (registrationPoint != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center + result.postTranslate((ENTITY_ITEM_HALF_VEC3 - registrationPoint) * getScaledDimensions()); // Position to center } return result; } @@ -1623,15 +1643,16 @@ AACube EntityItem::getMaximumAACube(bool& success) const { _recalcMaxAACube = false; // 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 = getScaledDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint); + // and then scale by dimensions and add the absolute value of the pivot + glm::vec3 registrationPoint = getRegistrationPoint(); + glm::vec3 maxExtents = getScaledDimensions() * glm::max(registrationPoint, glm::vec3(1.0f) - registrationPoint); // there exists a sphere that contains maxExtents for all rotations float radius = glm::length(maxExtents); // put a cube around the sphere // TODO? replace _maxAACube with _boundingSphereRadius - glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); + glm::vec3 minimumCorner = (centerOfRotation + getWorldOrientation() * getPivot()) - glm::vec3(radius, radius, radius); _maxAACube = AACube(minimumCorner, radius * 2.0f); } } else { @@ -1650,9 +1671,12 @@ AACube EntityItem::getMinimumAACube(bool& success) const { if (success) { _recalcMinAACube = false; glm::vec3 dimensions = getScaledDimensions(); - glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint); - glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint); + glm::vec3 registrationPoint = getRegistrationPoint(); + glm::vec3 pivot = getPivot(); + glm::vec3 unrotatedMinRelativeToEntity = -(dimensions * registrationPoint); + glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (ENTITY_ITEM_ONE_VEC3 - registrationPoint); Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; + extents.shiftBy(pivot); extents.rotate(getWorldOrientation()); // shift the extents to be relative to the position/registration point @@ -1680,9 +1704,12 @@ AABox EntityItem::getAABox(bool& success) const { if (success) { _recalcAABox = false; glm::vec3 dimensions = getScaledDimensions(); - glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint); - glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint); + glm::vec3 registrationPoint = getRegistrationPoint(); + glm::vec3 pivot = getPivot(); + glm::vec3 unrotatedMinRelativeToEntity = -(dimensions * registrationPoint); + glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (ENTITY_ITEM_ONE_VEC3 - registrationPoint); Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; + extents.shiftBy(pivot); extents.rotate(getWorldOrientation()); // shift the extents to be relative to the position/registration point @@ -1722,12 +1749,22 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(getScaledDimensions()); } -void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { - if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { - glm::mat4 scale = glm::scale(getScaledDimensions()); - glm::mat4 registration = scale * glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); - glm::vec3 regTransVec = glm::vec3(registration[3]); // extract position component from matrix - info.setOffset(regTransVec); +void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot) const { + glm::vec3 offset; + glm::vec3 registrationPoint = getRegistrationPoint(); + if (registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { + offset += (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - registrationPoint) * getScaledDimensions(); + } + + if (includePivot) { + glm::vec3 pivot = getPivot(); + if (pivot != ENTITY_ITEM_ZERO_VEC3) { + offset += pivot; + } + } + + if (offset != ENTITY_ITEM_ZERO_VEC3) { + info.setOffset(offset); } } @@ -1739,7 +1776,7 @@ bool EntityItem::contains(const glm::vec3& point) const { // anything with shapeType == SPHERE must collide as a bounding sphere in the world-frame regardless of dimensions // therefore we must do math using an unscaled localPoint relative to sphere center glm::vec3 dimensions = getScaledDimensions(); - glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()))); + glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()) + getPivot())); const float HALF_SQUARED = 0.25f; return glm::length2(localPoint) < HALF_SQUARED * glm::length2(dimensions); } @@ -1797,11 +1834,16 @@ float EntityItem::getVolumeEstimate() const { } void EntityItem::setRegistrationPoint(const glm::vec3& value) { - if (value != _registrationPoint) { - withWriteLock([&] { + bool changed = false; + withWriteLock([&] { + if (value != _registrationPoint) { _registrationPoint = glm::clamp(value, glm::vec3(ENTITY_ITEM_MIN_REGISTRATION_POINT), glm::vec3(ENTITY_ITEM_MAX_REGISTRATION_POINT)); - }); + changed = true; + } + }); + + if (changed) { dimensionsChanged(); // Registration Point affects the bounding box markDirtyFlags(Simulation::DIRTY_SHAPE); } @@ -2892,11 +2934,9 @@ QString EntityItem::getCollisionSoundURL() const { } glm::vec3 EntityItem::getRegistrationPoint() const { - glm::vec3 result; - withReadLock([&] { - result = _registrationPoint; + return resultWithReadLock([&] { + return _registrationPoint; }); - return result; } float EntityItem::getAngularDamping() const { @@ -3591,4 +3631,17 @@ QVector EntityItem::getRenderWithZones() const { return resultWithReadLock>([&] { return _renderWithZones; }); -} \ No newline at end of file +} + +BillboardMode EntityItem::getBillboardMode() const { + return resultWithReadLock([&] { + return _billboardMode; + }); +} + +void EntityItem::setBillboardMode(BillboardMode value) { + withWriteLock([&] { + _needsRenderUpdate |= _billboardMode != value; + _billboardMode = value; + }); +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 2a6952fc0d..b08817044a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -175,12 +175,12 @@ public: virtual bool supportsDetailedIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return true; } virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return true; } // attributes applicable to all entity types @@ -190,6 +190,7 @@ public: void setCenterPosition(const glm::vec3& position); const Transform getTransformToCenter(bool& success) const; + const Transform getTransformToCenterWithOnlyLocalRotation(bool& success) const; void requiresRecalcBoxes(); @@ -203,7 +204,8 @@ public: /// Dimensions in meters (0.0 - TREE_SCALE) virtual glm::vec3 getScaledDimensions() const; virtual void setScaledDimensions(const glm::vec3& value); - virtual glm::vec3 getRaycastDimensions() const { return getScaledDimensions(); } + + virtual glm::vec3 getPivot() const { return glm::vec3(0.0f); } // pivot offset for positioning, mainly for model entities glm::vec3 getUnscaledDimensions() const; virtual void setUnscaledDimensions(const glm::vec3& value); @@ -403,7 +405,7 @@ public: // TODO: get rid of users of getRadius()... float getRadius() const; - virtual void adjustShapeInfoByRegistration(ShapeInfo& info) const; + virtual void adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot = true) const; virtual bool contains(const glm::vec3& point) const; virtual bool isReadyToComputeShape() const { return !isDead(); } @@ -572,11 +574,6 @@ public: virtual void removeGrab(GrabPointer grab) override; virtual void disableGrab(GrabPointer grab) override; - static void setBillboardRotationOperator(std::function getBillboardRotationOperator) { _getBillboardRotationOperator = getBillboardRotationOperator; } - static glm::quat getBillboardRotation(const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) { return _getBillboardRotationOperator(position, rotation, billboardMode, frustumPos); } - static void setPrimaryViewFrustumPositionOperator(std::function getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; } - static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); } - bool stillHasMyGrab() const; bool needsRenderUpdate() const { return _needsRenderUpdate; } @@ -587,6 +584,10 @@ public: bool needsZoneOcclusionUpdate() const { return _needsZoneOcclusionUpdate; } void resetNeedsZoneOcclusionUpdate() { withWriteLock([&] { _needsZoneOcclusionUpdate = false; }); } + void setBillboardMode(BillboardMode value); + BillboardMode getBillboardMode() const; + virtual bool getRotateForPicking() const { return false; } + signals: void spaceUpdate(std::pair data); @@ -778,13 +779,11 @@ protected: QVector _renderWithZones; mutable bool _needsZoneOcclusionUpdate { false }; + BillboardMode _billboardMode { BillboardMode::NONE }; + bool _cullWithParent { false }; mutable bool _needsRenderUpdate { false }; - -private: - static std::function _getBillboardRotationOperator; - static std::function _getPrimaryViewFrustumPositionOperator; }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index ff55137a03..5c52cb2708 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -309,7 +309,6 @@ void EntityItemProperties::setScreenshareFromString(const QString& mode) { } } - inline void addTextEffect(QHash& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; } const QHash stringToTextEffectLookup = [] { QHash toReturn; @@ -328,6 +327,23 @@ void EntityItemProperties::setTextEffectFromString(const QString& effect) { } } +inline void addTextAlignment(QHash& lookup, TextAlignment alignment) { lookup[TextAlignmentHelpers::getNameForTextAlignment(alignment)] = alignment; } +const QHash stringToTextAlignmentLookup = [] { + QHash toReturn; + addTextAlignment(toReturn, TextAlignment::LEFT); + addTextAlignment(toReturn, TextAlignment::CENTER); + addTextAlignment(toReturn, TextAlignment::RIGHT); + return toReturn; +}(); +QString EntityItemProperties::getAlignmentAsString() const { return TextAlignmentHelpers::getNameForTextAlignment(_alignment); } +void EntityItemProperties::setAlignmentFromString(const QString& alignment) { + auto textAlignmentItr = stringToTextAlignmentLookup.find(alignment.toLower()); + if (textAlignmentItr != stringToTextAlignmentLookup.end()) { + _alignment = textAlignmentItr.value(); + _alignmentChanged = true; + } +} + QString getCollisionGroupAsString(uint16_t group) { switch (group) { case USER_COLLISION_GROUP_DYNAMIC: @@ -433,6 +449,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_PRIMITIVE_MODE, primitiveMode); CHECK_PROPERTY_CHANGE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection); CHECK_PROPERTY_CHANGE(PROP_RENDER_WITH_ZONES, renderWithZones); + CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); changedProperties += _grab.getChangedProperties(); // Physics @@ -493,7 +510,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ALPHA, alpha); changedProperties += _pulse.getChangedProperties(); CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); - CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); // Particles CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles); @@ -538,6 +554,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled); CHECK_PROPERTY_CHANGE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); + CHECK_PROPERTY_CHANGE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot); changedProperties += _animation.getChangedProperties(); // Light @@ -563,6 +580,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT, textEffect); CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT_COLOR, textEffectColor); CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT_THICKNESS, textEffectThickness); + CHECK_PROPERTY_CHANGE(PROP_TEXT_ALIGNMENT, alignment); // Zone changedProperties += _keyLight.getChangedProperties(); @@ -603,6 +621,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode); CHECK_PROPERTY_CHANGE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); CHECK_PROPERTY_CHANGE(PROP_WEB_USE_BACKGROUND, useBackground); + CHECK_PROPERTY_CHANGE(PROP_USER_AGENT, userAgent); // Polyline CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); @@ -811,6 +830,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Uuid[]} renderWithZones=[]] - A list of entity IDs representing with which zones this entity should render. * If it is empty, this entity will render normally. Otherwise, this entity will only render if your avatar is within * one of the zones in this list. + * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. Use the rotation + * property to control which axis is facing you. * * @property {Entities.Grab} grab - The entity's grab-related properties. * @@ -1002,6 +1023,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} blendshapeCoefficients - A JSON string of a map of blendshape names to values. Only stores set values. * When editing this property, only coefficients that you are editing will change; it will not explicitly reset other * coefficients. + * @property {boolean} useOriginalPivot=false - If false, the model will be centered based on its content, + * ignoring any offset in the model itself. If true, the model will respect its original offset. Currently, + * only pivots relative to {x: 0, y: 0, z: 0} are supported. * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the * model's original textures. Use a texture name from the originalTextures property to override that texture. * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no @@ -1332,7 +1356,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.TextEffect} textEffect="none" - The effect that is applied to the text. * @property {Color} textEffectColor=255,255,255 - The color of the effect. * @property {number} textEffectThickness=0.2 - The magnitude of the text effect, range 0.00.5. - * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. + * @property {Entities.TextAlignment} alignment="left" - How the text is aligned against its background. * @property {boolean} faceCamera - true if billboardMode is "yaw", false * if it isn't. Setting this property to false sets the billboardMode to "none". *

Deprecated: This property is deprecated and will be removed.

@@ -1368,7 +1392,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} alpha=1 - The opacity of the web surface. * @property {Entities.Pulse} pulse - Color and alpha pulse. *

Deprecated: This property is deprecated and will be removed.

- * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. * @property {boolean} faceCamera - true if billboardMode is "yaw", false * if it isn't. Setting this property to false sets the billboardMode to "none". *

Deprecated: This property is deprecated and will be removed.

@@ -1387,6 +1410,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {boolean} useBackground=true - true if the web entity should have a background, * false if the web entity's background should be transparent. The webpage must have CSS properties for transparency set * on the background-color for this property to have an effect. + * @property {string} userAgent - The user agent for the web entity to use when visiting web pages. + * Default value: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) + * Chrome/69.0.3497.113 Mobile Safari/537.36 * @example
* var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1487,7 +1513,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} alpha=1 - The opacity of the image. * @property {Entities.Pulse} pulse - Color and alpha pulse. *

Deprecated: This property is deprecated and will be removed.

- * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. * @property {boolean} faceCamera - true if billboardMode is "yaw", false * if it isn't. Setting this property to false sets the billboardMode to "none". *

Deprecated: This property is deprecated and will be removed.

@@ -1611,6 +1636,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PRIMITIVE_MODE, primitiveMode, getPrimitiveModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RENDER_WITH_ZONES, renderWithZones); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); // Physics @@ -1733,6 +1759,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot); if (!psuedoPropertyFlagsButDesiredEmpty) { _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } @@ -1766,7 +1793,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Text only if (_type == EntityTypes::Text) { _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight); @@ -1783,6 +1809,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_TEXT_EFFECT, textEffect, getTextEffectAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_TEXT_EFFECT_COLOR, textEffectColor, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT_EFFECT_THICKNESS, textEffectThickness); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_TEXT_ALIGNMENT, alignment, getAlignmentAsString()); } // Zones only @@ -1816,7 +1843,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi); @@ -1825,6 +1851,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_WEB_USE_BACKGROUND, useBackground); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_AGENT, userAgent); } // PolyVoxel only @@ -1884,7 +1911,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMISSIVE, emissive); @@ -2032,6 +2058,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(primitiveMode, PrimitiveMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(ignorePickIntersection, bool, setIgnorePickIntersection); COPY_PROPERTY_FROM_QSCRIPTVALUE(renderWithZones, qVectorQUuid, setRenderWithZones); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); _grab.copyFromScriptValue(object, _defaultSettings); // Physics @@ -2097,7 +2124,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); _pulse.copyFromScriptValue(object, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); // Particles COPY_PROPERTY_FROM_QSCRIPTVALUE(maxParticles, quint32, setMaxParticles); @@ -2142,6 +2168,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled); COPY_PROPERTY_FROM_QSCRIPTVALUE(blendshapeCoefficients, QString, setBlendshapeCoefficients); + COPY_PROPERTY_FROM_QSCRIPTVALUE(useOriginalPivot, bool, setUseOriginalPivot); _animation.copyFromScriptValue(object, _defaultSettings); // Light @@ -2167,6 +2194,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(textEffect, TextEffect); COPY_PROPERTY_FROM_QSCRIPTVALUE(textEffectColor, u8vec3Color, setTextEffectColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(textEffectThickness, float, setTextEffectThickness); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(alignment, Alignment); // Zone _keyLight.copyFromScriptValue(object, _defaultSettings); @@ -2207,6 +2235,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(showKeyboardFocusHighlight, bool, setShowKeyboardFocusHighlight); COPY_PROPERTY_FROM_QSCRIPTVALUE(useBackground, bool, setUseBackground); + COPY_PROPERTY_FROM_QSCRIPTVALUE(userAgent, QString, setUserAgent); // Polyline COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); @@ -2329,6 +2358,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(primitiveMode); COPY_PROPERTY_IF_CHANGED(ignorePickIntersection); COPY_PROPERTY_IF_CHANGED(renderWithZones); + COPY_PROPERTY_IF_CHANGED(billboardMode); _grab.merge(other._grab); // Physics @@ -2389,7 +2419,6 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(alpha); _pulse.merge(other._pulse); COPY_PROPERTY_IF_CHANGED(textures); - COPY_PROPERTY_IF_CHANGED(billboardMode); // Particles COPY_PROPERTY_IF_CHANGED(maxParticles); @@ -2434,6 +2463,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(relayParentJoints); COPY_PROPERTY_IF_CHANGED(groupCulled); COPY_PROPERTY_IF_CHANGED(blendshapeCoefficients); + COPY_PROPERTY_IF_CHANGED(useOriginalPivot); _animation.merge(other._animation); // Light @@ -2459,6 +2489,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(textEffect); COPY_PROPERTY_IF_CHANGED(textEffectColor); COPY_PROPERTY_IF_CHANGED(textEffectThickness); + COPY_PROPERTY_IF_CHANGED(alignment); // Zone _keyLight.merge(other._keyLight); @@ -2499,6 +2530,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(inputMode); COPY_PROPERTY_IF_CHANGED(showKeyboardFocusHighlight); COPY_PROPERTY_IF_CHANGED(useBackground); + COPY_PROPERTY_IF_CHANGED(userAgent); // Polyline COPY_PROPERTY_IF_CHANGED(linePoints); @@ -2625,6 +2657,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode); ADD_PROPERTY_TO_MAP(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool); ADD_PROPERTY_TO_MAP(PROP_RENDER_WITH_ZONES, RenderWithZones, renderWithZones, QVector); + ADD_PROPERTY_TO_MAP(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode); { // Grab ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic); @@ -2721,7 +2754,6 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode); } ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString); - ADD_PROPERTY_TO_MAP(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode); // Particles ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, @@ -2790,6 +2822,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool); ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool); ADD_PROPERTY_TO_MAP(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString); + ADD_PROPERTY_TO_MAP(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool); { // Animation ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); @@ -2826,6 +2859,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_TEXT_EFFECT, TextEffect, textEffect, TextEffect); ADD_PROPERTY_TO_MAP(PROP_TEXT_EFFECT_COLOR, TextEffectColor, textEffectColor, u8vec3Color); ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_TEXT_EFFECT_THICKNESS, TextEffectThickness, textEffectThickness, float, 0.0, 0.5); + ADD_PROPERTY_TO_MAP(PROP_TEXT_ALIGNMENT, Alignment, alignment, TextAlignment); // Zone { // Keylight @@ -2899,6 +2933,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode); ADD_PROPERTY_TO_MAP(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool); ADD_PROPERTY_TO_MAP(PROP_WEB_USE_BACKGROUND, useBackground, useBackground, bool); + ADD_PROPERTY_TO_MAP(PROP_USER_AGENT, UserAgent, userAgent, QString); // Polyline ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); @@ -3117,6 +3152,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)properties.getPrimitiveMode()); APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, properties.getIgnorePickIntersection()); APPEND_ENTITY_PROPERTY(PROP_RENDER_WITH_ZONES, properties.getRenderWithZones()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); _staticGrab.setProperties(properties); _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -3231,6 +3267,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints()); APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled()); APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, properties.getBlendshapeCoefficients()); + APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, properties.getUseOriginalPivot()); _staticAnimation.setProperties(properties); _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -3249,7 +3286,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticPulse.setProperties(properties); _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); @@ -3266,6 +3302,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT, (uint32_t)properties.getTextEffect()); APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, properties.getTextEffectColor()); APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, properties.getTextEffectThickness()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_ALIGNMENT, (uint32_t)properties.getAlignment()); } if (properties.getType() == EntityTypes::Zone) { @@ -3321,7 +3358,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticPulse.setProperties(properties); _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); @@ -3330,6 +3366,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode()); APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, properties.getShowKeyboardFocusHighlight()); APPEND_ENTITY_PROPERTY(PROP_WEB_USE_BACKGROUND, properties.getUseBackground()); + APPEND_ENTITY_PROPERTY(PROP_USER_AGENT, properties.getUserAgent()); } if (properties.getType() == EntityTypes::Line) { @@ -3384,7 +3421,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticPulse.setProperties(properties); _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, properties.getEmissive()); @@ -3610,6 +3646,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_WITH_ZONES, QVector, setRenderWithZones); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); // Physics @@ -3720,6 +3757,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot); properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); } @@ -3736,7 +3774,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Text) { properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); @@ -3753,6 +3790,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT, TextEffect, setTextEffect); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT_COLOR, u8vec3Color, setTextEffectColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT_THICKNESS, float, setTextEffectThickness); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_ALIGNMENT, TextAlignment, setAlignment); } if (properties.getType() == EntityTypes::Zone) { @@ -3797,7 +3835,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); @@ -3806,6 +3843,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_WEB_USE_BACKGROUND, bool, setUseBackground); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_AGENT, QString, setUserAgent); } if (properties.getType() == EntityTypes::Line) { @@ -3857,7 +3895,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMISSIVE, bool, setEmissive); @@ -4031,6 +4068,7 @@ void EntityItemProperties::markAllChanged() { _primitiveModeChanged = true; _ignorePickIntersectionChanged = true; _renderWithZonesChanged = true; + _billboardModeChanged = true; _grab.markAllChanged(); // Physics @@ -4084,7 +4122,6 @@ void EntityItemProperties::markAllChanged() { _alphaChanged = true; _pulse.markAllChanged(); _texturesChanged = true; - _billboardModeChanged = true; // Particles _maxParticlesChanged = true; @@ -4129,6 +4166,7 @@ void EntityItemProperties::markAllChanged() { _relayParentJointsChanged = true; _groupCulledChanged = true; _blendshapeCoefficientsChanged = true; + _useOriginalPivotChanged = true; _animation.markAllChanged(); // Light @@ -4154,6 +4192,7 @@ void EntityItemProperties::markAllChanged() { _textEffectChanged = true; _textEffectColorChanged = true; _textEffectThicknessChanged = true; + _alignmentChanged = true; // Zone _keyLight.markAllChanged(); @@ -4194,6 +4233,7 @@ void EntityItemProperties::markAllChanged() { _inputModeChanged = true; _showKeyboardFocusHighlightChanged = true; _useBackgroundChanged = true; + _userAgentChanged = true; // Polyline _linePointsChanged = true; @@ -4439,6 +4479,9 @@ QList EntityItemProperties::listChangedProperties() { if (renderWithZonesChanged()) { out += "renderWithZones"; } + if (billboardModeChanged()) { + out += "billboardMode"; + } getGrab().listChangedProperties(out); // Physics @@ -4574,9 +4617,6 @@ QList EntityItemProperties::listChangedProperties() { if (texturesChanged()) { out += "textures"; } - if (billboardModeChanged()) { - out += "billboardMode"; - } // Particles if (maxParticlesChanged()) { @@ -4701,6 +4741,9 @@ QList EntityItemProperties::listChangedProperties() { if (blendshapeCoefficientsChanged()) { out += "blendshapeCoefficients"; } + if (useOriginalPivotChanged()) { + out += "useOriginalPivot"; + } getAnimation().listChangedProperties(out); // Light @@ -4766,6 +4809,9 @@ QList EntityItemProperties::listChangedProperties() { if (textEffectThicknessChanged()) { out += "textEffectThickness"; } + if (alignmentChanged()) { + out += "alignment"; + } // Zone getKeyLight().listChangedProperties(out); @@ -4887,6 +4933,9 @@ QList EntityItemProperties::listChangedProperties() { if (useBackgroundChanged()) { out += "useBackground"; } + if (userAgentChanged()) { + out += "userAgent"; + } // Shape if (shapeChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index f7fde73430..91de540062 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,6 +67,7 @@ #include "WebInputMode.h" #include "GizmoType.h" #include "TextEffect.h" +#include "TextAlignment.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -197,6 +198,7 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode, PrimitiveMode::SOLID); DEFINE_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool, false); DEFINE_PROPERTY_REF(PROP_RENDER_WITH_ZONES, RenderWithZones, renderWithZones, QVector, QVector()); + DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup); // Physics @@ -257,7 +259,6 @@ public: DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, ENTITY_ITEM_DEFAULT_ALPHA); DEFINE_PROPERTY_GROUP(Pulse, pulse, PulsePropertyGroup); DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, ""); - DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); // Particles DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, particle::DEFAULT_MAX_PARTICLES); @@ -302,6 +303,7 @@ public: DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS); DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false); DEFINE_PROPERTY_REF(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString, ""); + DEFINE_PROPERTY_REF(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool, false); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); // Light @@ -327,6 +329,7 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_TEXT_EFFECT, TextEffect, textEffect, TextEffect, TextEffect::NO_EFFECT); DEFINE_PROPERTY_REF(PROP_TEXT_EFFECT_COLOR, TextEffectColor, textEffectColor, u8vec3Color, TextEntityItem::DEFAULT_TEXT_COLOR); DEFINE_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, TextEffectThickness, textEffectThickness, float, TextEntityItem::DEFAULT_TEXT_EFFECT_THICKNESS); + DEFINE_PROPERTY_REF_ENUM(PROP_TEXT_ALIGNMENT, Alignment, alignment, TextAlignment, TextAlignment::LEFT); // Zone DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); @@ -367,6 +370,7 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH); DEFINE_PROPERTY_REF(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool, true); DEFINE_PROPERTY_REF(PROP_WEB_USE_BACKGROUND, UseBackground, useBackground, bool, true); + DEFINE_PROPERTY_REF(PROP_USER_AGENT, UserAgent, userAgent, QString, WebEntityItem::DEFAULT_USER_AGENT); // Polyline DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 93bb8a89a7..e0b5a04094 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -45,6 +45,7 @@ enum EntityPropertyList { PROP_PRIMITIVE_MODE, PROP_IGNORE_PICK_INTERSECTION, PROP_RENDER_WITH_ZONES, + PROP_BILLBOARD_MODE, // Grab PROP_GRAB_GRABBABLE, PROP_GRAB_KINEMATIC, @@ -122,7 +123,6 @@ enum EntityPropertyList { PROP_PULSE_COLOR_MODE, PROP_PULSE_ALPHA_MODE, PROP_TEXTURES, - PROP_BILLBOARD_MODE, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new shared EntityItem properties to the list ABOVE this line @@ -218,16 +218,17 @@ enum EntityPropertyList { PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_6, PROP_GROUP_CULLED = PROP_DERIVED_7, PROP_BLENDSHAPE_COEFFICIENTS = PROP_DERIVED_8, + PROP_USE_ORIGINAL_PIVOT = PROP_DERIVED_9, // Animation - PROP_ANIMATION_URL = PROP_DERIVED_9, - PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_10, - PROP_ANIMATION_FPS = PROP_DERIVED_11, - PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_12, - PROP_ANIMATION_PLAYING = PROP_DERIVED_13, - PROP_ANIMATION_LOOP = PROP_DERIVED_14, - PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_15, - PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_16, - PROP_ANIMATION_HOLD = PROP_DERIVED_17, + PROP_ANIMATION_URL = PROP_DERIVED_10, + PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_11, + PROP_ANIMATION_FPS = PROP_DERIVED_12, + PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_13, + PROP_ANIMATION_PLAYING = PROP_DERIVED_14, + PROP_ANIMATION_LOOP = PROP_DERIVED_15, + PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_16, + PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_17, + PROP_ANIMATION_HOLD = PROP_DERIVED_18, // Light PROP_IS_SPOTLIGHT = PROP_DERIVED_0, @@ -252,6 +253,7 @@ enum EntityPropertyList { PROP_TEXT_EFFECT = PROP_DERIVED_12, PROP_TEXT_EFFECT_COLOR = PROP_DERIVED_13, PROP_TEXT_EFFECT_THICKNESS = PROP_DERIVED_14, + PROP_TEXT_ALIGNMENT = PROP_DERIVED_15, // Zone // Keylight @@ -319,6 +321,7 @@ enum EntityPropertyList { PROP_INPUT_MODE = PROP_DERIVED_4, PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT = PROP_DERIVED_5, PROP_WEB_USE_BACKGROUND = PROP_DERIVED_6, + PROP_USER_AGENT = PROP_DERIVED_7, // Polyline PROP_LINE_POINTS = PROP_DERIVED_0, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 24b6f7ccd4..eeb42626c2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -798,6 +798,7 @@ public: glm::vec3 origin; glm::vec3 direction; glm::vec3 invDirection; + glm::vec3 viewFrustumPos; const QVector& entityIdsToInclude; const QVector& entityIdsToDiscard; PickFilter searchFilter; @@ -815,7 +816,7 @@ bool evalRayIntersectionOp(const OctreeElementPointer& element, void* extraData) RayArgs* args = static_cast(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); - EntityItemID entityID = entityTreeElementPointer->evalRayIntersection(args->origin, args->direction, + EntityItemID entityID = entityTreeElementPointer->evalRayIntersection(args->origin, args->direction, args->viewFrustumPos, args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude, args->entityIdsToDiscard, args->searchFilter, args->extraInfo); if (!entityID.isNull()) { @@ -837,7 +838,8 @@ float evalRayIntersectionSortingOp(const OctreeElementPointer& element, void* ex float boundDistance = FLT_MAX; BoxFace face; glm::vec3 surfaceNormal; - if (entityTreeElementPointer->getAACube().findRayIntersection(args->origin, args->direction, args->invDirection, boundDistance, face, surfaceNormal)) { + if (entityTreeElementPointer->getAACube().findRayIntersection(args->origin, args->direction, args->invDirection, + boundDistance, face, surfaceNormal)) { // Don't add this cell if it's already farther than our best distance so far if (boundDistance < args->distance) { distance = boundDistance; @@ -857,7 +859,7 @@ EntityItemID EntityTree::evalRayIntersection(const glm::vec3& origin, const glm: vec3 dirReciprocal = glm::vec3(direction.x == 0.0f ? 0.0f : 1.0f / direction.x, direction.y == 0.0f ? 0.0f : 1.0f / direction.y, direction.z == 0.0f ? 0.0f : 1.0f / direction.z); - RayArgs args = { origin, direction, dirReciprocal, entityIdsToInclude, entityIdsToDiscard, + RayArgs args = { origin, direction, dirReciprocal, BillboardModeHelpers::getPrimaryViewFrustumPosition(), entityIdsToInclude, entityIdsToDiscard, searchFilter, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; @@ -879,6 +881,7 @@ public: glm::vec3 origin; glm::vec3 velocity; glm::vec3 acceleration; + glm::vec3 viewFrustumPos; const QVector& entityIdsToInclude; const QVector& entityIdsToDiscard; PickFilter searchFilter; @@ -896,7 +899,7 @@ bool evalParabolaIntersectionOp(const OctreeElementPointer& element, void* extra ParabolaArgs* args = static_cast(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); - EntityItemID entityID = entityTreeElementPointer->evalParabolaIntersection(args->origin, args->velocity, args->acceleration, + EntityItemID entityID = entityTreeElementPointer->evalParabolaIntersection(args->origin, args->velocity, args->acceleration, args->viewFrustumPos, args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude, args->entityIdsToDiscard, args->searchFilter, args->extraInfo); if (!entityID.isNull()) { @@ -918,7 +921,8 @@ float evalParabolaIntersectionSortingOp(const OctreeElementPointer& element, voi float boundDistance = FLT_MAX; BoxFace face; glm::vec3 surfaceNormal; - if (entityTreeElementPointer->getAACube().findParabolaIntersection(args->origin, args->velocity, args->acceleration, boundDistance, face, surfaceNormal)) { + if (entityTreeElementPointer->getAACube().findParabolaIntersection(args->origin, args->velocity, args->acceleration, + boundDistance, face, surfaceNormal)) { // Don't add this cell if it's already farther than our best distance so far if (boundDistance < args->parabolicDistance) { distance = boundDistance; @@ -934,8 +938,8 @@ EntityItemID EntityTree::evalParabolaIntersection(const PickParabola& parabola, OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { - ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard, - searchFilter, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; + ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, BillboardModeHelpers::getPrimaryViewFrustumPosition(), + entityIdsToInclude, entityIdsToDiscard, searchFilter, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; parabolicDistance = FLT_MAX; distance = FLT_MAX; @@ -3137,6 +3141,13 @@ bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) { } } + // Before, billboarded entities ignored rotation. Now, they use it to determine which axis is facing you. + if (contentVersion < (int)EntityVersion::AllBillboardMode) { + if (properties.getBillboardMode() != BillboardMode::NONE) { + properties.setRotation(glm::quat()); + } + } + EntityItemPointer entity = addEntity(entityItemID, properties, isImport); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 9af0bbfdb6..f330058a78 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -162,7 +162,7 @@ bool EntityTreeElement::checkFilterSettings(const EntityItemPointer& entity, Pic return true; } -EntityItemID EntityTreeElement::evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +EntityItemID EntityTreeElement::evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo) { @@ -177,7 +177,7 @@ EntityItemID EntityTreeElement::evalRayIntersection(const glm::vec3& origin, con QVariantMap localExtraInfo; float distanceToElementDetails = distance; - EntityItemID entityID = evalDetailedRayIntersection(origin, direction, element, distanceToElementDetails, + EntityItemID entityID = evalDetailedRayIntersection(origin, direction, viewFrustumPos, element, distanceToElementDetails, localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, searchFilter, localExtraInfo); if (!entityID.isNull() && distanceToElementDetails < distance) { distance = distanceToElementDetails; @@ -189,7 +189,7 @@ EntityItemID EntityTreeElement::evalRayIntersection(const glm::vec3& origin, con return result; } -EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo) { @@ -205,10 +205,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori // (this is faster and more likely to cull results than the filter check below so we do it first) bool success; AABox entityBox = entity->getAABox(success); - if (!success) { - return; - } - if (!entityBox.rayHitsBoundingSphere(origin, direction)) { + if (!success || !entityBox.rayHitsBoundingSphere(origin, direction)) { return; } @@ -219,14 +216,18 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori } // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); - glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::vec3 position = entity->getWorldPosition(); + glm::mat4 translation = glm::translate(position); + BillboardMode billboardMode = entity->getBillboardMode(); + glm::quat orientation = billboardMode == BillboardMode::NONE ? entity->getWorldOrientation() : entity->getLocalOrientation(); + glm::mat4 rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(position, orientation, billboardMode, + viewFrustumPos, entity->getRotateForPicking())); glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); glm::vec3 registrationPoint = entity->getRegistrationPoint(); - glm::vec3 corner = -(dimensions * registrationPoint); + glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot(); AABox entityFrameBox(corner, dimensions); @@ -244,7 +245,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori // now ask the entity if we actually intersect if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; - if (entity->findDetailedRayIntersection(origin, direction, element, localDistance, + if (entity->findDetailedRayIntersection(origin, direction, viewFrustumPos, element, localDistance, localFace, localSurfaceNormal, localExtraInfo, searchFilter.isPrecise())) { if (localDistance < distance) { distance = localDistance; @@ -277,11 +278,12 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad bool result = false; withReadLock([&] { foreach(EntityItemPointer entity, _entityItems) { - glm::vec3 entityCenter = entity->getWorldPosition(); + bool success; + glm::vec3 entityCenter = entity->getCenterPosition(success); float entityRadius = entity->getRadius(); // don't penetrate yourself - if (entityCenter == center && entityRadius == radius) { + if (!success || (entityCenter == center && entityRadius == radius)) { return; } @@ -298,7 +300,7 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad } EntityItemID EntityTreeElement::evalParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo) { @@ -321,7 +323,7 @@ EntityItemID EntityTreeElement::evalParabolaIntersection(const glm::vec3& origin } // Get the normal of the plane, the cross product of two vectors on the plane glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); - EntityItemID entityID = evalDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails, + EntityItemID entityID = evalDetailedParabolaIntersection(origin, velocity, acceleration, viewFrustumPos, normal, element, distanceToElementDetails, localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, searchFilter, localExtraInfo); if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { parabolicDistance = distanceToElementDetails; @@ -334,9 +336,9 @@ EntityItemID EntityTreeElement::evalParabolaIntersection(const glm::vec3& origin } EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, - const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, - PickFilter searchFilter, QVariantMap& extraInfo) { + const glm::vec3& viewFrustumPos,const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIDsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... EntityItemID entityID; @@ -349,15 +351,12 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3 // (this is faster and more likely to cull results than the filter check below so we do it first) bool success; AABox entityBox = entity->getAABox(success); - if (!success) { - return; - } // Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane // defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic, // the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection // below - if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) { + if (!success || !entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) { return; } @@ -368,14 +367,18 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3 } // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); - glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::vec3 position = entity->getWorldPosition(); + glm::mat4 translation = glm::translate(position); + BillboardMode billboardMode = entity->getBillboardMode(); + glm::quat orientation = billboardMode == BillboardMode::NONE ? entity->getWorldOrientation() : entity->getLocalOrientation(); + glm::mat4 rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(position, orientation, billboardMode, + viewFrustumPos, entity->getRotateForPicking())); glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); glm::vec3 registrationPoint = entity->getRegistrationPoint(); - glm::vec3 corner = -(dimensions * registrationPoint); + glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot(); AABox entityFrameBox(corner, dimensions); @@ -394,7 +397,7 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3 // now ask the entity if we actually intersect if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; - if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance, + if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, viewFrustumPos, element, localDistance, localFace, localSurfaceNormal, localExtraInfo, searchFilter.isPrecise())) { if (localDistance < parabolicDistance) { parabolicDistance = localDistance; @@ -445,12 +448,11 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra bool success; AABox entityBox = entity->getAABox(success); - // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case glm::vec3 penetration; if (success && entityBox.findSpherePenetration(position, radius, penetration)) { - glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); // FIXME - consider allowing the entity to determine penetration so that // entities could presumably do actual hull testing if they wanted to @@ -464,21 +466,20 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra float entityTrueRadius = dimensions.x / 2.0f; bool success; - if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { - if (success) { - foundEntities.push_back(entity->getID()); - } + glm::vec3 center = entity->getCenterPosition(success); + if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) { + foundEntities.push_back(entity->getID()); } } else { // determine the worldToEntityMatrix that doesn't include scale because // we're going to use the registration aware aa box in the entity frame - glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 registrationPoint = entity->getRegistrationPoint(); - glm::vec3 corner = -(dimensions * registrationPoint); + glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot(); AABox entityFrameBox(corner, dimensions); @@ -499,12 +500,11 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position, bool success; AABox entityBox = entity->getAABox(success); - // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case glm::vec3 penetration; if (success && entityBox.findSpherePenetration(position, radius, penetration)) { - glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); // FIXME - consider allowing the entity to determine penetration so that // entities could presumably do actual hull testing if they wanted to @@ -518,21 +518,20 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position, float entityTrueRadius = dimensions.x / 2.0f; bool success; - if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { - if (success) { - foundEntities.push_back(entity->getID()); - } + glm::vec3 center = entity->getCenterPosition(success); + if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) { + foundEntities.push_back(entity->getID()); } } else { // determine the worldToEntityMatrix that doesn't include scale because // we're going to use the registration aware aa box in the entity frame - glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 registrationPoint = entity->getRegistrationPoint(); - glm::vec3 corner = -(dimensions * registrationPoint); + glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot(); AABox entityFrameBox(corner, dimensions); @@ -563,7 +562,7 @@ void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position, glm::vec3 penetration; if (success && entityBox.findSpherePenetration(position, radius, penetration)) { - glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); // FIXME - consider allowing the entity to determine penetration so that // entities could presumably do actual hull testing if they wanted to @@ -575,23 +574,22 @@ void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position, // NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the // maximum bounding sphere, which is actually larger than our actual radius float entityTrueRadius = dimensions.x / 2.0f; - bool success; - if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { - if (success) { - foundEntities.push_back(entity->getID()); - } + glm::vec3 center = entity->getCenterPosition(success); + + if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) { + foundEntities.push_back(entity->getID()); } } else { // determine the worldToEntityMatrix that doesn't include scale because // we're going to use the registration aware aa box in the entity frame - glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 registrationPoint = entity->getRegistrationPoint(); - glm::vec3 corner = -(dimensions * registrationPoint); + glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot(); AABox entityFrameBox(corner, dimensions); @@ -612,6 +610,7 @@ void EntityTreeElement::evalEntitiesInCube(const AACube& cube, PickFilter search bool success; AABox entityBox = entity->getAABox(success); + // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to @@ -642,6 +641,7 @@ void EntityTreeElement::evalEntitiesInBox(const AABox& box, PickFilter searchFil bool success; AABox entityBox = entity->getAABox(success); + // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to @@ -680,7 +680,7 @@ void EntityTreeElement::evalEntitiesInFrustum(const ViewFrustum& frustum, PickFi }); } -void EntityTreeElement::getEntities(EntityItemFilter& filter, QVector& foundEntities) { +void EntityTreeElement::getEntities(EntityItemFilter& filter, QVector& foundEntities) { forEachEntity([&](EntityItemPointer entity) { if (filter(entity)) { foundEntities.push_back(entity); diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index f94da44138..dab56132c9 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -136,24 +136,24 @@ public: static bool checkFilterSettings(const EntityItemPointer& entity, PickFilter searchFilter); virtual bool canPickIntersect() const override { return hasEntities(); } - virtual EntityItemID evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + virtual EntityItemID evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); virtual EntityItemID evalDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; virtual EntityItemID evalParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); virtual EntityItemID evalDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const glm::vec3& normal, const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); template diff --git a/libraries/entities/src/GizmoEntityItem.cpp b/libraries/entities/src/GizmoEntityItem.cpp index 47c9afd168..b6c5713c1a 100644 --- a/libraries/entities/src/GizmoEntityItem.cpp +++ b/libraries/entities/src/GizmoEntityItem.cpp @@ -103,14 +103,16 @@ bool GizmoEntityItem::supportsDetailedIntersection() const { } bool GizmoEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.z); - glm::quat rotation = getWorldOrientation(); - rotation = glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation; + BillboardMode billboardMode = getBillboardMode(); + glm::quat rotation = billboardMode == BillboardMode::NONE ? getWorldOrientation() : getLocalOrientation(); + rotation *= glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + rotation = BillboardModeHelpers::getBillboardRotation(position, rotation, billboardMode, viewFrustumPos); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { glm::vec3 hitPosition = origin + (distance * direction); @@ -136,15 +138,17 @@ bool GizmoEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const } bool GizmoEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { //// Scale the dimensions by the diameter glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.z); - glm::quat rotation = getWorldOrientation(); - rotation = glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation; + BillboardMode billboardMode = getBillboardMode(); + glm::quat rotation = billboardMode == BillboardMode::NONE ? getWorldOrientation() : getLocalOrientation(); + rotation *= glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT); glm::vec3 position = getWorldPosition(); + rotation = BillboardModeHelpers::getBillboardRotation(position, rotation, billboardMode, viewFrustumPos); glm::quat inverseRot = glm::inverse(rotation); glm::vec3 localOrigin = inverseRot * (origin - position); diff --git a/libraries/entities/src/GizmoEntityItem.h b/libraries/entities/src/GizmoEntityItem.h index 37a802387d..a05c294523 100644 --- a/libraries/entities/src/GizmoEntityItem.h +++ b/libraries/entities/src/GizmoEntityItem.h @@ -45,13 +45,14 @@ public: bool supportsDetailedIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + bool getRotateForPicking() const override { return getBillboardMode() != BillboardMode::NONE; } GizmoType getGizmoType() const; void setGizmoType(GizmoType value); diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 6a8c457b0a..afa6d9ae69 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -35,7 +35,6 @@ EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& d withReadLock([&] { _pulseProperties.getProperties(properties); }); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive); @@ -55,7 +54,6 @@ bool ImageEntityItem::setSubClassProperties(const EntityItemProperties& properti somethingChanged |= pulsePropertiesChanged; _needsRenderUpdate |= pulsePropertiesChanged; }); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive); @@ -82,7 +80,6 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromPulse; dataAt += bytesFromPulse; }); - READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive); @@ -98,7 +95,6 @@ EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; requestedProperties += _pulseProperties.getEntityProperties(params); - requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_IMAGE_URL; requestedProperties += PROP_EMISSIVE; @@ -124,7 +120,6 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, getImageURL()); APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive()); @@ -132,69 +127,6 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, getSubImage()); } -glm::vec3 ImageEntityItem::getRaycastDimensions() const { - glm::vec3 dimensions = getScaledDimensions(); - if (getBillboardMode() != BillboardMode::NONE) { - float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); - const float SQRT_2 = 1.41421356237f; - return glm::vec3(SQRT_2 * max); - } - return dimensions; -} - -bool ImageEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { - glm::vec3 dimensions = getScaledDimensions(); - glm::vec2 xyDimensions(dimensions.x, dimensions.y); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode, EntityItem::getPrimaryViewFrustumPosition()); - - if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - glm::vec3 forward = rotation * Vectors::FRONT; - if (glm::dot(forward, direction) > 0.0f) { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } else { - face = MIN_Z_FACE; - surfaceNormal = forward; - } - return true; - } - return false; -} - -bool ImageEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { - glm::vec3 dimensions = getScaledDimensions(); - glm::vec2 xyDimensions(dimensions.x, dimensions.y); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - - if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { - float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; - glm::vec3 forward = rotation * Vectors::FRONT; - if (localIntersectionVelocityZ > 0.0f) { - face = MIN_Z_FACE; - surfaceNormal = forward; - } else { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } - return true; - } - return false; -} - QString ImageEntityItem::getImageURL() const { QString result; withReadLock([&] { @@ -240,21 +172,6 @@ void ImageEntityItem::setKeepAspectRatio(bool keepAspectRatio) { }); } -BillboardMode ImageEntityItem::getBillboardMode() const { - BillboardMode result; - withReadLock([&] { - result = _billboardMode; - }); - return result; -} - -void ImageEntityItem::setBillboardMode(BillboardMode value) { - withWriteLock([&] { - _needsRenderUpdate |= _billboardMode != value; - _billboardMode = value; - }); -} - QRect ImageEntityItem::getSubImage() const { QRect result; withReadLock([&] { diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index bca67dc738..4f7aac0c13 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -43,17 +43,6 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - glm::vec3 getRaycastDimensions() const override; - virtual bool supportsDetailedIntersection() const override { return true; } - virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; - virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; - void setImageURL(const QString& imageUrl); QString getImageURL() const; @@ -63,9 +52,6 @@ public: void setKeepAspectRatio(bool keepAspectRatio); bool getKeepAspectRatio() const; - void setBillboardMode(BillboardMode value); - BillboardMode getBillboardMode() const; - void setSubImage(const QRect& subImage); QRect getSubImage() const; @@ -81,7 +67,6 @@ protected: glm::u8vec3 _color; float _alpha; PulsePropertyGroup _pulseProperties; - BillboardMode _billboardMode; QString _imageURL; bool _emissive { false }; diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 0dcb5d125a..a3e00cddba 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -254,7 +254,7 @@ float LightEntityItem::getCutoff() const { } bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { @@ -267,8 +267,8 @@ bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const } bool LightEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { // TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state // this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 5245770ec8..0f21c6acd9 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -74,12 +74,12 @@ public: virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; private: diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 38e526204e..505291cdda 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -52,13 +52,13 @@ class LineEntityItem : public EntityItem { // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 4716db6a41..321e92b9f0 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -73,6 +73,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled); COPY_ENTITY_PROPERTY_TO_PROPERTIES(blendshapeCoefficients, getBlendshapeCoefficients); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(useOriginalPivot, getUseOriginalPivot); withReadLock([&] { _animationProperties.getProperties(properties); }); @@ -96,6 +97,7 @@ bool ModelEntityItem::setSubClassProperties(const EntityItemProperties& properti SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled); SET_ENTITY_PROPERTY_FROM_PROPERTIES(blendshapeCoefficients, setBlendshapeCoefficients); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(useOriginalPivot, setUseOriginalPivot); withWriteLock([&] { AnimationPropertyGroup animationProperties = _animationProperties; @@ -130,6 +132,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled); READ_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients); + READ_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot); // grab a local copy of _animationProperties to avoid multiple locks int bytesFromAnimation; @@ -169,6 +172,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_RELAY_PARENT_JOINTS; requestedProperties += PROP_GROUP_CULLED; requestedProperties += PROP_BLENDSHAPE_COEFFICIENTS; + requestedProperties += PROP_USE_ORIGINAL_PIVOT; requestedProperties += _animationProperties.getEntityProperties(params); return requestedProperties; @@ -198,6 +202,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints()); APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled()); APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, getBlendshapeCoefficients()); + APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, getUseOriginalPivot()); withReadLock([&] { _animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -251,6 +256,7 @@ void ModelEntityItem::debugDump() const { qCDebug(entities) << " model URL:" << getModelURL(); qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); qCDebug(entities) << " blendshapeCoefficients:" << getBlendshapeCoefficients(); + qCDebug(entities) << " useOrigialPivot:" << getUseOriginalPivot(); } void ModelEntityItem::setShapeType(ShapeType type) { @@ -324,6 +330,19 @@ const Transform ModelEntityItem::getTransform(bool& success, int depth) const { return worldTransform; } + +const Transform ModelEntityItem::getTransformWithOnlyLocalRotation(bool& success, int depth) const { + const Transform parentTransform = getParentTransform(success, depth); + Transform localTransform = getLocalTransform(); + localTransform.postScale(getModelScale()); + + Transform worldTransform; + Transform::mult(worldTransform, parentTransform, localTransform); + worldTransform.setRotation(localTransform.getRotation()); + + return worldTransform; +} + void ModelEntityItem::setCompoundShapeURL(const QString& url) { withWriteLock([&] { if (_compoundShapeURL.get() != url) { @@ -713,3 +732,25 @@ QVector ModelEntityItem::getBlendshapeCoefficientVector() { return _blendshapeCoefficientsVector; }); } + +void ModelEntityItem::setUseOriginalPivot(bool value) { + bool changed = false; + withWriteLock([&] { + if (_useOriginalPivot != value) { + _needsRenderUpdate = true; + _useOriginalPivot = value; + changed = true; + } + }); + + if (changed) { + markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); + locationChanged(); + } +} + +bool ModelEntityItem::getUseOriginalPivot() const { + return resultWithReadLock([&] { + return _useOriginalPivot; + }); +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index b835b48d13..a00327251c 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -71,6 +71,7 @@ public: virtual void setScaledDimensions(const glm::vec3& value) override; virtual const Transform getTransform(bool& success, int depth = 0) const override; + virtual const Transform getTransformWithOnlyLocalRotation(bool& success, int depth = 0) const override; virtual const Transform getTransform() const override; static const QString DEFAULT_COMPOUND_SHAPE_URL; @@ -119,6 +120,9 @@ public: bool blendshapesChanged() const { return _blendshapesChanged; } QVector getBlendshapeCoefficientVector(); + bool getUseOriginalPivot() const; + void setUseOriginalPivot(bool useOriginalPivot); + private: void setAnimationSettings(const QString& value); // only called for old bitstream format bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); @@ -152,6 +156,7 @@ protected: bool _relayParentJoints; bool _groupCulled { false }; QVariantMap _blendshapeCoefficientsMap; + bool _useOriginalPivot { false }; ThreadSafeValueCache _compoundShapeURL; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 8fb7831c6a..23f6e36e73 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -82,12 +82,12 @@ public: // never have a ray intersection pick a PolyLineEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } void computeTightLocalBoundingBox(AABox& box) const; diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index dd55ca898b..49dc0d7c90 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -377,15 +377,22 @@ glm::mat4 PolyVoxEntityItem::localToVoxelMatrix() const { return localToModelMatrix; } -glm::mat4 PolyVoxEntityItem::voxelToWorldMatrix() const { - glm::mat4 rotation = glm::mat4_cast(getWorldOrientation()); - glm::mat4 translation = glm::translate(getWorldPosition()); +glm::mat4 PolyVoxEntityItem::voxelToWorldMatrix(bool includeBillboard) const { + glm::vec3 position = getWorldPosition(); + glm::mat4 translation = glm::translate(position); + glm::mat4 rotation; + if (includeBillboard) { + BillboardMode billboardMode = getBillboardMode(); + glm::quat orientation = billboardMode == BillboardMode::NONE ? getWorldOrientation() : getLocalOrientation(); + rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(position, orientation, billboardMode, BillboardModeHelpers::getPrimaryViewFrustumPosition())); + } else { + rotation = glm::mat4_cast(getWorldOrientation()); + } return translation * rotation * voxelToLocalMatrix(); } -glm::mat4 PolyVoxEntityItem::worldToVoxelMatrix() const { - glm::mat4 worldToModelMatrix = glm::inverse(voxelToWorldMatrix()); - return worldToModelMatrix; +glm::mat4 PolyVoxEntityItem::worldToVoxelMatrix(bool includeBillboard) const { + return glm::inverse(voxelToWorldMatrix(includeBillboard)); } glm::vec3 PolyVoxEntityItem::voxelCoordsToWorldCoords(const glm::vec3& voxelCoords) const { diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index f994fcd37c..3e3fe0b18c 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -44,12 +44,12 @@ class PolyVoxEntityItem : public EntityItem { // never have a ray intersection pick a PolyVoxEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual void debugDump() const override; @@ -167,8 +167,8 @@ class PolyVoxEntityItem : public EntityItem { bool isEdged() const; - glm::mat4 voxelToWorldMatrix() const; - glm::mat4 worldToVoxelMatrix() const; + glm::mat4 voxelToWorldMatrix(bool includeBillboard = false) const; + glm::mat4 worldToVoxelMatrix(bool includeBillboard = false) const; glm::mat4 voxelToLocalMatrix() const; glm::mat4 localToVoxelMatrix() const; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 3dbb02775a..4349eefd2d 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -257,7 +257,8 @@ float ShapeEntityItem::getAlpha() const { void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; - if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { + const auto shape = getShape(); + if ((shape == entity::Shape::Circle || shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { // enforce flatness in Y glm::vec3 newDimensions = value; newDimensions.y = MAX_FLAT_DIMENSION; @@ -268,15 +269,21 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { } bool ShapeEntityItem::supportsDetailedIntersection() const { - return _shape == entity::Sphere; + return getShape() == entity::Sphere; } bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + BillboardMode billboardMode = getBillboardMode(); + glm::quat rotation = billboardMode == BillboardMode::NONE ? getWorldOrientation() : getLocalOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + rotation = BillboardModeHelpers::getBillboardRotation(position, rotation, billboardMode, viewFrustumPos); + // determine the ray in the frame of the entity transformed from a unit sphere - glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 entityToWorldMatrix = glm::translate(position) * glm::mat4_cast(rotation) * glm::scale(dimensions); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); @@ -299,11 +306,17 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const } bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + BillboardMode billboardMode = getBillboardMode(); + glm::quat rotation = billboardMode == BillboardMode::NONE ? getWorldOrientation() : getLocalOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + rotation = BillboardModeHelpers::getBillboardRotation(position, rotation, billboardMode, viewFrustumPos); + // determine the parabola in the frame of the entity transformed from a unit sphere - glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 entityToWorldMatrix = glm::translate(position) * glm::mat4_cast(rotation) * glm::scale(dimensions); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); @@ -324,6 +337,11 @@ bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, return false; } +bool ShapeEntityItem::getRotateForPicking() const { + auto shape = getShape(); + return getBillboardMode() != BillboardMode::NONE && (_shape < entity::Shape::Cube || _shape > entity::Shape::Icosahedron); +} + void ShapeEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; @@ -343,8 +361,9 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { // is set. const glm::vec3 entityDimensions = getScaledDimensions(); + const auto shape = getShape(); - switch (_shape){ + switch (shape){ case entity::Shape::Quad: // Quads collide like flat Cubes case entity::Shape::Cube: { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index c0dc9642fe..d5b934153a 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -86,13 +86,14 @@ public: bool supportsDetailedIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + bool getRotateForPicking() const override; void debugDump() const override; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index a996319463..021e753710 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -53,7 +53,6 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de withReadLock([&] { _pulseProperties.getProperties(properties); }); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(text, getText); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); @@ -70,6 +69,7 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffect, getTextEffect); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffectColor, getTextEffectColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffectThickness, getTextEffectThickness); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alignment, getAlignment); return properties; } @@ -81,7 +81,6 @@ bool TextEntityItem::setSubClassProperties(const EntityItemProperties& propertie somethingChanged |= pulsePropertiesChanged; _needsRenderUpdate |= pulsePropertiesChanged; }); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(text, setText); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); @@ -98,7 +97,8 @@ bool TextEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffect, setTextEffect); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectColor, setTextEffectColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectThickness, setTextEffectThickness); - + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alignment, setAlignment); + return somethingChanged; } @@ -117,7 +117,6 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromPulse; dataAt += bytesFromPulse; }); - READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); @@ -134,6 +133,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT, TextEffect, setTextEffect); READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, glm::u8vec3, setTextEffectColor); READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, float, setTextEffectThickness); + READ_ENTITY_PROPERTY(PROP_TEXT_ALIGNMENT, TextAlignment, setAlignment); return bytesRead; } @@ -142,7 +142,6 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += _pulseProperties.getEntityProperties(params); - requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_TEXT; requestedProperties += PROP_LINE_HEIGHT; @@ -159,6 +158,7 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_TEXT_EFFECT; requestedProperties += PROP_TEXT_EFFECT_COLOR; requestedProperties += PROP_TEXT_EFFECT_THICKNESS; + requestedProperties += PROP_TEXT_ALIGNMENT; return requestedProperties; } @@ -177,7 +177,6 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_TEXT, getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); @@ -194,69 +193,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT, (uint32_t)getTextEffect()); APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, getTextEffectColor()); APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, getTextEffectThickness()); -} - -glm::vec3 TextEntityItem::getRaycastDimensions() const { - glm::vec3 dimensions = getScaledDimensions(); - if (getBillboardMode() != BillboardMode::NONE) { - float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); - const float SQRT_2 = 1.41421356237f; - return glm::vec3(SQRT_2 * max); - } - return dimensions; -} - -bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { - glm::vec3 dimensions = getScaledDimensions(); - glm::vec2 xyDimensions(dimensions.x, dimensions.y); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode, EntityItem::getPrimaryViewFrustumPosition()); - - if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - glm::vec3 forward = rotation * Vectors::FRONT; - if (glm::dot(forward, direction) > 0.0f) { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } else { - face = MIN_Z_FACE; - surfaceNormal = forward; - } - return true; - } - return false; -} - -bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { - glm::vec3 dimensions = getScaledDimensions(); - glm::vec2 xyDimensions(dimensions.x, dimensions.y); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - - if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { - float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; - glm::vec3 forward = rotation * Vectors::FRONT; - if (localIntersectionVelocityZ > 0.0f) { - face = MIN_Z_FACE; - surfaceNormal = forward; - } else { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } - return true; - } - return false; + APPEND_ENTITY_PROPERTY(PROP_TEXT_ALIGNMENT, (uint32_t)getAlignment()); } void TextEntityItem::setText(const QString& value) { @@ -339,21 +276,6 @@ float TextEntityItem::getBackgroundAlpha() const { }); } -BillboardMode TextEntityItem::getBillboardMode() const { - BillboardMode result; - withReadLock([&] { - result = _billboardMode; - }); - return result; -} - -void TextEntityItem::setBillboardMode(BillboardMode value) { - withWriteLock([&] { - _needsRenderUpdate |= _billboardMode != value; - _billboardMode = value; - }); -} - void TextEntityItem::setLeftMargin(float value) { withWriteLock([&] { _needsRenderUpdate |= _leftMargin != value; @@ -471,8 +393,21 @@ float TextEntityItem::getTextEffectThickness() const { }); } +void TextEntityItem::setAlignment(TextAlignment value) { + withWriteLock([&] { + _needsRenderUpdate |= _alignment != value; + _alignment = value; + }); +} + +TextAlignment TextEntityItem::getAlignment() const { + return resultWithReadLock([&] { + return _alignment; + }); +} + PulsePropertyGroup TextEntityItem::getPulseProperties() const { return resultWithReadLock([&] { return _pulseProperties; }); -} \ No newline at end of file +} diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 91496708f6..fff7f57bfb 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -15,7 +15,6 @@ #include "EntityItem.h" #include "PulsePropertyGroup.h" -#include "TextEffect.h" class TextEntityItem : public EntityItem { public: @@ -48,17 +47,6 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - glm::vec3 getRaycastDimensions() const override; - virtual bool supportsDetailedIntersection() const override { return true; } - virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; - virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; - static const QString DEFAULT_TEXT; void setText(const QString& value); QString getText() const; @@ -82,9 +70,6 @@ public: float getBackgroundAlpha() const; void setBackgroundAlpha(float value); - BillboardMode getBillboardMode() const; - void setBillboardMode(BillboardMode value); - static const float DEFAULT_MARGIN; float getLeftMargin() const; void setLeftMargin(float value); @@ -114,11 +99,12 @@ public: float getTextEffectThickness() const; void setTextEffectThickness(float value); + TextAlignment getAlignment() const; + void setAlignment(TextAlignment value); + PulsePropertyGroup getPulseProperties() const; private: - BillboardMode _billboardMode; - QString _text; float _lineHeight; glm::u8vec3 _textColor; @@ -133,6 +119,7 @@ private: bool _unlit; QString _font; + TextAlignment _alignment; TextEffect _effect; glm::u8vec3 _effectColor; float _effectThickness; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 61a1ed18c5..2f8389fe2c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -25,6 +25,7 @@ #include "EntityTreeElement.h" const QString WebEntityItem::DEFAULT_SOURCE_URL = NetworkingConstants::WEB_ENTITY_DEFAULT_SOURCE_URL; +const QString WebEntityItem::DEFAULT_USER_AGENT = NetworkingConstants::WEB_ENTITY_DEFAULT_USER_AGENT; const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10; EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -54,7 +55,6 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des withReadLock([&] { _pulseProperties.getProperties(properties); }); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI); @@ -63,6 +63,7 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(showKeyboardFocusHighlight, getShowKeyboardFocusHighlight); COPY_ENTITY_PROPERTY_TO_PROPERTIES(useBackground, getUseBackground); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(userAgent, getUserAgent); return properties; } @@ -76,7 +77,6 @@ bool WebEntityItem::setSubClassProperties(const EntityItemProperties& properties somethingChanged |= pulsePropertiesChanged; _needsRenderUpdate |= pulsePropertiesChanged; }); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl); SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI); @@ -85,6 +85,7 @@ bool WebEntityItem::setSubClassProperties(const EntityItemProperties& properties SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(showKeyboardFocusHighlight, setShowKeyboardFocusHighlight); SET_ENTITY_PROPERTY_FROM_PROPERTIES(useBackground, setUseBackground); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(userAgent, setUserAgent); return somethingChanged; } @@ -106,7 +107,6 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i bytesRead += bytesFromPulse; dataAt += bytesFromPulse; }); - READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI); @@ -115,6 +115,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode); READ_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); READ_ENTITY_PROPERTY(PROP_WEB_USE_BACKGROUND, bool, setUseBackground); + READ_ENTITY_PROPERTY(PROP_USER_AGENT, QString, setUserAgent); return bytesRead; } @@ -124,7 +125,6 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; requestedProperties += _pulseProperties.getEntityProperties(params); - requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_SOURCE_URL; requestedProperties += PROP_DPI; @@ -133,6 +133,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa requestedProperties += PROP_INPUT_MODE; requestedProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT; requestedProperties += PROP_WEB_USE_BACKGROUND; + requestedProperties += PROP_USER_AGENT; return requestedProperties; } @@ -151,7 +152,6 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI()); @@ -160,71 +160,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode()); APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, getShowKeyboardFocusHighlight()); APPEND_ENTITY_PROPERTY(PROP_WEB_USE_BACKGROUND, getUseBackground()); -} - -glm::vec3 WebEntityItem::getRaycastDimensions() const { - glm::vec3 dimensions = getScaledDimensions(); - if (getBillboardMode() != BillboardMode::NONE) { - float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); - const float SQRT_2 = 1.41421356237f; - return glm::vec3(SQRT_2 * max); - } - return dimensions; -} - -bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { - glm::vec3 dimensions = getScaledDimensions(); - glm::vec2 xyDimensions(dimensions.x, dimensions.y); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode, EntityItem::getPrimaryViewFrustumPosition()); - - if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - glm::vec3 forward = rotation * Vectors::FRONT; - if (glm::dot(forward, direction) > 0.0f) { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } else { - face = MIN_Z_FACE; - surfaceNormal = forward; - } - return true; - } else { - return false; - } -} - -bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { - glm::vec3 dimensions = getScaledDimensions(); - glm::vec2 xyDimensions(dimensions.x, dimensions.y); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - - if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { - float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; - glm::vec3 forward = rotation * Vectors::FRONT; - if (localIntersectionVelocityZ > 0.0f) { - face = MIN_Z_FACE; - surfaceNormal = forward; - } else { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } - return true; - } else { - return false; - } + APPEND_ENTITY_PROPERTY(PROP_USER_AGENT, getUserAgent()); } void WebEntityItem::setColor(const glm::u8vec3& value) { @@ -259,19 +195,6 @@ float WebEntityItem::getAlpha() const { }); } -BillboardMode WebEntityItem::getBillboardMode() const { - return resultWithReadLock([&] { - return _billboardMode; - }); -} - -void WebEntityItem::setBillboardMode(BillboardMode value) { - withWriteLock([&] { - _needsRenderUpdate |= _billboardMode != value; - _billboardMode = value; - }); -} - void WebEntityItem::setSourceUrl(const QString& value) { withWriteLock([&] { _needsRenderUpdate |= _sourceUrl != value; @@ -365,6 +288,17 @@ bool WebEntityItem::getUseBackground() const { return resultWithReadLock([&] { return _useBackground; }); } +void WebEntityItem::setUserAgent(const QString& value) { + withWriteLock([&] { + _needsRenderUpdate |= _userAgent != value; + _userAgent = value; + }); +} + +QString WebEntityItem::getUserAgent() const { + return resultWithReadLock([&] { return _userAgent; }); +} + PulsePropertyGroup WebEntityItem::getPulseProperties() const { return resultWithReadLock([&] { return _pulseProperties; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 4b39261bfb..cc689c8998 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -45,26 +45,12 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - glm::vec3 getRaycastDimensions() const override; - virtual bool supportsDetailedIntersection() const override { return true; } - virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; - virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; - glm::u8vec3 getColor() const; void setColor(const glm::u8vec3& value); float getAlpha() const; void setAlpha(float alpha); - void setBillboardMode(BillboardMode value); - BillboardMode getBillboardMode() const; - static const QString DEFAULT_SOURCE_URL; void setSourceUrl(const QString& value); QString getSourceUrl() const; @@ -89,6 +75,10 @@ public: bool getUseBackground() const; void setUseBackground(bool value); + + static const QString DEFAULT_USER_AGENT; + QString getUserAgent() const; + void setUserAgent(const QString& value); PulsePropertyGroup getPulseProperties() const; @@ -96,7 +86,6 @@ protected: glm::u8vec3 _color; float _alpha { 1.0f }; PulsePropertyGroup _pulseProperties; - BillboardMode _billboardMode; QString _sourceUrl; uint16_t _dpi; @@ -105,6 +94,7 @@ protected: WebInputMode _inputMode; bool _showKeyboardFocusHighlight; bool _useBackground; + QString _userAgent; bool _localSafeContext { false }; }; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 8b020e92d5..3eabfb4f1e 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -318,15 +318,15 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) { } bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return _zonesArePickable; } bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return _zonesArePickable; } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 9c8e3839b7..2b61bbd346 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -116,12 +116,12 @@ public: virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& acceleration, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; bool contains(const glm::vec3& point) const override; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 38aca093cd..8126988294 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -153,7 +153,7 @@ void GLBackend::init() { if (vendor.contains("NVIDIA") ) { qCDebug(gpugllogging) << "NVIDIA card detected"; -#if !defined(Q_OS_ANDROID) +#if !defined(Q_OS_ANDROID) && !defined(USE_GLES) GL_GET_INTEGER(GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX); GL_GET_INTEGER(GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX); GL_GET_INTEGER(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX); @@ -170,7 +170,7 @@ void GLBackend::init() { } else if (vendor.contains("ATI")) { qCDebug(gpugllogging) << "ATI card detected"; -#if !defined(Q_OS_ANDROID) +#if !defined(Q_OS_ANDROID) && !defined(USE_GLES) GL_GET_INTEGER(TEXTURE_FREE_MEMORY_ATI); #endif @@ -225,12 +225,12 @@ size_t GLBackend::getAvailableMemory() { switch( _videoCard ) { case NVIDIA: -#if !defined(Q_OS_ANDROID) +#if !defined(Q_OS_ANDROID) && !defined(USE_GLES) glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem[0]); #endif return mem[0] * BYTES_PER_KIB; case ATI: -#if !defined(Q_OS_ANDROID) +#if !defined(Q_OS_ANDROID) && !defined(USE_GLES) glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem[0]); #endif return mem[0] * BYTES_PER_KIB; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp index af865b3ad7..0712f68541 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp @@ -8,8 +8,10 @@ #include "GLTexture.h" +#include #include #include +#include #include "GLBackend.h" @@ -64,7 +66,7 @@ public: protected: class TextureBufferThread : public QThread { public: - TextureBufferThread(GLTextureTransferEngineDefault& parent) : _parent(parent) { start(); } + TextureBufferThread(GLTextureTransferEngineDefault& parent) : _parent(parent) {} protected: void run() override { @@ -302,6 +304,10 @@ void GLTextureTransferEngineDefault::processTransferQueues() { #if THREADED_TEXTURE_BUFFERING if (!_transferThread) { _transferThread = new TextureBufferThread(*this); + QString name = "TextureBufferThread"; + _transferThread->setObjectName(name); + QObject::connect(_transferThread, &QThread::started, [name] { setThreadName(name.toStdString()); }); + _transferThread->start(); } #endif diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index 8fb0720c0d..dd13d6d4f3 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -104,7 +104,7 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const { auto checkEachPrimitive = [=](HFMMesh& mesh, QVector indices, int primitiveSize) -> bool { // Check whether the point is "behind" all the primitives. // But first must transform from model-frame into mesh-frame - glm::vec3 transformedPoint = glm::vec3(glm::inverse(mesh.modelTransform) * glm::vec4(point, 1.0f)); + glm::vec3 transformedPoint = glm::vec3(glm::inverse(offset * mesh.modelTransform) * glm::vec4(point, 1.0f)); int verticesSize = mesh.vertices.size(); for (int j = 0; j < indices.size() - 2; // -2 in case the vertices aren't the right size -- we access j + 2 below diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index d867b49b30..3da5b53dcb 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -1197,7 +1197,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) { } // We now setup a timer here to fire every so often to check that our IP address has not changed. - // Or, if we failed - if will check if we can eventually get a public socket + // Or, if we failed - it will check if we can eventually get a public socket const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 10 * 1000; QTimer* stunOccasionalTimer = new QTimer { this }; diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index b64a0845ac..ccdb9ea75c 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -25,6 +25,8 @@ namespace NetworkingConstants { // You can avoid changing that and still effectively use a connected domain on staging // if you manually generate a personal access token for the domains scope // at https://staging.highfidelity.com/user/tokens/new?for_domain_server=true + + const QString WEB_ENGINE_VERSION = "Chrome/69.0.3497.113"; // For now we only have one Metaverse server. const QUrl METAVERSE_SERVER_URL_STABLE { "https://metaverse.vircadia.com/live" }; @@ -37,15 +39,16 @@ namespace NetworkingConstants { // Use a custom User-Agent to avoid ModSecurity filtering, e.g. by hosting providers. const QByteArray VIRCADIA_USER_AGENT = "Mozilla/5.0 (VircadiaInterface)"; - const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (VircadiaInterface)"; - const QString METAVERSE_USER_AGENT = "Chrome/48.0 (VircadiaInterface)"; - const QString MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"; - - const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); - const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml"); + const QString WEB_ENGINE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) " + WEB_ENGINE_VERSION + " Mobile Safari/537.36"; + const QString MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) " + WEB_ENGINE_VERSION + " Mobile Safari/537.36"; // WebEntity Defaults const QString WEB_ENTITY_DEFAULT_SOURCE_URL = "https://vircadia.com/"; + const QString WEB_ENTITY_DEFAULT_USER_AGENT = WEB_ENGINE_USER_AGENT; + + // Builds URLs + const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); + const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml"); const QString DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav"; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 0b9d5a2d60..f4e4cca482 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "AssetResourceRequest.h" #include "FileResourceRequest.h" @@ -28,12 +29,16 @@ #include "NetworkingConstants.h" ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { - _thread.setObjectName("Resource Manager Thread"); + QString name = "Resource Manager Thread"; + _thread.setObjectName(name); if (_atpSupportEnabled) { auto assetClient = DependencyManager::set(); assetClient->moveToThread(&_thread); - QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::initCaching); + QObject::connect(&_thread, &QThread::started, assetClient.data(), [assetClient, name] { + setThreadName(name.toStdString()); + assetClient->initCaching(); + }); } _thread.start(); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index fc74f4459c..1d96969035 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -283,6 +283,10 @@ enum class EntityVersion : PacketVersion { ZoneOcclusion, ModelBlendshapes, TransparentWeb, + UseOriginalPivot, + UserAgent, + AllBillboardMode, + TextAlignment, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 2997c272f9..63447f6e70 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -31,6 +31,7 @@ #include "Socket.h" #include #include +#include #include "../NetworkLogging.h" @@ -67,24 +68,26 @@ const microseconds SendQueue::MINIMUM_ESTIMATED_TIMEOUT = milliseconds(10); std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber, MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - + auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber, currentMessageNumber, hasReceivedHandshakeACK)); // Setup queue private thread - QThread* thread = new QThread; - thread->setObjectName("Networking: SendQueue " + destination.objectName()); // Name thread for easier debug - + QThread* thread = new QThread(); + QString name = "Networking: SendQueue " + destination.objectName(); + thread->setObjectName(name); // Name thread for easier debug + + connect(thread, &QThread::started, [name] { setThreadName(name.toStdString()); }); connect(thread, &QThread::started, queue.get(), &SendQueue::run); - + connect(queue.get(), &QObject::destroyed, thread, &QThread::quit); // Thread auto cleanup connect(thread, &QThread::finished, thread, &QThread::deleteLater); // Thread auto cleanup - + // Move queue to private thread and start it queue->moveToThread(thread); - + thread->start(); - + return queue; } diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 2050dd1487..583f090942 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -41,6 +41,7 @@ #include "PulseMode.h" #include "GizmoType.h" #include "TextEffect.h" +#include "TextAlignment.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -278,6 +279,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, PulseMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, GizmoType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, TextEffect& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, TextAlignment& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); diff --git a/libraries/oculusMobile/src/ovr/Helpers.h b/libraries/oculusMobile/src/ovr/Helpers.h index 2bd0b7f603..10058dbac8 100644 --- a/libraries/oculusMobile/src/ovr/Helpers.h +++ b/libraries/oculusMobile/src/ovr/Helpers.h @@ -31,9 +31,9 @@ static inline void for_each_eye(const std::function& f) { f(VRAPI_EYE_RIGHT); } -static inline void for_each_hand(const std::function& f) { - f(VRAPI_HAND_LEFT); - f(VRAPI_HAND_RIGHT); +static inline void for_each_hand(const std::function& f) { + f(VRAPI_TRACKED_DEVICE_HAND_LEFT); + f(VRAPI_TRACKED_DEVICE_HAND_RIGHT); } static inline glm::mat4 toGlm(const ovrMatrix4f& om) { diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp index aff1f256b5..1fe4d19401 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.cpp +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -324,7 +324,7 @@ struct VrSurface : public TaskQueue { vrapi_SetTrackingSpace( session, VRAPI_TRACKING_SPACE_LOCAL); vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, gettid()); vrapi_SetClockLevels(session, 2, 4); - vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC); + vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_ON); // Generates a warning on the quest: "vrapi_SetDisplayRefreshRate: Dynamic Display Refresh Rate not supported" // vrapi_SetDisplayRefreshRate(session, 72); }); diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp index 705045b517..54a796954e 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp @@ -31,7 +31,7 @@ const quint64 LOST_TRACKING_DELAY = 3000000; namespace ovr { - controller::Pose toControllerPose(ovrHandedness hand, const ovrRigidBodyPosef& handPose) { + controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { // When the sensor-to-world rotation is identity the coordinate axes look like this: // // user @@ -111,7 +111,7 @@ namespace ovr { return pose; } - controller::Pose toControllerPose(ovrHandedness hand, + controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose, const ovrRigidBodyPosef& lastHandPose) { static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); @@ -165,9 +165,9 @@ public: private: void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, - ovrHandedness hand, const ovrRigidBodyPosef& handPose); + ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose); void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, - ovrHandedness hand, const ovrRigidBodyPosef& handPose); + ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose); void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrRigidBodyPosef& headPose); @@ -379,9 +379,9 @@ void OculusMobileInputDevice::update(float deltaTime, const controller::InputCal handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose); static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED; - ovr::for_each_hand([&](ovrHandedness hand) { - size_t handIndex = (hand == VRAPI_HAND_LEFT) ? 0 : 1; - int controller = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; + ovr::for_each_hand([&](ovrTrackedDeviceTypeId hand) { + size_t handIndex = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? 0 : 1; + int controller = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& handData = _hands[handIndex]; const auto& tracking = handData.tracking; ++numTrackedControllers; @@ -476,7 +476,7 @@ void OculusMobileInputDevice::focusOutEvent() { void OculusMobileInputDevice::handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, - ovrHandedness hand, const ovrRigidBodyPosef& handPose) { + ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { auto poseId = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; pose = ovr::toControllerPose(hand, handPose); @@ -507,7 +507,7 @@ void OculusMobileInputDevice::handleHeadPose(float deltaTime, } void OculusMobileInputDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, - ovrHandedness hand, const ovrRigidBodyPosef& handPose) { + ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) { auto poseId = (hand == VRAPI_HAND_LEFT ? controller::LEFT_HAND : controller::RIGHT_HAND); auto& pose = _poseStateMap[poseId]; const auto& lastHandPose = (hand == VRAPI_HAND_LEFT) ? _hands[0].lastPose : _hands[1].lastPose; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index e222692aea..777a3d3f87 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -107,12 +107,12 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const static uint32_t _numCharacterControllers { 0 }; -CharacterController::CharacterController() { +CharacterController::CharacterController(const FollowTimePerType& followTimeRemainingPerType) : + _followTimeRemainingPerType(followTimeRemainingPerType) { _floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; _targetVelocity.setValue(0.0f, 0.0f, 0.0f); _followDesiredBodyTransform.setIdentity(); - _followTimeRemaining = 0.0f; _state = State::Hover; _isPushingUp = false; _rayHitStartTime = 0; @@ -350,64 +350,103 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity; computeNewVelocity(dt, velocity); - const float MINIMUM_TIME_REMAINING = 0.005f; - const float MAX_DISPLACEMENT = 0.5f * _radius; - _followTimeRemaining -= dt; - if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) { - btTransform bodyTransform = _rigidBody->getWorldTransform(); + constexpr float MINIMUM_TIME_REMAINING = 0.005f; + static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true."); + bool hasFollowTimeRemaining = false; + for (float followTime : _followTimeRemainingPerType) { + if (followTime > MINIMUM_TIME_REMAINING) { + hasFollowTimeRemaining = true; + break; + } + } + + if (hasFollowTimeRemaining) { + const float MAX_DISPLACEMENT = 0.5f * _radius; + + btTransform bodyTransform = _rigidBody->getWorldTransform(); btVector3 startPos = bodyTransform.getOrigin(); btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; - btVector3 vel = deltaPos / _followTimeRemaining; - btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. + + btVector3 linearDisplacement(0.0f, 0.0f, 0.0f); + { + float horizontalTime = _followTimeRemainingPerType[static_cast(FollowType::Horizontal)]; + float verticalTime = _followTimeRemainingPerType[static_cast(FollowType::Vertical)]; + + if (horizontalTime == FOLLOW_TIME_IMMEDIATE_SNAP) { + linearDisplacement.setX(deltaPos.x()); + linearDisplacement.setZ(deltaPos.z()); + } else if (horizontalTime > MINIMUM_TIME_REMAINING) { + linearDisplacement.setX((deltaPos.x() * dt) / horizontalTime); + linearDisplacement.setZ((deltaPos.z() * dt) / horizontalTime); + } + + if (verticalTime == FOLLOW_TIME_IMMEDIATE_SNAP) { + linearDisplacement.setY(deltaPos.y()); + } else if (verticalTime > MINIMUM_TIME_REMAINING) { + linearDisplacement.setY((deltaPos.y() * dt) / verticalTime); + } + + linearDisplacement = clampLength(linearDisplacement, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. + } + btVector3 endPos = startPos + linearDisplacement; // resolve the simple linearDisplacement _followLinearDisplacement += linearDisplacement; // now for the rotational part... + btQuaternion startRot = bodyTransform.getRotation(); - btQuaternion desiredRot = _followDesiredBodyTransform.getRotation(); // startRot as default rotation btQuaternion endRot = startRot; - // the dot product between two quaternions is equal to +/- cos(angle/2) - // where 'angle' is that of the rotation between them - float qDot = desiredRot.dot(startRot); + float rotationTime = _followTimeRemainingPerType[static_cast(FollowType::Rotation)]; + if (rotationTime > MINIMUM_TIME_REMAINING) { + btQuaternion desiredRot = _followDesiredBodyTransform.getRotation(); - // when the abs() value of the dot product is approximately 1.0 - // then the two rotations are effectively adjacent - const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees - if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) { - if (qDot < 0.0f) { - // the quaternions are actually on opposite hyperhemispheres - // so we move one to agree with the other and negate qDot - desiredRot = -desiredRot; - qDot = -qDot; + // the dot product between two quaternions is equal to +/- cos(angle/2) + // where 'angle' is that of the rotation between them + float qDot = desiredRot.dot(startRot); + + // when the abs() value of the dot product is approximately 1.0 + // then the two rotations are effectively adjacent + const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees + if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) { + if (qDot < 0.0f) { + // the quaternions are actually on opposite hyperhemispheres + // so we move one to agree with the other and negate qDot + desiredRot = -desiredRot; + qDot = -qDot; + } + btQuaternion deltaRot = desiredRot * startRot.inverse(); + + // the axis is the imaginary part, but scaled by sin(angle/2) + btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ()); + axis /= sqrtf(1.0f - qDot * qDot); + + // compute the angle we will resolve for this dt, but don't overshoot + float angle = 2.0f * acosf(qDot); + + if (rotationTime != FOLLOW_TIME_IMMEDIATE_SNAP) { + if (dt < rotationTime) { + angle *= dt / rotationTime; + } + } + + // accumulate rotation + deltaRot = btQuaternion(axis, angle); + _followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize(); + + // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. + btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); + + endRot = deltaRot * startRot; + btVector3 swingDisplacement = + rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); + _followLinearDisplacement += swingDisplacement; } - btQuaternion deltaRot = desiredRot * startRot.inverse(); - - // the axis is the imaginary part, but scaled by sin(angle/2) - btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ()); - axis /= sqrtf(1.0f - qDot * qDot); - - // compute the angle we will resolve for this dt, but don't overshoot - float angle = 2.0f * acosf(qDot); - if (dt < _followTimeRemaining) { - angle *= dt / _followTimeRemaining; - } - - // accumulate rotation - deltaRot = btQuaternion(axis, angle); - _followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize(); - - // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. - btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); - - endRot = deltaRot * startRot; - btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); - _followLinearDisplacement += swingDisplacement; } _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } @@ -606,8 +645,7 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) { _parentVelocity = glmToBullet(velocity); } -void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) { - _followTimeRemaining = timeRemaining; +void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) { _followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset)); } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index e7ad3ddfa8..8242ae4b97 100755 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -53,7 +53,20 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f; class CharacterController : public btCharacterControllerInterface { public: - CharacterController(); + enum class FollowType : uint8_t { + Rotation, + Horizontal, + Vertical, + Count + }; + + // Remaining follow time for each FollowType + typedef std::array(FollowType::Count)> FollowTimePerType; + + // Follow time value meaning that we should snap immediately to the target. + static constexpr float FOLLOW_TIME_IMMEDIATE_SNAP = FLT_MAX; + + CharacterController(const FollowTimePerType& followTimeRemainingPerType); virtual ~CharacterController(); bool needsRemoval() const; bool needsAddition() const; @@ -99,7 +112,8 @@ public: void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const; void setParentVelocity(const glm::vec3& parentVelocity); - void setFollowParameters(const glm::mat4& desiredWorldMatrix, float timeRemaining); + + void setFollowParameters(const glm::mat4& desiredWorldMatrix); float getFollowTime() const { return _followTime; } glm::vec3 getFollowLinearDisplacement() const; glm::quat getFollowAngularDisplacement() const; @@ -144,7 +158,7 @@ public: void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } void setSeated(bool isSeated) { _isSeated = isSeated; } - bool getSeated() { return _isSeated; } + bool getSeated() const { return _isSeated; } void resetStuckCounter() { _numStuckSubsteps = 0; } @@ -178,7 +192,7 @@ protected: btVector3 _preSimulationVelocity; btVector3 _velocityChange; btTransform _followDesiredBodyTransform; - btScalar _followTimeRemaining; + const FollowTimePerType& _followTimeRemainingPerType; btTransform _characterBodyTransform; btVector3 _position; btQuaternion _rotation; diff --git a/libraries/procedural/CMakeLists.txt b/libraries/procedural/CMakeLists.txt index d737d34e95..a7394a3670 100644 --- a/libraries/procedural/CMakeLists.txt +++ b/libraries/procedural/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME procedural) setup_hifi_library() -link_hifi_libraries(shared gpu shaders networking graphics material-networking ktx image hfm) +link_hifi_libraries(shared gpu shaders networking render graphics material-networking ktx image hfm) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 88c111f8fd..66dde1ca56 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -125,7 +125,7 @@ Procedural::Procedural() { opaqueStencil(_opaqueState); _transparentState->setCullMode(gpu::State::CULL_NONE); - _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); + _transparentState->setDepthTest(true, false, gpu::LESS_EQUAL); _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 9b3d0a9bd4..7d655b5ef4 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -113,9 +114,9 @@ public: void setDoesFade(bool doesFade) { _doesFade = doesFade; } bool hasVertexShader() const; - void setBoundOperator(const std::function& boundOperator) { _boundOperator = boundOperator; } + void setBoundOperator(const std::function& boundOperator) { _boundOperator = boundOperator; } bool hasBoundOperator() const { return (bool)_boundOperator; } - AABox getBound() { return _boundOperator(); } + AABox getBound(RenderArgs* args) { return _boundOperator(args); } gpu::Shader::Source _vertexSource; gpu::Shader::Source _vertexSourceSkinned; @@ -199,7 +200,7 @@ private: bool _doesFade { true }; ProceduralProgramKey _prevKey; - std::function _boundOperator { nullptr }; + std::function _boundOperator { nullptr }; mutable std::mutex _mutex; }; @@ -232,9 +233,9 @@ public: void initializeProcedural(); - void setBoundOperator(const std::function& boundOperator) { _procedural.setBoundOperator(boundOperator); } + void setBoundOperator(const std::function& boundOperator) { _procedural.setBoundOperator(boundOperator); } bool hasBoundOperator() const { return _procedural.hasBoundOperator(); } - AABox getBound() { return _procedural.getBound(); } + AABox getBound(RenderArgs* args) { return _procedural.getBound(args); } private: QString _proceduralString; diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 55788c8a02..c5411a6195 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -29,6 +29,7 @@ #include "RenderControl.h" #include "RenderEventHandler.h" #include "TextureCache.h" +#include // Time between receiving a request to render the offscreen UI actually triggering // the render. Could possibly be increased depending on the framerate we expect to @@ -162,7 +163,9 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { // Create the render thread _renderThread = new QThread(); - _renderThread->setObjectName(objectName()); + QString name = objectName(); + _renderThread->setObjectName(name); + QObject::connect(_renderThread, &QThread::started, [name] { setThreadName("QML SharedObject " + name.toStdString()); }); _renderThread->start(); // Create event handler for the render thread diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index c4020cb4c4..770ac0b7dd 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -68,7 +68,7 @@ typedef render::Payload AnimDebugDrawPayload; namespace render { template <> const ItemKey payloadGetKey(const AnimDebugDrawData::Pointer& data) { return (data->_isVisible ? ItemKey::Builder::transparentShape() : ItemKey::Builder::transparentShape().withInvisible()).withTagBits(ItemKey::TAG_BITS_ALL); } - template <> const Item::Bound payloadGetBound(const AnimDebugDrawData::Pointer& data) { return data->_bound; } + template <> const Item::Bound payloadGetBound(const AnimDebugDrawData::Pointer& data, RenderArgs* args) { return data->_bound; } template <> void payloadRender(const AnimDebugDrawData::Pointer& data, RenderArgs* args) { data->render(args); } diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 9c7a2ee60b..dc1ffb7b67 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -19,8 +19,8 @@ using namespace render; CauterizedMeshPartPayload::CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, - const Transform& transform, const Transform& offsetTransform, const uint64_t& created) - : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform, created) {} + const Transform& transform, const uint64_t& created) + : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, created) {} void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices, const std::vector& cauterizedClusterMatrices) { @@ -52,11 +52,26 @@ void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices, @@ -23,9 +23,9 @@ public: void updateClusterBuffer(const std::vector& clusterDualQuaternions, const std::vector& cauterizedClusterQuaternions); - void updateTransformForCauterizedMesh(const Transform& renderTransform); + void updateTransformForCauterizedMesh(const Transform& modelTransform, const Model::MeshState& meshState, bool useDualQuaternionSkinning); - void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override; + void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const override; void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; } diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 94b7661b2f..a5d27dc7d2 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -69,10 +69,6 @@ void CauterizedModel::createRenderItemSet() { transform.setTranslation(_translation); transform.setRotation(_rotation); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); @@ -85,7 +81,7 @@ void CauterizedModel::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset, _created); + auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, _created); _modelMeshRenderItems << std::static_pointer_cast(ptr); auto material = getGeometry()->getShapeMaterial(shapeID); _modelMeshMaterialNames.push_back(material ? material->getName() : ""); @@ -235,37 +231,8 @@ void CauterizedModel::updateRenderItems() { data.computeAdjustedLocalBound(meshState.clusterMatrices); } - Transform renderTransform = modelTransform; - if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { - const auto& dq = meshState.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = modelTransform.worldTransform(transform); - } - } else { - if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { - renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); - } - } - data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - - renderTransform = modelTransform; - if (useDualQuaternionSkinning) { - if (cauterizedMeshState.clusterDualQuaternions.size() == 1 || cauterizedMeshState.clusterDualQuaternions.size() == 2) { - const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = modelTransform.worldTransform(Transform(transform)); - } - } else { - if (cauterizedMeshState.clusterMatrices.size() == 1 || cauterizedMeshState.clusterMatrices.size() == 2) { - renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0])); - } - } - data.updateTransformForCauterizedMesh(renderTransform); + data.updateTransformForSkinnedMesh(modelTransform, meshState, useDualQuaternionSkinning); + data.updateTransformForCauterizedMesh(modelTransform, cauterizedMeshState, useDualQuaternionSkinning); data.setEnableCauterization(enableCauterization); data.updateKey(renderItemKeyGlobalFlags); diff --git a/libraries/render-utils/src/FadeEffectJobs.cpp b/libraries/render-utils/src/FadeEffectJobs.cpp index 43206e77fa..6f6205060a 100644 --- a/libraries/render-utils/src/FadeEffectJobs.cpp +++ b/libraries/render-utils/src/FadeEffectJobs.cpp @@ -575,7 +575,7 @@ void FadeJob::run(const render::RenderContextPointer& renderContext, FadeJob::Ou auto& item = scene->getItem(state.itemId); assert(item.getTransitionId() == transitionId); #endif - if (update(*jobConfig, scene, transaction, state, deltaTime)) { + if (update(renderContext->args, *jobConfig, scene, transaction, state, deltaTime)) { hasTransaction = true; } if (isFirstItem && (state.threshold != jobConfig->threshold)) { @@ -599,7 +599,7 @@ const FadeCategory FadeJob::transitionToCategory[render::Transition::TYPE_COUNT] FADE_AVATAR_CHANGE }; -bool FadeJob::update(const Config& config, const render::ScenePointer& scene, render::Transaction& transaction, render::Transition& transition, const double deltaTime) const { +bool FadeJob::update(RenderArgs* args, const Config& config, const render::ScenePointer& scene, render::Transaction& transaction, render::Transition& transition, const double deltaTime) const { const auto fadeCategory = transitionToCategory[transition.eventType]; auto& eventConfig = config.events[fadeCategory]; auto item = scene->getItemSafe(transition.itemId); @@ -607,11 +607,11 @@ bool FadeJob::update(const Config& config, const render::ScenePointer& scene, re const FadeConfig::Timing timing = (FadeConfig::Timing) eventConfig.timing; if (item.exist()) { - auto aabb = item.getBound(); + auto aabb = item.getBound(args); if (render::Item::isValidID(transition.boundItemId)) { auto boundItem = scene->getItemSafe(transition.boundItemId); if (boundItem.exist()) { - aabb = boundItem.getBound(); + aabb = boundItem.getBound(args); } } auto& dimensions = aabb.getDimensions(); diff --git a/libraries/render-utils/src/FadeEffectJobs.h b/libraries/render-utils/src/FadeEffectJobs.h index 449995dba5..08b49c8d2d 100644 --- a/libraries/render-utils/src/FadeEffectJobs.h +++ b/libraries/render-utils/src/FadeEffectJobs.h @@ -223,7 +223,7 @@ private: float _thresholdScale[FADE_CATEGORY_COUNT]; uint64_t _previousTime{ 0 }; - bool update(const Config& config, const render::ScenePointer& scene, render::Transaction& transaction, render::Transition& transition, const double deltaTime) const; + bool update(RenderArgs* args, const Config& config, const render::ScenePointer& scene, render::Transaction& transaction, render::Transition& transition, const double deltaTime) const; static float computeElementEnterRatio(double time, const double period, FadeConfig::Timing timing); }; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index ea66ac19ec..325e228120 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2027,7 +2027,7 @@ void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bo for (auto& key : keys) { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL); if (std::get<0>(key)) { PrepareStencil::testMask(*state); } else { @@ -2135,7 +2135,7 @@ gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent, bool auto pipeline = (transparent || forward) ? web_browser_forward : web_browser; gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(true, !transparent, gpu::LESS_EQUAL); // FIXME: do we need a testMaskDrawNoAA? PrepareStencil::testMaskDrawShapeNoAA(*state); state->setBlendFunction(transparent, @@ -2207,7 +2207,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp } else { state->setCullMode(gpu::State::CULL_BACK); } - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(true, !config.isTransparent(), gpu::LESS_EQUAL); if (config.hasDepthBias()) { state->setDepthBias(1.0f); state->setDepthBiasSlopeScale(1.0f); diff --git a/libraries/render-utils/src/LightPayload.cpp b/libraries/render-utils/src/LightPayload.cpp index 79e0c1d94d..d1018982d0 100644 --- a/libraries/render-utils/src/LightPayload.cpp +++ b/libraries/render-utils/src/LightPayload.cpp @@ -28,7 +28,7 @@ namespace render { return builder.build(); } - template <> const Item::Bound payloadGetBound(const LightPayload::Pointer& payload) { + template <> const Item::Bound payloadGetBound(const LightPayload::Pointer& payload, RenderArgs* args) { if (payload) { return payload->editBound(); } @@ -98,7 +98,7 @@ namespace render { return builder.build(); } - template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload) { + template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload, RenderArgs* args) { if (payload) { return payload->editBound(); } diff --git a/libraries/render-utils/src/LightPayload.h b/libraries/render-utils/src/LightPayload.h index 44b79ce10c..4c6695ec58 100644 --- a/libraries/render-utils/src/LightPayload.h +++ b/libraries/render-utils/src/LightPayload.h @@ -43,7 +43,7 @@ protected: namespace render { template <> const ItemKey payloadGetKey(const LightPayload::Pointer& payload); - template <> const Item::Bound payloadGetBound(const LightPayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const LightPayload::Pointer& payload, RenderArgs* args); template <> void payloadRender(const LightPayload::Pointer& payload, RenderArgs* args); } @@ -79,7 +79,7 @@ protected: namespace render { template <> const ItemKey payloadGetKey(const KeyLightPayload::Pointer& payload); - template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload, RenderArgs* args); template <> void payloadRender(const KeyLightPayload::Pointer& payload, RenderArgs* args); } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 84aed55d72..8ba5be54e6 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -11,8 +11,7 @@ #include "MeshPartPayload.h" -#include - +#include #include #include #include @@ -23,292 +22,33 @@ #include "RenderPipelines.h" -// static const QString ENABLE_MATERIAL_PROCEDURAL_SHADERS_STRING { "HIFI_ENABLE_MATERIAL_PROCEDURAL_SHADERS" }; -// static bool ENABLE_MATERIAL_PROCEDURAL_SHADERS = QProcessEnvironment::systemEnvironment().contains(ENABLE_MATERIAL_PROCEDURAL_SHADERS_STRING); - -bool MeshPartPayload::enableMaterialProceduralShaders = false; - using namespace render; -namespace render { -template <> const ItemKey payloadGetKey(const MeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getKey(); - } - return ItemKey::Builder::opaqueShape(); // for lack of a better idea -} - -template <> const Item::Bound payloadGetBound(const MeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getBound(); - } - return Item::Bound(); -} - -template <> const ShapeKey shapeGetShapeKey(const MeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getShapeKey(); - } - return ShapeKey::Builder::invalid(); -} - -template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderArgs* args) { - return payload->render(args); -} - -template <> bool payloadPassesZoneOcclusionTest(const MeshPartPayload::Pointer& payload, const std::unordered_set& containingZones) { - if (payload) { - return payload->passesZoneOcclusionTest(containingZones); - } - return false; -} -} - -MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material, const uint64_t& created) : - _created(created) -{ - updateMeshPart(mesh, partIndex); - addMaterial(graphics::MaterialLayer(material, 0)); -} - -void MeshPartPayload::updateMeshPart(const std::shared_ptr& drawMesh, int partIndex) { - _drawMesh = drawMesh; - if (_drawMesh) { - auto vertexFormat = _drawMesh->getVertexFormat(); - _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); - _drawPart = _drawMesh->getPartBuffer().get(partIndex); - _localBound = _drawMesh->evalPartBound(partIndex); - } -} - -void MeshPartPayload::updateTransform(const Transform& transform, const Transform& offsetTransform) { - _transform = transform; - Transform::mult(_drawTransform, _transform, offsetTransform); - _worldBound = _localBound; - _worldBound.transform(_drawTransform); -} - -void MeshPartPayload::addMaterial(graphics::MaterialLayer material) { - _drawMaterials.push(material); -} - -void MeshPartPayload::removeMaterial(graphics::MaterialPointer material) { - _drawMaterials.remove(material); -} - -void MeshPartPayload::updateKey(const render::ItemKey& key) { - ItemKey::Builder builder(key); - builder.withTypeShape(); - - if (_drawMaterials.shouldUpdate()) { - RenderPipelines::updateMultiMaterial(_drawMaterials); - } - - auto matKey = _drawMaterials.getMaterialKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } - - if (_cullWithParent) { - builder.withSubMetaCulled(); - } - - _itemKey = builder.build(); -} - -ItemKey MeshPartPayload::getKey() const { - return _itemKey; -} - -Item::Bound MeshPartPayload::getBound() const { - graphics::MaterialPointer material = _drawMaterials.empty() ? nullptr : _drawMaterials.top().material; - if (material && material->isProcedural() && material->isReady()) { - auto procedural = std::static_pointer_cast(_drawMaterials.top().material); - if (procedural->hasVertexShader() && procedural->hasBoundOperator()) { - return procedural->getBound(); - } - } - return _worldBound; -} - -ShapeKey MeshPartPayload::getShapeKey() const { - ShapeKey::Builder builder; - graphics::MaterialPointer material = _drawMaterials.empty() ? nullptr : _drawMaterials.top().material; - graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey(); - - if (drawMaterialKey.isTranslucent()) { - builder.withTranslucent(); - } - - if (material && material->isProcedural() && material->isReady()) { - builder.withOwnPipeline(); - } else { - builder.withMaterial(); - - if (drawMaterialKey.isNormalMap()) { - builder.withTangents(); - } - if (drawMaterialKey.isLightMap()) { - builder.withLightMap(); - } - if (drawMaterialKey.isUnlit()) { - builder.withUnlit(); - } - if (material) { - builder.withCullFaceMode(material->getCullFaceMode()); - } - } - - return builder.build(); -} - -void MeshPartPayload::drawCall(gpu::Batch& batch) const { - batch.drawIndexed(gpu::TRIANGLES, _drawPart._numIndices, _drawPart._startIndex); -} - -void MeshPartPayload::bindMesh(gpu::Batch& batch) { - batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - - batch.setInputFormat((_drawMesh->getVertexFormat())); - - batch.setInputStream(0, _drawMesh->getVertexStream()); -} - -void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { - batch.setModelTransform(_drawTransform); -} - -bool MeshPartPayload::passesZoneOcclusionTest(const std::unordered_set& containingZones) const { - if (!_renderWithZones.isEmpty()) { - if (!containingZones.empty()) { - for (auto renderWithZone : _renderWithZones) { - if (containingZones.find(renderWithZone) != containingZones.end()) { - return true; - } - } - } - return false; - } - return true; -} - -void MeshPartPayload::render(RenderArgs* args) { - PerformanceTimer perfTimer("MeshPartPayload::render"); - - if (!args) { - return; - } - - gpu::Batch& batch = *(args->_batch); - - // Bind the model transform and the skinCLusterMatrices if needed - bindTransform(batch, args->_renderMode); - - //Bind the index buffer and vertex buffer and Blend shapes if needed - bindMesh(batch); - - if (!_drawMaterials.empty() && _drawMaterials.top().material && _drawMaterials.top().material->isProcedural() && - _drawMaterials.top().material->isReady()) { - if (!enableMaterialProceduralShaders) { - return; - } - auto procedural = std::static_pointer_cast(_drawMaterials.top().material); - auto& schema = _drawMaterials.getSchemaBuffer().get(); - glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity); - outColor = procedural->getColor(outColor); - procedural->prepare(batch, _drawTransform.getTranslation(), _drawTransform.getScale(), _drawTransform.getRotation(), _created, - ProceduralProgramKey(outColor.a < 1.0f)); - batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); - } else { - // apply material properties - if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) { - args->_details._materialSwitches++; - } - } - - // Draw! - { - PerformanceTimer perfTimer("batch.drawIndexed()"); - drawCall(batch); - } - - const int INDICES_PER_TRIANGLE = 3; - args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; -} - -namespace render { -template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getKey(); - } - return ItemKey::Builder::opaqueShape(); // for lack of a better idea -} - -template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getBound(); - } - return Item::Bound(); -} - -template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getShapeKey(); - } - return ShapeKey::Builder::invalid(); -} - -template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args) { - return payload->render(args); -} - -template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set& containingZones) { - if (payload) { - return payload->passesZoneOcclusionTest(containingZones); - } - return false; -} -} +bool ModelMeshPartPayload::enableMaterialProceduralShaders = false; ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, - const Transform& transform, const Transform& offsetTransform, const uint64_t& created) : + const Transform& transform, const uint64_t& created) : _meshIndex(meshIndex), - _shapeID(shapeIndex) { + _created(created) { assert(model && model->isLoaded()); - bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); - auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); _meshNumVertices = (int)modelMesh->getNumVertices(); const Model::MeshState& state = model->getMeshState(_meshIndex); updateMeshPart(modelMesh, partIndex); + bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); if (useDualQuaternionSkinning) { computeAdjustedLocalBound(state.clusterDualQuaternions); } else { computeAdjustedLocalBound(state.clusterMatrices); } - updateTransform(transform, offsetTransform); - Transform renderTransform = transform; - if (useDualQuaternionSkinning) { - if (state.clusterDualQuaternions.size() == 1) { - const auto& dq = state.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = transform.worldTransform(Transform(transform)); - } - } else { - if (state.clusterMatrices.size() == 1) { - renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0])); - } - } - updateTransformForSkinnedMesh(renderTransform, transform); + updateTransformForSkinnedMesh(transform, state, useDualQuaternionSkinning); - initCache(model); + initCache(model, shapeIndex); #if defined(Q_OS_MAC) || defined(Q_OS_ANDROID) // On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline @@ -322,14 +62,11 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in _meshBlendshapeBuffer = std::make_shared(sizeof(BlendshapeOffset), reinterpret_cast(&data), sizeof(BlendshapeOffset)); } #endif - - _created = created; } -void ModelMeshPartPayload::initCache(const ModelPointer& model) { +void ModelMeshPartPayload::initCache(const ModelPointer& model, int shapeID) { if (_drawMesh) { auto vertexFormat = _drawMesh->getVertexFormat(); - _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); const HFMModel& hfmModel = model->getHFMModel(); @@ -339,18 +76,22 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { _hasTangents = !mesh.tangents.isEmpty(); } - auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); + auto networkMaterial = model->getGeometry()->getShapeMaterial(shapeID); if (networkMaterial) { addMaterial(graphics::MaterialLayer(networkMaterial, 0)); } } -void ModelMeshPartPayload::notifyLocationChanged() { - +void ModelMeshPartPayload::updateMeshPart(const std::shared_ptr& drawMesh, int partIndex) { + _drawMesh = drawMesh; + if (_drawMesh) { + auto vertexFormat = _drawMesh->getVertexFormat(); + _drawPart = _drawMesh->getPartBuffer().get(partIndex); + _localBound = _drawMesh->evalPartBound(partIndex); + } } void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices) { - // reset cluster buffer if we change the cluster buffer type if (_clusterBufferType != ClusterBufferType::Matrices) { _clusterBuffer.reset(); @@ -370,7 +111,6 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clu } void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterDualQuaternions) { - // reset cluster buffer if we change the cluster buffer type if (_clusterBufferType != ClusterBufferType::DualQuaternions) { _clusterBuffer.reset(); @@ -389,13 +129,76 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices) { + _adjustedLocalBound = _localBound; + if (clusterMatrices.size() > 0) { + _adjustedLocalBound.transform(clusterMatrices.back()); + + for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) { + AABox clusterBound = _localBound; + clusterBound.transform(clusterMatrices[i]); + _adjustedLocalBound += clusterBound; + } + } +} + +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions) { + _adjustedLocalBound = _localBound; + if (clusterDualQuaternions.size() > 0) { + Transform rootTransform(clusterDualQuaternions.back().getRotation(), + clusterDualQuaternions.back().getScale(), + clusterDualQuaternions.back().getTranslation()); + _adjustedLocalBound.transform(rootTransform); + + for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) { + AABox clusterBound = _localBound; + Transform transform(clusterDualQuaternions[i].getRotation(), + clusterDualQuaternions[i].getScale(), + clusterDualQuaternions[i].getTranslation()); + clusterBound.transform(transform); + _adjustedLocalBound += clusterBound; + } + } +} + +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& modelTransform, const Model::MeshState& meshState, bool useDualQuaternionSkinning) { + _localTransform = Transform(); + if (useDualQuaternionSkinning) { + if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { + const auto& dq = meshState.clusterDualQuaternions[0]; + _localTransform = Transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + } + } else { + if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { + _localTransform = Transform(meshState.clusterMatrices[0]); + } + } + + _parentTransform = modelTransform; +} + +void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { + batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); + batch.setInputFormat((_drawMesh->getVertexFormat())); + if (_meshBlendshapeBuffer) { + batch.setResourceBuffer(0, _meshBlendshapeBuffer); + } + batch.setInputStream(0, _drawMesh->getVertexStream()); +} + +void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const { + if (_clusterBuffer) { + batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer); + } + batch.setModelTransform(transform); +} + +void ModelMeshPartPayload::drawCall(gpu::Batch& batch) const { + batch.drawIndexed(gpu::TRIANGLES, _drawPart._numIndices, _drawPart._startIndex); } -// Note that this method is called for models but not for shapes void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { ItemKey::Builder builder(key); builder.withTypeShape(); @@ -480,26 +283,33 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, PrimitiveMode pr _shapeKey = builder.build(); } +ItemKey ModelMeshPartPayload::getKey() const { + return _itemKey; +} + +Item::Bound ModelMeshPartPayload::getBound(RenderArgs* args) const { + graphics::MaterialPointer material = _drawMaterials.empty() ? nullptr : _drawMaterials.top().material; + if (material && material->isProcedural() && material->isReady()) { + auto procedural = std::static_pointer_cast(_drawMaterials.top().material); + if (procedural->hasVertexShader() && procedural->hasBoundOperator()) { + return procedural->getBound(args); + } + } + + auto worldBound = _adjustedLocalBound; + auto parentTransform = _parentTransform; + if (args) { + parentTransform.setRotation(BillboardModeHelpers::getBillboardRotation(parentTransform.getTranslation(), parentTransform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + } + worldBound.transform(parentTransform); + return worldBound; +} + ShapeKey ModelMeshPartPayload::getShapeKey() const { return _shapeKey; } -void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { - batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - batch.setInputFormat((_drawMesh->getVertexFormat())); - if (_meshBlendshapeBuffer) { - batch.setResourceBuffer(0, _meshBlendshapeBuffer); - } - batch.setInputStream(0, _drawMesh->getVertexStream()); -} - -void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { - if (_clusterBuffer) { - batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer); - } - batch.setModelTransform(_transform); -} - void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); @@ -509,7 +319,12 @@ void ModelMeshPartPayload::render(RenderArgs* args) { gpu::Batch& batch = *(args->_batch); - bindTransform(batch, args->_renderMode); + Transform transform = _parentTransform; + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + + Transform modelTransform = transform.worldTransform(_localTransform); + bindTransform(batch, modelTransform, args->_renderMode); //Bind the index buffer and vertex buffer and Blend shapes if needed bindMesh(batch); @@ -528,7 +343,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { auto& schema = _drawMaterials.getSchemaBuffer().get(); glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity); outColor = procedural->getColor(outColor); - procedural->prepare(batch, _drawTransform.getTranslation(), _drawTransform.getScale(), _drawTransform.getRotation(), _created, + procedural->prepare(batch, transform.getTranslation(), transform.getScale(), transform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f, _shapeKey.isDeformed(), _shapeKey.isDualQuatSkinned())); batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); } else { @@ -548,36 +363,18 @@ void ModelMeshPartPayload::render(RenderArgs* args) { args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } -void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { - _adjustedLocalBound = _localBound; - if (clusterMatrices.size() > 0) { - _adjustedLocalBound.transform(clusterMatrices.back()); - - for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) { - AABox clusterBound = _localBound; - clusterBound.transform(clusterMatrices[i]); - _adjustedLocalBound += clusterBound; - } - } -} - -void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions) { - _adjustedLocalBound = _localBound; - if (clusterDualQuaternions.size() > 0) { - Transform rootTransform(clusterDualQuaternions.back().getRotation(), - clusterDualQuaternions.back().getScale(), - clusterDualQuaternions.back().getTranslation()); - _adjustedLocalBound.transform(rootTransform); - - for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) { - AABox clusterBound = _localBound; - Transform transform(clusterDualQuaternions[i].getRotation(), - clusterDualQuaternions[i].getScale(), - clusterDualQuaternions[i].getTranslation()); - clusterBound.transform(transform); - _adjustedLocalBound += clusterBound; +bool ModelMeshPartPayload::passesZoneOcclusionTest(const std::unordered_set& containingZones) const { + if (!_renderWithZones.isEmpty()) { + if (!containingZones.empty()) { + for (auto renderWithZone : _renderWithZones) { + if (containingZones.find(renderWithZone) != containingZones.end()) { + return true; + } + } } + return false; } + return true; } void ModelMeshPartPayload::setBlendshapeBuffer(const std::unordered_map& blendshapeBuffers, const QVector& blendedMeshSizes) { @@ -596,3 +393,37 @@ void ModelMeshPartPayload::setBlendshapeBuffer(const std::unordered_map const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload) { + if (payload) { + return payload->getKey(); + } + return ItemKey::Builder::opaqueShape(); // for lack of a better idea +} + +template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args) { + if (payload) { + return payload->getBound(args); + } + return Item::Bound(); +} + +template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload) { + if (payload) { + return payload->getShapeKey(); + } + return ShapeKey::Builder::invalid(); +} + +template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args) { + return payload->render(args); +} + +template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set& containingZones) { + if (payload) { + return payload->passesZoneOcclusionTest(containingZones); + } + return false; +} +} diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 8ed799bd74..b5cff41bf8 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -12,152 +12,98 @@ #ifndef hifi_MeshPartPayload_h #define hifi_MeshPartPayload_h -#include - -#include - -#include - -#include - #include "Model.h" -class Model; +#include +#include +#include -class MeshPartPayload { +class ModelMeshPartPayload { public: - MeshPartPayload() = default; - MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material, const uint64_t& created); - virtual ~MeshPartPayload() = default; - - typedef render::Payload Payload; + typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateKey(const render::ItemKey& key); + ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const uint64_t& created); virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); - virtual void notifyLocationChanged() {} - void updateTransform(const Transform& transform, const Transform& offsetTransform); + void updateClusterBuffer(const std::vector& clusterMatrices); // matrix palette skinning + void updateClusterBuffer(const std::vector& clusterDualQuaternions); // dual quaternion skinning - // Render Item interface - virtual render::ItemKey getKey() const; - virtual render::Item::Bound getBound() const; - virtual render::ShapeKey getShapeKey() const; - virtual void render(RenderArgs* args); + void computeAdjustedLocalBound(const std::vector& clusterMatrices); // matrix palette skinning + void computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); // dual quaternion skinning + + void updateTransformForSkinnedMesh(const Transform& modelTransform, const Model::MeshState& meshState, bool useDualQuaternionSkinning); // ModelMeshPartPayload functions to perform render + void bindMesh(gpu::Batch& batch); + virtual void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const; void drawCall(gpu::Batch& batch) const; - virtual void bindMesh(gpu::Batch& batch); - virtual void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const; - // Payload resource cached values - Transform _drawTransform; - Transform _transform; - int _partIndex = 0; - bool _hasColorAttrib { false }; + void updateKey(const render::ItemKey& key); + void setShapeKey(bool invalidateShapeKey, PrimitiveMode primitiveMode, bool useDualQuaternionSkinning); - graphics::Box _localBound; - graphics::Box _adjustedLocalBound; - mutable graphics::Box _worldBound; - std::shared_ptr _drawMesh; - - graphics::MultiMaterial _drawMaterials; - graphics::Mesh::Part _drawPart; + // Render Item interface + render::ItemKey getKey() const; + render::Item::Bound getBound(RenderArgs* args) const; + render::ShapeKey getShapeKey() const; + void render(RenderArgs* args); size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } size_t getMaterialTextureSize() { return _drawMaterials.getTextureSize(); } int getMaterialTextureCount() { return _drawMaterials.getTextureCount(); } bool hasTextureInfo() const { return _drawMaterials.hasTextureInfo(); } - void addMaterial(graphics::MaterialLayer material); - void removeMaterial(graphics::MaterialPointer material); - + void setCauterized(bool cauterized) { _cauterized = cauterized; } void setCullWithParent(bool value) { _cullWithParent = value; } - void setRenderWithZones(const QVector& renderWithZones) { _renderWithZones = renderWithZones; } + void setBillboardMode(BillboardMode billboardMode) { _billboardMode = billboardMode; } bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const; - static bool enableMaterialProceduralShaders; - -protected: - render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() }; - bool _cullWithParent { false }; - QVector _renderWithZones; - uint64_t _created; -}; - -namespace render { - template <> const ItemKey payloadGetKey(const MeshPartPayload::Pointer& payload); - template <> const Item::Bound payloadGetBound(const MeshPartPayload::Pointer& payload); - template <> const ShapeKey shapeGetShapeKey(const MeshPartPayload::Pointer& payload); - template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderArgs* args); - template <> bool payloadPassesZoneOcclusionTest(const MeshPartPayload::Pointer& payload, const std::unordered_set& containingZones); -} - -class ModelMeshPartPayload : public MeshPartPayload { -public: - ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform, const uint64_t& created); - - typedef render::Payload Payload; - typedef Payload::DataPointer Pointer; - - void notifyLocationChanged() override; - - void updateKey(const render::ItemKey& key) override; - - // matrix palette skinning - void updateClusterBuffer(const std::vector& clusterMatrices); - - // dual quaternion skinning - void updateClusterBuffer(const std::vector& clusterDualQuaternions); - void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); - - // Render Item interface - render::ShapeKey getShapeKey() const override; - void render(RenderArgs* args) override; - - void setShapeKey(bool invalidateShapeKey, PrimitiveMode primitiveMode, bool useDualQuaternionSkinning); - void setCauterized(bool cauterized) { _cauterized = cauterized; } - - // ModelMeshPartPayload functions to perform render - void bindMesh(gpu::Batch& batch) override; - void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override; - - // matrix palette skinning - void computeAdjustedLocalBound(const std::vector& clusterMatrices); - - // dual quaternion skinning - void computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); - - gpu::BufferPointer _clusterBuffer; - - enum class ClusterBufferType { Matrices, DualQuaternions }; - ClusterBufferType _clusterBufferType { ClusterBufferType::Matrices }; - - int _meshIndex; - int _shapeID; - - bool _isSkinned{ false }; - bool _isBlendShaped { false }; - bool _hasTangents { false }; + void addMaterial(graphics::MaterialLayer material) { _drawMaterials.push(material); } + void removeMaterial(graphics::MaterialPointer material) { _drawMaterials.remove(material); } void setBlendshapeBuffer(const std::unordered_map& blendshapeBuffers, const QVector& blendedMeshSizes); + static bool enableMaterialProceduralShaders; + private: - void initCache(const ModelPointer& model); + void initCache(const ModelPointer& model, int shapeID); + + int _meshIndex; + std::shared_ptr _drawMesh; + graphics::Mesh::Part _drawPart; + graphics::MultiMaterial _drawMaterials; + + gpu::BufferPointer _clusterBuffer; + enum class ClusterBufferType { Matrices, DualQuaternions }; + ClusterBufferType _clusterBufferType { ClusterBufferType::Matrices }; gpu::BufferPointer _meshBlendshapeBuffer; int _meshNumVertices; + + render::ItemKey _itemKey { render::ItemKey::Builder::opaqueShape().build() }; render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; + + bool _isSkinned { false }; + bool _isBlendShaped { false }; + bool _hasTangents { false }; bool _prevUseDualQuaternionSkinning { false }; bool _cauterized { false }; + bool _cullWithParent { false }; + QVector _renderWithZones; + BillboardMode _billboardMode; + uint64_t _created; + Transform _localTransform; + Transform _parentTransform; + graphics::Box _localBound; + graphics::Box _adjustedLocalBound; }; namespace render { template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload); - template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload); template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args); template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set& containingZones); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9ad343639d..9fa822994c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -114,7 +114,8 @@ Transform Model::getTransform() const { return transform; } else if (_spatiallyNestableOverride) { bool success; - Transform transform = _spatiallyNestableOverride->getTransform(success); + Transform transform = _billboardMode == BillboardMode::NONE ? _spatiallyNestableOverride->getTransform(success) : + _spatiallyNestableOverride->getTransformWithOnlyLocalRotation(success); if (success) { transform.setScale(getScale()); return transform; @@ -151,6 +152,7 @@ void Model::setOffset(const glm::vec3& offset) { // if someone manually sets our offset, then we are no longer snapped to center _snapModelToRegistrationPoint = false; _snappedToRegistrationPoint = false; + _forceOffset = true; } void Model::calculateTextureInfo() { @@ -226,6 +228,7 @@ void Model::updateRenderItems() { modelTransform.setScale(glm::vec3(1.0f)); PrimitiveMode primitiveMode = self->getPrimitiveMode(); + BillboardMode billboardMode = self->getBillboardMode(); auto renderWithZones = self->getRenderWithZones(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); bool cauterized = self->isCauterized(); @@ -242,7 +245,7 @@ void Model::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, - invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, + invalidatePayloadShapeKey, primitiveMode, billboardMode, renderItemKeyGlobalFlags, cauterized, renderWithZones](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); @@ -252,25 +255,11 @@ void Model::updateRenderItems() { data.computeAdjustedLocalBound(meshState.clusterMatrices); } - Transform renderTransform = modelTransform; - - if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { - const auto& dq = meshState.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = modelTransform.worldTransform(Transform(transform)); - } - } else { - if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { - renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); - } - } - data.updateTransformForSkinnedMesh(renderTransform, modelTransform); + data.updateTransformForSkinnedMesh(modelTransform, meshState, useDualQuaternionSkinning); data.setCauterized(cauterized); data.setRenderWithZones(renderWithZones); + data.setBillboardMode(billboardMode); data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); @@ -332,8 +321,8 @@ void Model::initJointStates() { _rig.initJointStates(hfmModel, modelOffset); } -bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, +bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; @@ -343,7 +332,12 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g } // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); + glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos); + glm::mat4 transRot = createMatFromQuatAndPos(rotation, _translation); + glm::mat4 modelToWorldMatrix = transRot; + if (!_snapModelToRegistrationPoint) { + modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset()); + } glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated @@ -375,8 +369,12 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g calculateTriangleSets(hfmModel); } - glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; + glm::mat4 meshToWorldMatrix = transRot; + if (_snapModelToRegistrationPoint || _forceOffset) { + meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset)); + } else { + meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint))); + } glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); @@ -488,8 +486,8 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g } bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, - bool pickAgainstTriangles, bool allowBackface) { + const glm::vec3& viewFrustumPos, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; // if we aren't active, we can't pick yet... @@ -498,7 +496,12 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co } // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); + glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos); + glm::mat4 transRot = createMatFromQuatAndPos(rotation, _translation); + glm::mat4 modelToWorldMatrix = transRot; + if (!_snapModelToRegistrationPoint) { + modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset()); + } glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated @@ -531,8 +534,12 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co calculateTriangleSets(hfmModel); } - glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; + glm::mat4 meshToWorldMatrix = transRot; + if (_snapModelToRegistrationPoint || _forceOffset) { + meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset)); + } else { + meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint))); + } glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); @@ -987,6 +994,24 @@ void Model::setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePoi } } +void Model::setBillboardMode(BillboardMode billboardMode, const render::ScenePointer& scene) { + if (_billboardMode != billboardMode) { + _billboardMode = billboardMode; + if (!scene) { + _needsFixupInScene = true; + return; + } + + render::Transaction transaction; + for (auto item : _modelMeshRenderItemIDs) { + transaction.updateItem(item, [billboardMode](ModelMeshPartPayload& data) { + data.setBillboardMode(billboardMode); + }); + } + scene->enqueueTransaction(transaction); + } +} + void Model::setCullWithParent(bool cullWithParent, const render::ScenePointer& scene) { if (_cullWithParent != cullWithParent) { _cullWithParent = cullWithParent; @@ -1017,9 +1042,8 @@ void Model::setRenderWithZones(const QVector& renderWithZones, const rend } render::Transaction transaction; - auto renderItemsKey = _renderItemKeyGlobalFlags; for (auto item : _modelMeshRenderItemIDs) { - transaction.updateItem(item, [renderWithZones, renderItemsKey](ModelMeshPartPayload& data) { + transaction.updateItem(item, [renderWithZones](ModelMeshPartPayload& data) { data.setRenderWithZones(renderWithZones); }); } @@ -1182,17 +1206,8 @@ glm::vec3 Model::getNaturalDimensions() const { } Extents Model::getMeshExtents() const { - if (!isLoaded()) { - return Extents(); - } - const Extents& extents = getHFMModel().meshExtents; - - // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which - // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(getHFMModel().offset * glm::vec4(extents.minimum, 1.0f)); - glm::vec3 maximum = glm::vec3(getHFMModel().offset * glm::vec4(extents.maximum, 1.0f)); - Extents scaledExtents = { minimum * _scale, maximum * _scale }; - return scaledExtents; + Extents extents = getUnscaledMeshExtents(); + return { extents.minimum * _scale, extents.maximum * _scale }; } Extents Model::getUnscaledMeshExtents() const { @@ -1417,6 +1432,15 @@ void Model::snapToRegistrationPoint() { _snappedToRegistrationPoint = true; } +glm::vec3 Model::getOriginalOffset() const { + Extents modelMeshExtents = getUnscaledMeshExtents(); + glm::vec3 dimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum); + glm::vec3 offset = modelMeshExtents.minimum + (0.5f * dimensions); + glm::mat4 transform = glm::scale(_scale) * glm::translate(offset); + return transform[3]; +} + + void Model::setUseDualQuaternionSkinning(bool value) { _useDualQuaternionSkinning = value; } @@ -1437,7 +1461,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) { snapToRegistrationPoint(); } // update the world space transforms for all joints - glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 parentTransform = glm::scale(_scale) * ((_snapModelToRegistrationPoint || _forceOffset) ? + glm::translate(_offset) : glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint))); updateRig(deltaTime, parentTransform); } } @@ -1514,7 +1539,7 @@ AABox Model::getRenderableMeshBound() const { // Build a bound using the last known bound from all the renderItems. AABox totalBound; for (auto& renderItem : _modelMeshRenderItems) { - totalBound += renderItem->getBound(); + totalBound += renderItem->getBound(nullptr); } return totalBound; } @@ -1545,10 +1570,6 @@ void Model::createRenderItemSet() { transform.setTranslation(_translation); transform.setRotation(_rotation); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); @@ -1561,7 +1582,7 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset, _created); + _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, _created); auto material = getGeometry()->getShapeMaterial(shapeID); _modelMeshMaterialNames.push_back(material ? material->getName() : ""); _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 1e7ab55d5a..930f942260 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -38,6 +38,7 @@ #include "TextureCache.h" #include "Rig.h" #include "PrimitiveMode.h" +#include "BillboardMode.h" // Use dual quaternion skinning! // Must match define in Skinning.slh @@ -121,6 +122,9 @@ public: void setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene = nullptr); PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } + void setBillboardMode(BillboardMode billboardMode, const render::ScenePointer& scene = nullptr); + BillboardMode getBillboardMode() const { return _billboardMode; } + void setCullWithParent(bool value, const render::ScenePointer& scene = nullptr); void setRenderWithZones(const QVector& renderWithZones, const render::ScenePointer& scene = nullptr); @@ -167,6 +171,7 @@ public: void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } + bool getSnappedToRegistrationPoint() { return _snappedToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void updateClusterMatrices(); @@ -194,15 +199,16 @@ public: void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); - bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, + bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); bool findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + const glm::vec3& viewFrustumPos, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } + glm::vec3 getOriginalOffset() const; void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale = false); @@ -348,6 +354,7 @@ public: virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; void scaleToFit(); + void snapToRegistrationPoint(); bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; } void setUseDualQuaternionSkinning(bool value); @@ -409,14 +416,14 @@ protected: bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point - glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to + glm::vec3 _registrationPoint { glm::vec3(0.5f) }; /// the point in model space our center is snapped to + bool _forceOffset { false }; std::vector _meshStates; virtual void initJointStates(); void setScaleInternal(const glm::vec3& scale); - void snapToRegistrationPoint(); virtual void updateRig(float deltaTime, glm::mat4 parentTransform); @@ -447,6 +454,7 @@ protected: virtual void createRenderItemSet(); PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; + BillboardMode _billboardMode { BillboardMode::NONE }; bool _useDualQuaternionSkinning { false }; // debug rendering support diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 264ff409a6..76d8374fb7 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -43,18 +43,18 @@ float TextRenderer3D::getFontSize() const { void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, const QString& str, const glm::vec4& color, bool unlit, bool forward) { if (_font) { - _font->drawString(batch, _drawInfo, str, color, glm::vec3(0.0f), 0, TextEffect::NO_EFFECT, { x, y }, bounds, 1.0f, unlit, forward); + _font->drawString(batch, _drawInfo, str, color, glm::vec3(0.0f), 0, TextEffect::NO_EFFECT, TextAlignment::LEFT, { x, y }, bounds, 1.0f, unlit, forward); } } void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale, const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor, - float effectThickness, TextEffect effect, bool unlit, bool forward) { + float effectThickness, TextEffect effect, TextAlignment alignment, bool unlit, bool forward) { if (font != _family) { _family = font; _font = Font::load(_family); } if (_font) { - _font->drawString(batch, _drawInfo, str, color, effectColor, effectThickness, effect, { x, y }, bounds, scale, unlit, forward); + _font->drawString(batch, _drawInfo, str, color, effectColor, effectThickness, effect, alignment, { x, y }, bounds, scale, unlit, forward); } } \ No newline at end of file diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 3a88ed555c..edccf1429c 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -17,6 +17,7 @@ #include "text/Font.h" #include "TextEffect.h" +#include "TextAlignment.h" #include "FontFamilies.h" class TextRenderer3D { @@ -30,7 +31,7 @@ public: const QString& str, const glm::vec4& color, bool unlit, bool forward); void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale, const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor, - float effectThickness, TextEffect effect, bool unlit, bool forward); + float effectThickness, TextEffect effect, TextAlignment alignment, bool unlit, bool forward); private: TextRenderer3D(const char* family); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index a30bbad0e5..7147c10872 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -65,7 +65,6 @@ struct QuadBuilder { vertices[3] = TextureVertex(min + size, texMin + glm::vec2(texSize.x, 0.0f), bounds); } - }; Font::Pointer Font::load(QIODevice& fontFile) { @@ -276,7 +275,7 @@ void Font::setupGPU() { for (auto& key : keys) { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL); state->setBlendFunction(std::get<0>(key), gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -303,7 +302,18 @@ void Font::setupGPU() { } } -void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows) { +inline QuadBuilder adjustedQuadBuilderForAlignmentMode(const Glyph& glyph, glm::vec2 advance, float scale, float enlargeForShadows, + TextAlignment alignment, float rightSpacing) { + if (alignment == TextAlignment::RIGHT) { + advance.x += rightSpacing; + } else if (alignment == TextAlignment::CENTER) { + advance.x += 0.5f * rightSpacing; + } + return QuadBuilder(glyph, advance, scale, enlargeForShadows); +} + +void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows, + TextAlignment alignment) { drawInfo.verticesBuffer = std::make_shared(); drawInfo.indicesBuffer = std::make_shared(); drawInfo.indexCount = 0; @@ -314,15 +324,17 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm drawInfo.origin = origin; float enlargedBoundsX = bounds.x - 0.5f * DOUBLE_MAX_OFFSET_PIXELS * float(enlargeForShadows); + float rightEdge = origin.x + enlargedBoundsX; // Top left of text glm::vec2 advance = origin; + std::vector> glyphsAndCorners; foreach(const QString& token, tokenizeForWrapping(str)) { bool isNewLine = (token == QString('\n')); bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + enlargedBoundsX)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > rightEdge)) { // We are out of the x bound, force new line forceNewLine = true; } @@ -347,38 +359,8 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm if (!isNewLine) { for (auto c : token) { auto glyph = _glyphs[c]; - quint16 verticesOffset = numVertices; - QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent), scale, enlargeForShadows); - drawInfo.verticesBuffer->append(qd); - numVertices += VERTICES_PER_QUAD; - - // Sam's recommended triangle slices - // Triangle tri1 = { v0, v1, v3 }; - // Triangle tri2 = { v1, v2, v3 }; - // NOTE: Random guy on the internet's recommended triangle slices - // Triangle tri1 = { v0, v1, v2 }; - // Triangle tri2 = { v2, v3, v0 }; - - // The problem here being that the 4 vertices are { ll, lr, ul, ur }, a Z pattern - // Additionally, you want to ensure that the shared side vertices are used sequentially - // to improve cache locality - // - // 2 -- 3 - // | | - // | | - // 0 -- 1 - // - // { 0, 1, 2 } -> { 2, 1, 3 } - quint16 indices[NUMBER_OF_INDICES_PER_QUAD]; - indices[0] = verticesOffset + 0; - indices[1] = verticesOffset + 1; - indices[2] = verticesOffset + 2; - indices[3] = verticesOffset + 2; - indices[4] = verticesOffset + 1; - indices[5] = verticesOffset + 3; - drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); - drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD; + glyphsAndCorners.emplace_back(glyph, advance - glm::vec2(0.0f, _ascent)); // Advance by glyph size advance.x += glyph.d; @@ -388,10 +370,71 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm advance.x += _spaceWidth; } } + + std::vector quadBuilders; + quadBuilders.reserve(glyphsAndCorners.size()); + { + int i = glyphsAndCorners.size() - 1; + while (i >= 0) { + auto nextGlyphAndCorner = glyphsAndCorners[i]; + float rightSpacing = rightEdge - (nextGlyphAndCorner.second.x + nextGlyphAndCorner.first.d); + quadBuilders.push_back(adjustedQuadBuilderForAlignmentMode(nextGlyphAndCorner.first, nextGlyphAndCorner.second, scale, enlargeForShadows, + alignment, rightSpacing)); + i--; + while (i >= 0) { + auto prevGlyphAndCorner = glyphsAndCorners[i]; + // We're to the right of the last character we checked, which means we're on a previous line, so we need to + // recalculate the spacing, so we exit this loop + if (prevGlyphAndCorner.second.x >= nextGlyphAndCorner.second.x) { + break; + } + + quadBuilders.push_back(adjustedQuadBuilderForAlignmentMode(prevGlyphAndCorner.first, prevGlyphAndCorner.second, scale, enlargeForShadows, + alignment, rightSpacing)); + + nextGlyphAndCorner = prevGlyphAndCorner; + i--; + } + } + } + + // The quadBuilders is backwards now because we looped over the glyphs backwards to adjust their alignment + for (int i = quadBuilders.size() - 1; i >= 0; i--) { + quint16 verticesOffset = numVertices; + drawInfo.verticesBuffer->append(quadBuilders[i]); + numVertices += VERTICES_PER_QUAD; + + // Sam's recommended triangle slices + // Triangle tri1 = { v0, v1, v3 }; + // Triangle tri2 = { v1, v2, v3 }; + // NOTE: Random guy on the internet's recommended triangle slices + // Triangle tri1 = { v0, v1, v2 }; + // Triangle tri2 = { v2, v3, v0 }; + + // The problem here being that the 4 vertices are { ll, lr, ul, ur }, a Z pattern + // Additionally, you want to ensure that the shared side vertices are used sequentially + // to improve cache locality + // + // 2 -- 3 + // | | + // | | + // 0 -- 1 + // + // { 0, 1, 2 } -> { 2, 1, 3 } + quint16 indices[NUMBER_OF_INDICES_PER_QUAD]; + indices[0] = verticesOffset + 0; + indices[1] = verticesOffset + 1; + indices[2] = verticesOffset + 2; + indices[3] = verticesOffset + 2; + indices[4] = verticesOffset + 1; + indices[5] = verticesOffset + 3; + drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); + drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD; + } } void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - const glm::vec3& effectColor, float effectThickness, TextEffect effect, + const glm::vec3& effectColor, float effectThickness, TextEffect effect, TextAlignment alignment, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool unlit, bool forward) { if (!_loaded || str == "") { return; @@ -401,11 +444,12 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString const int SHADOW_EFFECT = (int)TextEffect::SHADOW_EFFECT; // If we're switching to or from shadow effect mode, we need to rebuild the vertices - if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin || + if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin || alignment != _alignment || (drawInfo.params.effect != textEffect && (textEffect == SHADOW_EFFECT || drawInfo.params.effect == SHADOW_EFFECT)) || (textEffect == SHADOW_EFFECT && scale != _scale)) { _scale = scale; - buildVertices(drawInfo, str, origin, bounds, scale, textEffect == SHADOW_EFFECT); + _alignment = alignment; + buildVertices(drawInfo, str, origin, bounds, scale, textEffect == SHADOW_EFFECT, alignment); } setupGPU(); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index c75f0f746f..322e96439e 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -14,6 +14,7 @@ #include "Glyph.h" #include "TextEffect.h" +#include "TextAlignment.h" #include #include @@ -58,7 +59,7 @@ public: // Render string to batch void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - const glm::vec3& effectColor, float effectThickness, TextEffect effect, + const glm::vec3& effectColor, float effectThickness, TextEffect effect, TextAlignment alignment, const glm::vec2& origin, const glm::vec2& bound, float scale, bool unlit, bool forward); static Pointer load(const QString& family); @@ -76,7 +77,8 @@ private: glm::vec2 computeTokenExtent(const QString& str) const; const Glyph& getGlyph(const QChar& c) const; - void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows); + void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows, + TextAlignment alignment); void setupGPU(); @@ -96,6 +98,7 @@ private: float _spaceWidth { 0.0f }; float _scale { 0.0f }; + TextAlignment _alignment; bool _loaded { true }; diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 164d8e9f21..68bb467f66 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -83,7 +83,7 @@ void FetchNonspatialItems::run(const RenderContextPointer& renderContext, const for (auto& id : items) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && item.passesZoneOcclusionTest(CullTest::_containingZones)) { - outItems.emplace_back(ItemBound(id, item.getBound())); + outItems.emplace_back(ItemBound(id, item.getBound(renderContext->args))); } } } @@ -188,10 +188,10 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.insideItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -203,10 +203,10 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.insideSubcellItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -218,10 +218,10 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.partialItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -233,10 +233,10 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.partialSubcellItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -250,10 +250,10 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.insideItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -265,11 +265,11 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.insideSubcellItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -282,11 +282,11 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.partialItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); if (test.frustumTest(itemBound.bound)) { outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } @@ -299,11 +299,11 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, for (auto id : inSelection.partialSubcellItems) { auto& item = scene->getItem(id); if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) { - ItemBound itemBound(id, item.getBound()); + ItemBound itemBound(id, item.getBound(args)); if (test.frustumTest(itemBound.bound) && test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); if (item.getKey().isMetaCullGroup()) { - item.fetchMetaSubItemBounds(outItems, (*scene)); + item.fetchMetaSubItemBounds(outItems, (*scene), args); } } } diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 493ed01c3f..44d54c3c28 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -194,7 +194,7 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite render::ItemBounds itemBounds; for (const auto& itemID : itemIDs) { auto& item = scene->getItem(itemID); - auto itemBound = item.getBound(); + auto itemBound = item.getBound(args); if (!itemBound.isInvalid()) { itemBounds.emplace_back(itemID, itemBound); } diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index 20d29f3e5d..b269f44b41 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -139,7 +139,7 @@ void IDsToBounds::run(const RenderContextPointer& renderContext, const ItemIDs& for (auto id : inItems) { auto& item = scene->getItem(id); if (item.exist()) { - outItems.emplace_back(ItemBound{ id, item.getBound() }); + outItems.emplace_back(ItemBound{ id, item.getBound(renderContext->args) }); } } } else { diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index f21df58368..369f227566 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -97,7 +97,7 @@ const ShapeKey Item::getShapeKey() const { return shapeKey; } -uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const { +uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene, RenderArgs* args) const { ItemIDs subItems; auto numSubs = fetchMetaSubItems(subItems); @@ -107,7 +107,7 @@ uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) c if (scene.isAllocatedID(id)) { auto& item = scene.getItem(id); if (item.exist()) { - subItemBounds.emplace_back(id, item.getBound()); + subItemBounds.emplace_back(id, item.getBound(args)); } else { numSubs--; } @@ -133,11 +133,11 @@ namespace render { return payload->getShapeKey(); } - template <> const Item::Bound payloadGetBound(const PayloadProxyInterface::Pointer& payload) { + template <> const Item::Bound payloadGetBound(const PayloadProxyInterface::Pointer& payload, RenderArgs* args) { if (!payload) { return render::Item::Bound(); } - return payload->getBound(); + return payload->getBound(args); } template <> void payloadRender(const PayloadProxyInterface::Pointer& payload, RenderArgs* args) { diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 8a67108d0e..5952be8a84 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -431,7 +431,7 @@ public: class PayloadInterface { public: virtual const ItemKey getKey() const = 0; - virtual const Bound getBound() const = 0; + virtual const Bound getBound(RenderArgs* args) const = 0; virtual void render(RenderArgs* args) = 0; virtual const ShapeKey getShapeKey() const = 0; @@ -476,7 +476,7 @@ public: // Payload Interface // Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace()) - const Bound getBound() const { return _payload->getBound(); } + const Bound getBound(RenderArgs* args) const { return _payload->getBound(args); } // Get the layer where the item belongs, simply reflecting the key. int getLayer() const { return _key.getLayer(); } @@ -489,7 +489,7 @@ public: // Meta Type Interface uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); } - uint32_t fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const; + uint32_t fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene, RenderArgs* args) const; bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const { return _payload->passesZoneOcclusionTest(containingZones); } @@ -524,13 +524,13 @@ public: inline QDebug operator<<(QDebug debug, const Item& item) { - debug << "[Item: _key:" << item.getKey() << ", bounds:" << item.getBound() << "]"; + debug << "[Item: _key:" << item.getKey() << "]"; return debug; } // Item shared interface supported by the payload template const ItemKey payloadGetKey(const std::shared_ptr& payloadData) { return ItemKey(); } -template const Item::Bound payloadGetBound(const std::shared_ptr& payloadData) { return Item::Bound(); } +template const Item::Bound payloadGetBound(const std::shared_ptr& payloadData, RenderArgs* args) { return Item::Bound(); } template void payloadRender(const std::shared_ptr& payloadData, RenderArgs* args) { } // Shape type interface @@ -561,7 +561,7 @@ public: // Payload general interface virtual const ItemKey getKey() const override { return payloadGetKey(_data); } - virtual const Item::Bound getBound() const override { return payloadGetBound(_data); } + virtual const Item::Bound getBound(RenderArgs* args) const override { return payloadGetBound(_data, args); } virtual void render(RenderArgs* args) override { payloadRender(_data, args); } @@ -607,9 +607,9 @@ template <> const ItemKey payloadGetKey(const FooPointer& foo) { foo->makeMyKey(); return foo->_myownKey; } -template <> const Item::Bound payloadGetBound(const FooPointer& foo) { +template <> const Item::Bound payloadGetBound(const FooPointer& foo, RenderArgs* args) { // evaluate Foo's own bound - return foo->evaluateMyBound(); + return foo->evaluateMyBound(args); } // In this example, do not specialize the payloadRender call which means the compiler will use the default version which does nothing @@ -624,7 +624,7 @@ public: virtual ItemKey getKey() = 0; virtual ShapeKey getShapeKey() = 0; - virtual Item::Bound getBound() = 0; + virtual Item::Bound getBound(RenderArgs* args) = 0; virtual void render(RenderArgs* args) = 0; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const = 0; virtual bool passesZoneOcclusionTest(const std::unordered_set& containingZones) const = 0; @@ -635,7 +635,7 @@ public: }; template <> const ItemKey payloadGetKey(const PayloadProxyInterface::Pointer& payload); -template <> const Item::Bound payloadGetBound(const PayloadProxyInterface::Pointer& payload); +template <> const Item::Bound payloadGetBound(const PayloadProxyInterface::Pointer& payload, RenderArgs* args); template <> void payloadRender(const PayloadProxyInterface::Pointer& payload, RenderArgs* args); template <> uint32_t metaFetchMetaSubItems(const PayloadProxyInterface::Pointer& payload, ItemIDs& subItems); template <> const ShapeKey shapeGetShapeKey(const PayloadProxyInterface::Pointer& payload); diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index bea111edb5..5500183196 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -305,7 +305,7 @@ void Scene::resetItems(const Transaction::Resets& transactions) { // Update the item's container assert((oldKey.isSpatial() == newKey.isSpatial()) || oldKey._flags.none()); if (newKey.isSpatial()) { - auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), itemId, newKey); + auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(nullptr), itemId, newKey); item.resetCell(newCell, newKey.isSmall()); } else { _masterNonspatialSet.insert(itemId); @@ -361,14 +361,14 @@ void Scene::updateItems(const Transaction::Updates& transactions) { // Update the item's container if (oldKey.isSpatial() == newKey.isSpatial()) { if (newKey.isSpatial()) { - auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey); + auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(nullptr), updateID, newKey); item.resetCell(newCell, newKey.isSmall()); } } else { if (newKey.isSpatial()) { _masterNonspatialSet.erase(updateID); - auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey); + auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(nullptr), updateID, newKey); item.resetCell(newCell, newKey.isSmall()); } else { _masterSpatialTree.removeItem(oldCell, oldKey, updateID); diff --git a/libraries/render/src/render/SortTask.cpp b/libraries/render/src/render/SortTask.cpp index 5b4061a10f..266c5be41d 100644 --- a/libraries/render/src/render/SortTask.cpp +++ b/libraries/render/src/render/SortTask.cpp @@ -60,7 +60,7 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron for (auto itemDetails : inItems) { auto item = scene->getItem(itemDetails.id); - auto bound = itemDetails.bound; // item.getBound(); + auto bound = itemDetails.bound; // item.getBound(args); float distanceSquared = args->getViewFrustum().distanceToCameraSquared(bound.calcCenter()); itemBoundSorts.emplace_back(ItemBoundSort(distanceSquared, distanceSquared, distanceSquared, itemDetails.id, bound)); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f42178b023..e553672b02 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -87,7 +87,7 @@ #include "SettingHandle.h" #include #include - +#include const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { "com.highfidelity.experimental.enableExtendedJSExceptions" @@ -429,13 +429,17 @@ void ScriptEngine::runInThread() { // The thread interface cannot live on itself, and we want to move this into the thread, so // the thread cannot have this as a parent. QThread* workerThread = new QThread(); - workerThread->setObjectName(QString("js:") + getFilename().replace("about:","")); + QString name = QString("js:") + getFilename().replace("about:",""); + workerThread->setObjectName(name); moveToThread(workerThread); // NOTE: If you connect any essential signals for proper shutdown or cleanup of // the script engine, make sure to add code to "reconnect" them to the // disconnectNonEssentialSignals() method - connect(workerThread, &QThread::started, this, &ScriptEngine::run); + connect(workerThread, &QThread::started, this, [this, name] { + setThreadName(name.toStdString()); + run(); + }); connect(this, &QObject::destroyed, workerThread, &QThread::quit); connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 4a79f6b487..a25142b2ad 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -20,6 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +const float DEFAULT_AVATAR_HIPS_HEIGHT = 1.01327407f; // meters const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f; const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; diff --git a/libraries/shared/src/BillboardMode.cpp b/libraries/shared/src/BillboardMode.cpp index 4b6af5db33..6cc6dca976 100644 --- a/libraries/shared/src/BillboardMode.cpp +++ b/libraries/shared/src/BillboardMode.cpp @@ -15,6 +15,23 @@ const char* billboardModeNames[] = { }; static const size_t BILLBOARD_MODE_NAMES = (sizeof(billboardModeNames) / sizeof(billboardModeNames[0])); +std::function BillboardModeHelpers::_getBillboardRotationOperator = + [](const glm::vec3&, const glm::quat& rotation, BillboardMode, const glm::vec3&, bool) { return rotation; }; +std::function BillboardModeHelpers::_getPrimaryViewFrustumPositionOperator = []() { return glm::vec3(0.0f); }; + +void BillboardModeHelpers::setBillboardRotationOperator(std::function getBillboardRotationOperator) { + _getBillboardRotationOperator = getBillboardRotationOperator; +} + +glm::quat BillboardModeHelpers::getBillboardRotation(const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, + const glm::vec3& frustumPos, bool rotate90x) { + return _getBillboardRotationOperator(position, rotation, billboardMode, frustumPos, rotate90x); +} + +void BillboardModeHelpers::setPrimaryViewFrustumPositionOperator(std::function getPrimaryViewFrustumPositionOperator) { + _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; +} QString BillboardModeHelpers::getNameForBillboardMode(BillboardMode mode) { if (((int)mode <= 0) || ((int)mode >= (int)BILLBOARD_MODE_NAMES)) { @@ -22,4 +39,4 @@ QString BillboardModeHelpers::getNameForBillboardMode(BillboardMode mode) { } return billboardModeNames[(int)mode]; -} \ No newline at end of file +} diff --git a/libraries/shared/src/BillboardMode.h b/libraries/shared/src/BillboardMode.h index aa00ce34aa..4d123c0603 100644 --- a/libraries/shared/src/BillboardMode.h +++ b/libraries/shared/src/BillboardMode.h @@ -9,8 +9,13 @@ #ifndef hifi_BillboardMode_h #define hifi_BillboardMode_h +#include + #include "QString" +#include "glm/glm.hpp" +#include "glm/gtc/quaternion.hpp" + /**jsdoc *

How an entity is billboarded.

*
ValueNameDescription
Create a Web entity displaying at 1920 x 1080 resolution.
@@ -37,7 +42,17 @@ enum class BillboardMode { class BillboardModeHelpers { public: static QString getNameForBillboardMode(BillboardMode mode); + + static void setBillboardRotationOperator(std::function getBillboardRotationOperator); + static glm::quat getBillboardRotation(const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, + const glm::vec3& frustumPos, bool rotate90x = false); + static void setPrimaryViewFrustumPositionOperator(std::function getPrimaryViewFrustumPositionOperator); + static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); } + +private: + static std::function _getBillboardRotationOperator; + static std::function _getPrimaryViewFrustumPositionOperator; }; #endif // hifi_BillboardMode_h - diff --git a/libraries/shared/src/GenericThread.cpp b/libraries/shared/src/GenericThread.cpp index e35c74e68a..4e9dba8893 100644 --- a/libraries/shared/src/GenericThread.cpp +++ b/libraries/shared/src/GenericThread.cpp @@ -14,6 +14,8 @@ #include #include +#include "ThreadHelpers.h" + GenericThread::GenericThread() : _stopThread(false), _isThreaded(false) // assume non-threaded, must call initialize() @@ -36,8 +38,11 @@ void GenericThread::initialize(bool isThreaded, QThread::Priority priority) { // match the thread name to our object name _thread->setObjectName(objectName()); - connect(_thread, &QThread::started, this, &GenericThread::started); - connect(_thread, &QThread::started, this, &GenericThread::threadRoutine); + connect(_thread, &QThread::started, this, [this] { + setThreadName("Generic thread " + objectName().toStdString()); + started(); + threadRoutine(); + }); connect(_thread, &QThread::finished, this, &GenericThread::finished); moveToThread(_thread); diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 72d53f0bcb..098e5e9a80 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -64,6 +64,8 @@ LogHandler::LogHandler() { _shouldOutputThreadID = true; } else if (option == "milliseconds") { _shouldDisplayMilliseconds = true; + } else if (option == "keep_repeats") { + _keepRepeats = true; } else if (option != "") { fprintf(stdout, "Unrecognized option in VIRCADIA_LOG_OPTIONS: '%s'\n", option.toUtf8().constData()); } @@ -202,7 +204,18 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont resetColor = colorReset(); } - fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor); + if (_keepRepeats || _previousMessage != message) { + if (_repeatCount > 0) { + fprintf(stdout, "[Previous message was repeated %i times]\n", _repeatCount); + } + + fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor); + _repeatCount = 0; + } else { + _repeatCount++; + } + + _previousMessage = message; #ifdef Q_OS_WIN // On windows, this will output log lines into the Visual Studio "output" tab OutputDebugStringA(qPrintable(logMessage)); diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index 7bd5c69c63..71df2a4189 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -67,6 +67,11 @@ private: bool _shouldOutputThreadID { false }; bool _shouldDisplayMilliseconds { false }; bool _useColor { false }; + bool _keepRepeats { false }; + + QString _previousMessage; + int _repeatCount { 0 }; + int _currentMessageID { 0 }; struct RepeatedMessageRecord { diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 04da35656e..ab5b3e380a 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -21,6 +21,7 @@ #include "SettingManager.h" #include "SharedLogging.h" #include "SharedUtil.h" +#include "ThreadHelpers.h" namespace Setting { // This should only run as a post-routine in the QCoreApplication destructor @@ -53,7 +54,10 @@ namespace Setting { thread->setObjectName("Settings Thread"); // Setup setting periodical save timer - QObject::connect(thread, &QThread::started, globalManager.data(), &Manager::startTimer); + QObject::connect(thread, &QThread::started, globalManager.data(), [globalManager] { + setThreadName("Settings Save Thread"); + globalManager->startTimer(); + }); QObject::connect(thread, &QThread::finished, globalManager.data(), &Manager::stopTimer); // Setup manager threading affinity diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index c1c1fd38d9..670b03611b 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -754,6 +754,17 @@ const Transform SpatiallyNestable::getTransform(bool& success, int depth) const return result; } +const Transform SpatiallyNestable::getTransformWithOnlyLocalRotation(bool& success, int depth) const { + Transform result; + // return a world-space transform for this object's location + Transform parentTransform = getParentTransform(success, depth); + _transformLock.withReadLock([&] { + Transform::mult(result, parentTransform, _transform); + result.setRotation(_transform.getRotation()); + }); + return result; +} + const Transform SpatiallyNestable::getTransform() const { bool success; Transform result = getTransform(success); diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 01e3b045ad..29f23afdfb 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -82,6 +82,7 @@ public: // world frame virtual const Transform getTransform(bool& success, int depth = 0) const; + virtual const Transform getTransformWithOnlyLocalRotation(bool& success, int depth = 0) const; virtual const Transform getTransform() const; virtual void setTransform(const Transform& transform, bool& success); virtual bool setTransform(const Transform& transform); diff --git a/libraries/shared/src/TextAlignment.cpp b/libraries/shared/src/TextAlignment.cpp new file mode 100644 index 0000000000..533234f1da --- /dev/null +++ b/libraries/shared/src/TextAlignment.cpp @@ -0,0 +1,25 @@ +// +// Created by HifiExperiments on 2/9/21 +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TextAlignment.h" + +const char* textAlignmentNames[] = { + "left", + "center", + "right" +}; + +static const size_t TEXT_ALIGNMENT_NAMES = (sizeof(textAlignmentNames) / sizeof(textAlignmentNames[0])); + +QString TextAlignmentHelpers::getNameForTextAlignment(TextAlignment alignment) { + if (((int)alignment <= 0) || ((int)alignment >= (int)TEXT_ALIGNMENT_NAMES)) { + alignment = (TextAlignment)0; + } + + return textAlignmentNames[(int)alignment]; +} \ No newline at end of file diff --git a/libraries/shared/src/TextAlignment.h b/libraries/shared/src/TextAlignment.h new file mode 100644 index 0000000000..829b07dc36 --- /dev/null +++ b/libraries/shared/src/TextAlignment.h @@ -0,0 +1,40 @@ +// +// Created by HifiExperiments on 2/9/21 +// Copyright 2021 Vircadia contributors. +// +// 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_TextAlignment_h +#define hifi_TextAlignment_h + +#include "QString" + +/**jsdoc + *

A {@link Entities.EntityProperties-Text|Text} entity may use one of the following alignments:

+ *
+ * + * + * + * + * + * + * + * + *
ValueDescription
"left"Text is aligned to the left side.
"center"Text is centered.
"right"Text is aligned to the right side.
+ * @typedef {string} Entities.TextAlignment + */ + +enum class TextAlignment { + LEFT = 0, + CENTER, + RIGHT +}; + +class TextAlignmentHelpers { +public: + static QString getNameForTextAlignment(TextAlignment alignment); +}; + +#endif // hifi_TextAlignment_h \ No newline at end of file diff --git a/libraries/shared/src/TextEffect.h b/libraries/shared/src/TextEffect.h index 91bd5ec60c..3e205f72fe 100644 --- a/libraries/shared/src/TextEffect.h +++ b/libraries/shared/src/TextEffect.h @@ -20,7 +20,7 @@ * * "none"No effect. * "outline"An outline effect. - * "outlineFill"An outline effect, with fill. + * "outline fill"An outline effect, with fill. * "shadow"A shadow effect. * * diff --git a/libraries/shared/src/ThreadHelpers.h b/libraries/shared/src/ThreadHelpers.h index d236344dc5..42de117e67 100644 --- a/libraries/shared/src/ThreadHelpers.h +++ b/libraries/shared/src/ThreadHelpers.h @@ -32,6 +32,8 @@ void withLock(QMutex& lock, F function) { function(); } +void setThreadName(const std::string& name); + void moveToNewNamedThread(QObject* object, const QString& name, std::function preStartCallback, std::function startCallback, diff --git a/libraries/ui/src/DockWidget.cpp b/libraries/ui/src/DockWidget.cpp index 13c4d9a548..f899def2c6 100644 --- a/libraries/ui/src/DockWidget.cpp +++ b/libraries/ui/src/DockWidget.cpp @@ -26,14 +26,15 @@ static void quickViewDeleter(QQuickView* quickView) { } DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent) { - auto offscreenUi = DependencyManager::get(); - auto qmlEngine = offscreenUi->getSurfaceContext()->engine(); - _quickView = std::shared_ptr(new QQuickView(qmlEngine, nullptr), quickViewDeleter); - _quickView->setFormat(getDefaultOpenGLSurfaceFormat()); - QWidget* widget = QWidget::createWindowContainer(_quickView.get()); - setWidget(widget); - QWidget* headerWidget = new QWidget(); - setTitleBarWidget(headerWidget); + if (auto offscreenUI = DependencyManager::get()) { + auto qmlEngine = offscreenUI->getSurfaceContext()->engine(); + _quickView = std::shared_ptr(new QQuickView(qmlEngine, nullptr), quickViewDeleter); + _quickView->setFormat(getDefaultOpenGLSurfaceFormat()); + QWidget* widget = QWidget::createWindowContainer(_quickView.get()); + setWidget(widget); + QWidget* headerWidget = new QWidget(); + setTitleBarWidget(headerWidget); + } } void DockWidget::setSource(const QUrl& url) { diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index 478401c6f8..c14ff6bf64 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -65,17 +65,18 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue } infoVersion.set(version); } - auto offscreenUi = DependencyManager::get(); - QString infoViewName(NAME + "_" + path); - offscreenUi->show(QML, NAME + "_" + path, [=](QQmlContext* context, QObject* newObject){ - QQuickItem* item = dynamic_cast(newObject); - item->setWidth(1024); - item->setHeight(720); - InfoView* newInfoView = newObject->findChild(); - Q_ASSERT(newInfoView); - newInfoView->parent()->setObjectName(infoViewName); - newInfoView->setUrl(url); - }); + if (auto offscreenUI = DependencyManager::get()) { + QString infoViewName(NAME + "_" + path); + offscreenUI->show(QML, NAME + "_" + path, [=] (QQmlContext* context, QObject* newObject) { + QQuickItem* item = dynamic_cast(newObject); + item->setWidth(1024); + item->setHeight(720); + InfoView* newInfoView = newObject->findChild(); + Q_ASSERT(newInfoView); + newInfoView->parent()->setObjectName(infoViewName); + newInfoView->setUrl(url); + }); + } } QUrl InfoView::url() { diff --git a/libraries/ui/src/OffscreenQmlElement.h b/libraries/ui/src/OffscreenQmlElement.h index 69009533c6..ac1bcb0866 100644 --- a/libraries/ui/src/OffscreenQmlElement.h +++ b/libraries/ui/src/OffscreenQmlElement.h @@ -53,25 +53,30 @@ private: } \ \ void x::show(std::function f) { \ - auto offscreenUi = DependencyManager::get(); \ + auto offscreenUI = DependencyManager::get(); \ if (!registered) { \ x::registerType(); \ } \ - offscreenUi->show(QML, NAME, f); \ + if (offscreenUI) { \ + offscreenUI->show(QML, NAME, f); \ + } \ } \ \ void x::hide() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->hide(NAME); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->hide(NAME); \ + } \ } \ \ void x::toggle(std::function f) { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->toggle(QML, NAME, f); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->toggle(QML, NAME, f); \ + } \ } \ void x::load(std::function f) { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->load(QML, f); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->load(QML, f); \ + } \ } #define HIFI_QML_DEF_LAMBDA(x, f) \ @@ -82,21 +87,25 @@ private: qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ } \ void x::show() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->show(QML, NAME, f); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->show(QML, NAME, f); \ + } \ } \ void x::hide() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->hide(NAME); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->hide(NAME); \ + } \ } \ \ void x::toggle() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->toggle(QML, NAME, f); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->toggle(QML, NAME, f); \ + } \ } \ void x::load() { \ - auto offscreenUi = DependencyManager::get(); \ - offscreenUi->load(QML, f); \ + if (auto offscreenUI = DependencyManager::get()) { \ + offscreenUI->load(QML, f); \ + } \ } #endif diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 34184057e0..26e77dcb5f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -189,10 +189,12 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { @@ -208,11 +210,14 @@ void OffscreenUi::toggle(const QUrl& url, const QString& name, std::functionfindChild(name); - if (item) { - return QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).read().toBool(); - } else { - return false; + auto rootItem = getRootItem(); + if (rootItem) { + QQuickItem* item = rootItem->findChild(name); + if (item) { + return QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).read().toBool(); + } } + return false; } class MessageBoxListener : public ModalDialogListener { @@ -280,12 +287,11 @@ QQuickItem* OffscreenUi::createMessageBox(Icon icon, const QString& title, const bool invokeResult; auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "messageBox", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); @@ -533,21 +539,21 @@ ModalDialogListener* OffscreenUi::customInputDialogAsync(const Icon icon, const } void OffscreenUi::togglePinned() { - bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned"); + bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "togglePinned"); if (!invokeResult) { qWarning() << "Failed to toggle window visibility"; } } void OffscreenUi::setPinned(bool pinned) { - bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); + bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); if (!invokeResult) { qWarning() << "Failed to set window visibility"; } } void OffscreenUi::setConstrainToolbarToCenterX(bool constrained) { - bool invokeResult = QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_ARG(QVariant, constrained)); + bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_ARG(QVariant, constrained)); if (!invokeResult) { qWarning() << "Failed to set toolbar constraint"; } @@ -575,17 +581,17 @@ QQuickItem* OffscreenUi::createInputDialog(const Icon icon, const QString& title TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); bool invokeResult; - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); emit tabletScriptingInterface->tabletNotification(); } + if (!invokeResult) { qWarning() << "Failed to create message box"; return nullptr; @@ -603,12 +609,11 @@ QQuickItem* OffscreenUi::createCustomInputDialog(const Icon icon, const QString& TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); bool invokeResult; - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, QVariant::fromValue(map))); @@ -718,7 +723,7 @@ QObject* OffscreenUi::getRootMenu() { } void OffscreenUi::unfocusWindows() { - bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows"); + bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "unfocusWindows"); Q_ASSERT(invokeResult); } @@ -752,12 +757,11 @@ QString OffscreenUi::fileDialog(const QVariantMap& properties) { bool invokeResult; auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); @@ -782,12 +786,11 @@ ModalDialogListener* OffscreenUi::fileDialogAsync(const QVariantMap& properties) bool invokeResult; auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); @@ -1003,12 +1006,11 @@ QString OffscreenUi::assetDialog(const QVariantMap& properties) { bool invokeResult; auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); @@ -1034,12 +1036,11 @@ ModalDialogListener *OffscreenUi::assetDialogAsync(const QVariantMap& properties bool invokeResult; auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - if (tablet->getToolbarMode()) { + if (tablet->getToolbarMode() && _desktop) { invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); + } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) { invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog", Q_RETURN_ARG(QVariant, buildDialogResult), Q_ARG(QVariant, QVariant::fromValue(properties))); diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp index fbd045fdb1..1219094afc 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -14,9 +14,6 @@ #include -#include "OffscreenUi.h" - - std::mutex QmlFragmentClass::_mutex; std::map QmlFragmentClass::_fragments; @@ -40,7 +37,6 @@ QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QSc } auto properties = parseArguments(context); - auto offscreenUi = DependencyManager::get(); QmlFragmentClass* retVal = new QmlFragmentClass(restricted, qml.toString()); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 282161497a..c7851d416f 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -14,7 +14,6 @@ #include #include -#include "OffscreenUi.h" static const char* const URL_PROPERTY = "source"; static const char* const SCRIPT_PROPERTY = "scriptUrl"; @@ -22,7 +21,6 @@ static const char* const SCRIPT_PROPERTY = "scriptUrl"; // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); - auto offscreenUi = DependencyManager::get(); QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 13a289a5fd..ae2292dc09 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -72,7 +72,6 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); - auto offscreenUi = DependencyManager::get(); QmlWindowClass* retVal = new QmlWindowClass(restricted); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { @@ -349,7 +348,6 @@ void QmlWindowClass::raise() { return; } - auto offscreenUi = DependencyManager::get(); if (_qmlWindow) { QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection); } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 040bb750d0..e80b11915a 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -321,8 +321,8 @@ void TabletScriptingInterface::processEvent(const QKeyEvent* event) { QObject* TabletScriptingInterface::getFlags() { Q_ASSERT(QThread::currentThread() == qApp->thread()); - auto offscreenUi = DependencyManager::get(); - return offscreenUi->getFlags(); + auto offscreenUI = DependencyManager::get(); + return offscreenUI ? offscreenUI->getFlags() : nullptr; } // @@ -364,8 +364,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _toolbarMode = toolbarMode; - auto offscreenUi = DependencyManager::get(); - if (toolbarMode) { #if !defined(DISABLE_QML) closeDialog(); @@ -388,13 +386,18 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) { loadHomeScreen(true); } - //check if running scripts window opened and save it for reopen in Tablet - if (offscreenUi->isVisible("RunningScripts")) { - offscreenUi->hide("RunningScripts"); - _showRunningScripts = true; + + auto offscreenUI = DependencyManager::get(); + if (offscreenUI) { + //check if running scripts window opened and save it for reopen in Tablet + if (offscreenUI->isVisible("RunningScripts")) { + offscreenUI->hide("RunningScripts"); + _showRunningScripts = true; + } + + offscreenUI->hideDesktopWindows(); } - offscreenUi->hideDesktopWindows(); // destroy desktop window if (_desktopWindow) { _desktopWindow->deleteLater(); @@ -577,9 +580,9 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { root = _desktopWindow->asQuickItem(); } - if (root) { - auto offscreenUi = DependencyManager::get(); - QObject* menu = offscreenUi->getRootMenu(); + auto offscreenUI = DependencyManager::get(); + if (root && offscreenUI) { + QObject* menu = offscreenUI->getRootMenu(); QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); _state = State::Menu; @@ -592,7 +595,7 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { } void TabletProxy::loadQMLOnTopImpl(const QVariant& path, bool localSafeContext) { - if (QThread::currentThread() != thread()) { + if (QThread::currentThread() != thread()) { qCWarning(uiLogging) << __FUNCTION__ << "may not be called directly by scripts"; return; } diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 0eea5230ca..ceac3d00e8 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -4,6 +4,7 @@ // // Created by Kunal Gosar on 2017-03-10. // Copyright 2017 High Fidelity, Inc. +// Copyright 2021 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -25,7 +26,7 @@ namespace { - bool isAuthableHighFidelityURL(const QUrl& url) { + bool isAuthableMetaverseURL(const QUrl& url) { auto metaverseServerURL = MetaverseAPI::getCurrentMetaverseServerURL(); static QStringList HF_HOSTS = { metaverseServerURL.toString() @@ -70,7 +71,7 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, } // check if this is a request to a highfidelity URL - bool isAuthable = isAuthableHighFidelityURL(info.requestUrl()); + bool isAuthable = isAuthableMetaverseURL(info.requestUrl()); auto accountManager = DependencyManager::get(); if (isAuthable) { // if we have an access token, add it to the right HTTP header for authorization @@ -82,13 +83,6 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, info.setHttpHeader(OAUTH_AUTHORIZATION_HEADER.toLocal8Bit(), bearerTokenString.toLocal8Bit()); } } - static const QString USER_AGENT = "User-Agent"; - const QString tokenStringMobile{ NetworkingConstants::MOBILE_USER_AGENT }; - const QString tokenStringMetaverse{ NetworkingConstants::METAVERSE_USER_AGENT }; - const QString tokenStringLimitedCommerce{ "Chrome/48.0 (VircadiaInterface limitedCommerce)" }; - - const QString tokenString = !isAuthable ? tokenStringMobile : (accountManager->getLimitedCommerce() ? tokenStringLimitedCommerce : tokenStringMetaverse); - info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); } void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index c5eb740325..b5c1f713e7 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "OpenVrHelpers.h" @@ -494,6 +495,7 @@ bool OpenVrDisplayPlugin::internalActivate() { _submitCanvas->doneCurrent(); }); } + connect(_submitThread.get(), &QThread::started, [] { setThreadName("OpenVR Submit Thread"); }); _submitCanvas->moveToThread(_submitThread.get()); } diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index ce7625eedb..eed2242602 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -203,14 +203,14 @@ void updateFromOpenVrKeyboardInput() { } void finishOpenVrKeyboardInput() { - auto offscreenUi = DependencyManager::get(); + auto offscreenUI = DependencyManager::get(); updateFromOpenVrKeyboardInput(); // Simulate an enter press on the top level window to trigger the action - if (0 == (_currentHints & Qt::ImhMultiLine)) { + if (0 == (_currentHints & Qt::ImhMultiLine) && offscreenUI) { auto keyPress = QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n")); auto keyRelease = QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers()); - qApp->sendEvent(offscreenUi->getWindow(), &keyPress); - qApp->sendEvent(offscreenUi->getWindow(), &keyRelease); + qApp->sendEvent(offscreenUI->getWindow(), &keyPress); + qApp->sendEvent(offscreenUI->getWindow(), &keyRelease); } } @@ -221,10 +221,8 @@ void enableOpenVrKeyboard(PluginContainer* container) { if (disableSteamVrKeyboard) { return; } - auto offscreenUi = DependencyManager::get(); _overlay = vr::VROverlay(); - auto menu = container->getPrimaryMenu(); auto action = menu->getActionForOption(MenuOption::Overlays); @@ -282,7 +280,9 @@ void handleOpenVrEvents() { case vr::VREvent_KeyboardClosed: _keyboardFocusObject = nullptr; _keyboardShown = false; - DependencyManager::get()->unfocusWindows(); + if (auto offscreenUI = DependencyManager::get()) { + offscreenUI->unfocusWindows(); + } break; default: diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 3629698e11..ac41502a38 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -403,6 +404,7 @@ bool ViveControllerManager::activate() { if (_viveProEye) { _viveProEyeReadThread = std::make_shared(); + connect(_viveProEyeReadThread.get(), &QThread::started, [] { setThreadName("ViveProEyeReadThread"); }); _viveProEyeReadThread->start(QThread::HighPriority); } } diff --git a/screenshare/package-lock.json b/screenshare/package-lock.json index 8195347159..4d0fea0805 100644 --- a/screenshare/package-lock.json +++ b/screenshare/package-lock.json @@ -71,27 +71,9 @@ } }, "@types/node": { - "version": "10.14.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.21.tgz", - "integrity": "sha512-nuFlRdBiqbF+PJIEVxm2jLFcQWN7q7iWEJGsBV4n7v1dbI9qXB8im2pMMKMCUZe092sQb5SQft2DHfuQGK5hqQ==", - "dev": true - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "12.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz", + "integrity": "sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==", "dev": true }, "ansi-styles": { @@ -102,12 +84,6 @@ "color-convert": "^1.9.0" } }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, "asar": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/asar/-/asar-2.0.1.tgz", @@ -123,45 +99,12 @@ "tmp-promise": "^1.0.5" } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, "author-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", "integrity": "sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=", "dev": true }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -174,15 +117,6 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "bluebird": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", @@ -215,6 +149,12 @@ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -259,28 +199,6 @@ } } }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, "chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", @@ -336,12 +254,6 @@ "mimic-response": "^1.0.0" } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -355,15 +267,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -392,44 +295,6 @@ "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "core-util-is": { @@ -464,24 +329,6 @@ "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", "dev": true }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -505,68 +352,29 @@ "mimic-response": "^1.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "defer-to-connect": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.2.tgz", "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==", "dev": true }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "electron": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/electron/-/electron-6.0.12.tgz", - "integrity": "sha512-70ODZa1RP6K0gE9IV9YLCXPSyhLjXksCuYSSPb3MljbfwfHo5uE6X0CGxzm+54YuPdE2e7EPnWZxOOsJYrS5iQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-7.2.4.tgz", + "integrity": "sha512-Z+R692uTzXgP8AHrabE+kkrMlQJ6pnAYoINenwj9QSqaD2YbO8IuXU9DMCcUY0+VpA91ee09wFZJNUKYPMnCKg==", "dev": true, "requires": { - "@types/node": "^10.12.18", - "electron-download": "^4.1.0", + "@electron/get": "^1.0.1", + "@types/node": "^12.0.12", "extract-zip": "^1.0.3" } }, - "electron-download": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", - "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", - "dev": true, - "requires": { - "debug": "^3.0.0", - "env-paths": "^1.0.0", - "fs-extra": "^4.0.1", - "minimist": "^1.2.0", - "nugget": "^2.0.1", - "path-exists": "^3.0.0", - "rc": "^1.2.1", - "semver": "^5.4.1", - "sumchecker": "^2.0.2" - } - }, "electron-notarize": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.1.1.tgz", @@ -713,12 +521,6 @@ "once": "^1.4.0" } }, - "env-paths": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", - "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", - "dev": true - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -728,22 +530,16 @@ "is-arrayish": "^0.2.1" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", "dev": true, "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" }, "dependencies": { "debug": { @@ -755,6 +551,21 @@ "ms": "2.0.0" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -763,54 +574,15 @@ } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { "pend": "~1.2.0" } }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, "flora-colossus": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.1.tgz", @@ -843,23 +615,6 @@ } } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "fs-extra": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", @@ -979,12 +734,6 @@ } } }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -994,15 +743,6 @@ "pump": "^3.0.0" } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -1042,22 +782,6 @@ "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, "hosted-git-info": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", @@ -1070,26 +794,6 @@ "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==", "dev": true }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1106,52 +810,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isbinaryfile": { @@ -1163,42 +831,12 @@ "buffer-alloc": "^1.2.0" } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -1208,18 +846,6 @@ "graceful-fs": "^4.1.6" } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -1235,19 +861,6 @@ "json-buffer": "3.0.0" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -1264,61 +877,12 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "dev": true - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "dev": true, - "requires": { - "mime-db": "1.40.0" - } - }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -1381,62 +945,6 @@ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, - "nugget": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", - "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", - "dev": true, - "requires": { - "debug": "^2.1.3", - "minimist": "^1.1.0", - "pretty-bytes": "^1.0.2", - "progress-stream": "^1.1.0", - "request": "^2.45.0", - "single-line-log": "^1.1.2", - "throttleit": "0.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1511,50 +1019,18 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "plist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", @@ -1572,38 +1048,12 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, - "pretty-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", - "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.1.0" - } - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "progress-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", - "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", - "dev": true, - "requires": { - "speedometer": "~0.1.2", - "through2": "~0.2.3" - } - }, - "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1614,114 +1064,25 @@ "once": "^1.3.1" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, "rcedit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-2.0.0.tgz", "integrity": "sha512-XcFGyEBjhWSsud+R8elwQtGBbVkCf7tAiad+nXo5jc6l2rMf46NfGNwjnmBNneBIZDfq+Npf8lwP371JTONfrw==", "dev": true }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "require-directory": { @@ -1762,15 +1123,9 @@ } }, "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "sanitize-filename": { @@ -1793,21 +1148,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "single-line-log": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", - "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", - "dev": true, - "requires": { - "string-width": "^1.0.1" - } - }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -1840,119 +1180,13 @@ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, - "speedometer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", - "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "sumchecker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", - "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", - "dev": true - }, - "through2": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", - "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9", - "xtend": "~2.1.1" + "safe-buffer": "~5.1.0" } }, "tmp": { @@ -1980,30 +1214,6 @@ "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", "dev": true }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, "truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -2013,21 +1223,6 @@ "utf8-byte-length": "^1.0.1" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -2040,15 +1235,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -2070,12 +1256,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true - }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -2086,17 +1266,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -2160,15 +1329,6 @@ "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", "dev": true }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", @@ -2282,12 +1442,13 @@ } }, "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "requires": { - "fd-slicer": "~1.0.1" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } } } diff --git a/screenshare/package.json b/screenshare/package.json index 372679082f..e92e20a073 100644 --- a/screenshare/package.json +++ b/screenshare/package.json @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/highfidelity/hifi#readme", "devDependencies": { - "electron": "^6.0.12", + "electron": "^7.2.4", "electron-packager": "^14.0.6" }, "dependencies": { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 71fb644528..b27008e255 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -36,7 +36,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/inspect.js", "system/keyboardShortcuts/keyboardShortcuts.js", "system/checkForUpdates.js", - "system/onFirstRun.js" + "system/onFirstRun.js", + "system/appreciate/appreciate_app.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/appreciate/README.md b/scripts/system/appreciate/README.md new file mode 100644 index 0000000000..b3f9a3649b --- /dev/null +++ b/scripts/system/appreciate/README.md @@ -0,0 +1,40 @@ +# Appreciate + +## Description + +Show someone else that you like what they're doing. Open the app to see usage instructions and some options! + +## Releases + +### v1.5 | [48d8247](https://github.com/highfidelity/hifi-content/commit/48d8247) + +- Fixed an issue where Appreciate app users wearing avatars without a specific joint wouldn't hear the Appreciate sound or see the Appreciation Dodecahedron + +### 2019-03-08_11-37-00 | Marketplace v1.4 | [93bf464](https://github.com/highfidelity/hifi-content/commit/93bf464) + +- Fixed an issue where a user could press the "Z" key to appreciate while the Appreciate UI was focused even if the Appreciate switch was turned off +- Fixed an issue where Appreciation Intensity decayed too quickly after switching from HMD mode to Desktop mode + +### 2019-02-22_10-49-00 | Marketplace v1.3 | [51704b5](https://github.com/highfidelity/hifi-content/commit/51704b5) + +- Optimize app +- Add option to not show Appreciation Dodecahedron while Appreciating +- Forward Z keypresses to the Appreciate script that the user makes when the App's HTML UI is focused + +### 2019-02-19_13-09-00 | Marketplace v1.2 | [0e2fa82](https://github.com/highfidelity/hifi-content/commit/0e2fa82) + +- Introduced functionality to stop running versions of Appreciate when those versions are baked into the client installation AND other versions of Appreciate are running + +### 2019-02-15_17-03-00 | Marketplace v1.1 | [83f8927](https://github.com/highfidelity/hifi-content/commit/83f8927) + +- Ensure that old Appreciation Dodecahedrons will be deleted in the event of a client crashing while Appreciating + +### 2019-02-14_10-00-00 | Marketplace v1.0 | [658ed4e](https://github.com/highfidelity/hifi-content/commit/658ed4e) + +- Initial Release + +## Project Links +[Trello Card](https://trello.com/c/2iMbEgdw/36-appreciation-app) + +## Known issues +- N/A diff --git a/scripts/system/appreciate/appreciate.jpg b/scripts/system/appreciate/appreciate.jpg new file mode 100644 index 0000000000..615991743b Binary files /dev/null and b/scripts/system/appreciate/appreciate.jpg differ diff --git a/scripts/system/appreciate/appreciate_app.js b/scripts/system/appreciate/appreciate_app.js new file mode 100644 index 0000000000..dce2a93502 --- /dev/null +++ b/scripts/system/appreciate/appreciate_app.js @@ -0,0 +1,1138 @@ +/* + Appreciate + Created by Zach Fox on 2019-01-30 + Copyright 2019 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 +*/ + +(function () { + // ************************************* + // START UTILITY FUNCTIONS + // ************************************* + // #region Utilities + var MS_PER_S = 1000; + var CM_PER_M = 100; + var HALF = 0.5; + + + // Returns the first valid joint position from the list of supplied test joint positions. + // If none are valid, returns MyAvatar.position. + function getValidJointPosition(jointsToTest) { + var currentJointIndex; + + for (var i = 0; i < jointsToTest.length; i++) { + currentJointIndex = MyAvatar.getJointIndex(jointsToTest[i]); + + if (currentJointIndex > -1) { + return MyAvatar.getJointPosition(jointsToTest[i]); + } + } + + return MyAvatar.position; + } + + + // Returns the world position halfway between the user's hands + function getAppreciationPosition() { + var validLeftJoints = ["LeftHandMiddle2", "LeftHand", "LeftArm"]; + var leftPosition = getValidJointPosition(validLeftJoints); + + var validRightJoints = ["RightHandMiddle2", "RightHand", "RightArm"];; + var rightPosition = getValidJointPosition(validRightJoints); + + var centerPosition = Vec3.sum(leftPosition, rightPosition); + centerPosition = Vec3.multiply(centerPosition, HALF); + + return centerPosition; + } + + + // Returns a linearly scaled value based on `factor` and the other inputs + function linearScale(factor, minInput, maxInput, minOutput, maxOutput) { + return minOutput + (maxOutput - minOutput) * + (factor - minInput) / (maxInput - minInput); + } + + + // Linearly scales an RGB color between 0 and 1 based on RGB color values + // between 0 and 255. + function linearScaleColor(intensity, min, max) { + var output = { + "red": 0, + "green": 0, + "blue": 0 + }; + + output.red = linearScale(intensity, 0, 1, min.red, max.red); + output.green = linearScale(intensity, 0, 1, min.green, max.green); + output.blue = linearScale(intensity, 0, 1, min.blue, max.blue); + + return output; + } + + + function randomFloat(min, max) { + return Math.random() * (max - min) + min; + } + + + // Updates the Current Intensity Meter UI element. Called when intensity changes. + function updateCurrentIntensityUI() { + ui.sendMessage({method: "updateCurrentIntensityUI", currentIntensity: currentIntensity}); + } + // #endregion + // ************************************* + // END UTILITY FUNCTIONS + // ************************************* + + // If the interval that updates the intensity interval exists, + // clear it. + var updateIntensityEntityInterval = false; + var UPDATE_INTENSITY_ENTITY_INTERVAL_MS = 75; + function maybeClearUpdateIntensityEntityInterval() { + if (updateIntensityEntityInterval) { + Script.clearInterval(updateIntensityEntityInterval); + updateIntensityEntityInterval = false; + } + + if (intensityEntity) { + Entities.deleteEntity(intensityEntity); + intensityEntity = false; + } + } + + + // Determines if any XYZ JSON object has changed "enough" based on + // last xyz values and current xyz values. + // Used for determining if angular velocity and dimensions have changed enough. + var lastAngularVelocity = { + "x": 0, + "y": 0, + "z": 0 + }; + var ANGVEL_DISTANCE_THRESHOLD_PERCENT_CHANGE = 0.35; + var lastDimensions= { + "x": 0, + "y": 0, + "z": 0 + }; + var DIMENSIONS_DISTANCE_THRESHOLD_PERCENT_CHANGE = 0.2; + function xyzVecChangedEnough(current, last, thresh) { + var currentLength = Math.sqrt( + Math.pow(current.x, TWO) + Math.pow(current.y, TWO) + Math.pow(current.z, TWO)); + var lastLength = Math.sqrt( + Math.pow(last.x, TWO) + Math.pow(last.y, TWO) + Math.pow(last.z, TWO)); + + var change = Math.abs(currentLength - lastLength); + if (change/lastLength > thresh) { + return true; + } + + return false; + } + + + // Determines if color values have changed "enough" based on + // last color and current color + var lastColor = { + "red": 0, + "blue": 0, + "green": 0 + }; + var COLOR_DISTANCE_THRESHOLD_PERCENT_CHANGE = 0.35; + var TWO = 2; + function colorChangedEnough(current, last, thresh) { + var currentLength = Math.sqrt( + Math.pow(current.red, TWO) + Math.pow(current.green, TWO) + Math.pow(current.blue, TWO)); + var lastLength = Math.sqrt( + Math.pow(last.red, TWO) + Math.pow(last.green, TWO) + Math.pow(last.blue, TWO)); + + var change = Math.abs(currentLength - lastLength); + if (change/lastLength > thresh) { + return true; + } + + return false; + } + + + // Updates the intensity entity based on the user's avatar's hand position and the + // current intensity of their appreciation. + // Many of these property values are empirically determined. + var intensityEntity = false; + var INTENSITY_ENTITY_MAX_DIMENSIONS = { + "x": 0.24, + "y": 0.24, + "z": 0.24 + }; + var INTENSITY_ENTITY_MIN_ANGULAR_VELOCITY = { + "x": -0.21, + "y": -0.21, + "z": -0.21 + }; + var INTENSITY_ENTITY_MAX_ANGULAR_VELOCITY = { + "x": 0.21, + "y": 0.21, + "z": 0.21 + }; + var intensityEntityColorMin = { + "red": 82, + "green": 196, + "blue": 145 + }; + var INTENSITY_ENTITY_COLOR_MAX_DEFAULT = { + "red": 5, + "green": 255, + "blue": 5 + }; + var MIN_COLOR_MULTIPLIER = 0.4; + var intensityEntityColorMax = JSON.parse(Settings.getValue("appreciate/entityColor", + JSON.stringify(INTENSITY_ENTITY_COLOR_MAX_DEFAULT))); + var ANGVEL_ENTITY_MULTIPLY_FACTOR = 62; + var INTENSITY_ENTITY_NAME = "Appreciation Dodecahedron"; + var INTENSITY_ENTITY_PROPERTIES = { + "name": INTENSITY_ENTITY_NAME, + "type": "Shape", + "shape": "Dodecahedron", + "dimensions": { + "x": 0, + "y": 0, + "z": 0 + }, + "angularVelocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "angularDamping": 0, + "grab": { + "grabbable": false, + "equippableLeftRotation": { + "x": -0.0000152587890625, + "y": -0.0000152587890625, + "z": -0.0000152587890625, + "w": 1 + }, + "equippableRightRotation": { + "x": -0.0000152587890625, + "y": -0.0000152587890625, + "z": -0.0000152587890625, + "w": 1 + } + }, + "collisionless": true, + "ignoreForCollisions": true, + "queryAACube": { + "x": -0.17320507764816284, + "y": -0.17320507764816284, + "z": -0.17320507764816284, + "scale": 0.3464101552963257 + }, + "damping": 0, + "color": intensityEntityColorMin, + "clientOnly": false, + "avatarEntity": true, + "localEntity": false, + "faceCamera": false, + "isFacingAvatar": false + }; + var currentInitialAngularVelocity = { + "x": 0, + "y": 0, + "z": 0 + }; + function updateIntensityEntity() { + if (!showAppreciationEntity) { + return; + } + + if (currentIntensity > 0) { + if (intensityEntity) { + intensityEntityColorMin.red = intensityEntityColorMax.red * MIN_COLOR_MULTIPLIER; + intensityEntityColorMin.green = intensityEntityColorMax.green * MIN_COLOR_MULTIPLIER; + intensityEntityColorMin.blue = intensityEntityColorMax.blue * MIN_COLOR_MULTIPLIER; + + var color = linearScaleColor(currentIntensity, intensityEntityColorMin, intensityEntityColorMax); + + var propsToUpdate = { + position: getAppreciationPosition() + }; + + var currentDimensions = Vec3.multiply(INTENSITY_ENTITY_MAX_DIMENSIONS, currentIntensity); + if (xyzVecChangedEnough(currentDimensions, lastDimensions, DIMENSIONS_DISTANCE_THRESHOLD_PERCENT_CHANGE)) { + propsToUpdate.dimensions = currentDimensions; + lastDimensions = currentDimensions; + } + + var currentAngularVelocity = Vec3.multiply(currentInitialAngularVelocity, + currentIntensity * ANGVEL_ENTITY_MULTIPLY_FACTOR); + if (xyzVecChangedEnough(currentAngularVelocity, lastAngularVelocity, ANGVEL_DISTANCE_THRESHOLD_PERCENT_CHANGE)) { + propsToUpdate.angularVelocity = currentAngularVelocity; + lastAngularVelocity = currentAngularVelocity; + } + + var currentColor = color; + if (colorChangedEnough(currentColor, lastColor, COLOR_DISTANCE_THRESHOLD_PERCENT_CHANGE)) { + propsToUpdate.color = currentColor; + lastColor = currentColor; + } + + Entities.editEntity(intensityEntity, propsToUpdate); + } else { + var props = INTENSITY_ENTITY_PROPERTIES; + props.position = getAppreciationPosition(); + + currentInitialAngularVelocity.x = + randomFloat(INTENSITY_ENTITY_MIN_ANGULAR_VELOCITY.x, INTENSITY_ENTITY_MAX_ANGULAR_VELOCITY.x); + currentInitialAngularVelocity.y = + randomFloat(INTENSITY_ENTITY_MIN_ANGULAR_VELOCITY.y, INTENSITY_ENTITY_MAX_ANGULAR_VELOCITY.y); + currentInitialAngularVelocity.z = + randomFloat(INTENSITY_ENTITY_MIN_ANGULAR_VELOCITY.z, INTENSITY_ENTITY_MAX_ANGULAR_VELOCITY.z); + props.angularVelocity = currentInitialAngularVelocity; + + intensityEntity = Entities.addEntity(props, "avatar"); + } + } else { + if (intensityEntity) { + Entities.deleteEntity(intensityEntity); + intensityEntity = false; + } + + maybeClearUpdateIntensityEntityInterval(); + } + } + + + // Function that AppUI calls when the App's UI opens + function onOpened() { + updateCurrentIntensityUI(); + } + + + // Locally pre-caches all of the sounds in the sounds/claps and sounds/whistles + // directories. + var NUM_CLAP_SOUNDS = 16; + var NUM_WHISTLE_SOUNDS = 17; + var clapSounds = []; + var whistleSounds = []; + function getSounds() { + for (var i = 1; i < NUM_CLAP_SOUNDS + 1; i++) { + clapSounds.push(SoundCache.getSound(Script.resolvePath( + "resources/sounds/claps/" + ("0" + i).slice(-2) + ".wav"))); + } + for (i = 1; i < NUM_WHISTLE_SOUNDS + 1; i++) { + whistleSounds.push(SoundCache.getSound(Script.resolvePath( + "resources/sounds/whistles/" + ("0" + i).slice(-2) + ".wav"))); + } + } + + + // Locally pre-caches the Cheering and Clapping animations + var whistlingAnimation = false; + var clappingAnimation = false; + function getAnimations() { + var animationURL = Script.resolvePath("resources/animations/Cheering.fbx"); + var resource = AnimationCache.prefetch(animationURL); + var animation = AnimationCache.getAnimation(animationURL); + whistlingAnimation = {url: animationURL, animation: animation, resource: resource}; + + animationURL = Script.resolvePath("resources/animations/Clapping.fbx"); + resource = AnimationCache.prefetch(animationURL); + animation = AnimationCache.getAnimation(animationURL); + clappingAnimation = {url: animationURL, animation: animation, resource: resource}; + } + + + // If we're currently fading out the appreciation sounds on an interval, + // clear that interval. + function maybeClearSoundFadeInterval() { + if (soundFadeInterval) { + Script.clearInterval(soundFadeInterval); + soundFadeInterval = false; + } + } + + + // Fade out the appreciation sounds by quickly + // lowering the global current intensity. + var soundFadeInterval = false; + var FADE_INTERVAL_MS = 20; + var FADE_OUT_STEP_SIZE = 0.05; // Unitless + function fadeOutAndStopSound() { + maybeClearSoundFadeInterval(); + + soundFadeInterval = Script.setInterval(function() { + currentIntensity -= FADE_OUT_STEP_SIZE; + + if (currentIntensity <= 0) { + if (soundInjector) { + soundInjector.stop(); + soundInjector = false; + } + + updateCurrentIntensityUI(); + + maybeClearSoundFadeInterval(); + } + + fadeIntensity(currentIntensity, INTENSITY_MAX_STEP_SIZE_DESKTOP); + }, FADE_INTERVAL_MS); + } + + + // Calculates the audio injector volume based on + // the current global appreciation intensity and some min/max values. + var MIN_VOLUME_CLAP = 0.05; + var MAX_VOLUME_CLAP = 1.0; + var MIN_VOLUME_WHISTLE = 0.07; + var MAX_VOLUME_WHISTLE = 0.16; + function calculateInjectorVolume() { + var minInputVolume = 0; + var maxInputVolume = MAX_CLAP_INTENSITY; + var minOutputVolume = MIN_VOLUME_CLAP; + var maxOutputVolume = MAX_VOLUME_CLAP; + + if (currentSound === "whistle") { + minInputVolume = MAX_CLAP_INTENSITY; + maxInputVolume = MAX_WHISTLE_INTENSITY; + minOutputVolume = MIN_VOLUME_WHISTLE; + maxOutputVolume = MAX_VOLUME_WHISTLE; + } + + var vol = linearScale(currentIntensity, minInputVolume, + maxInputVolume, minOutputVolume, maxOutputVolume); + return vol; + } + + + // Modifies the global currentIntensity. Moves towards the targetIntensity, + // but never moves faster than a given max step size per function call. + // Also clamps the intensity to a min of 0 and a max of 1.0. + var currentIntensity = 0; + var INTENSITY_MAX_STEP_SIZE = 0.003; // Unitless, determined empirically + var INTENSITY_MAX_STEP_SIZE_DESKTOP = 1; // Unitless, determined empirically + var MAX_CLAP_INTENSITY = 0.55; // Unitless, determined empirically + var MAX_WHISTLE_INTENSITY = 1.0; // Unitless, determined empirically + function fadeIntensity(targetIntensity, maxStepSize) { + if (!maxStepSize) { + maxStepSize = INTENSITY_MAX_STEP_SIZE; + } + + var volumeDelta = targetIntensity - currentIntensity; + volumeDelta = Math.min(Math.abs(volumeDelta), maxStepSize); + + if (targetIntensity < currentIntensity) { + volumeDelta *= -1; + } + + currentIntensity += volumeDelta; + + currentIntensity = Math.max(0.0, Math.min( + neverWhistleEnabled ? MAX_CLAP_INTENSITY : MAX_WHISTLE_INTENSITY, currentIntensity)); + + updateCurrentIntensityUI(); + + // Don't adjust volume or position while a sound is playing. + if (!soundInjector || soundInjector.isPlaying()) { + return; + } + + var injectorOptions = { + position: getAppreciationPosition(), + volume: calculateInjectorVolume() + }; + + soundInjector.setOptions(injectorOptions); + } + + + // Call this function to actually play a sound. + // Doesn't play a new sound if a sound is playing AND (you're whistling OR you're in HMD) + // Injectors are placed between the user's hands (at the same location as the apprecation + // entity) and are randomly pitched between a MIN and MAX value. + // Only uses one injector, ever. + var soundInjector = false; + var MINIMUM_PITCH = 0.85; + var MAXIMUM_PITCH = 1.15; + function playSound(sound) { + if (soundInjector && soundInjector.isPlaying() && (currentSound === "whistle" || HMD.active)) { + return; + } + + if (soundInjector) { + soundInjector.stop(); + soundInjector = false; + } + + soundInjector = Audio.playSound(sound, { + position: getAppreciationPosition(), + volume: calculateInjectorVolume(), + pitch: randomFloat(MINIMUM_PITCH, MAXIMUM_PITCH) + }); + } + + + // Returns true if the global intensity and user settings dictate that clapping is the + // right thing to do. + function shouldClap() { + return (currentIntensity > 0.0 && neverWhistleEnabled) || + (currentIntensity > 0.0 && currentIntensity <= MAX_CLAP_INTENSITY); + } + + + // Returns true if the global intensity and user settings dictate that whistling is the + // right thing to do. + function shouldWhistle() { + return currentIntensity > MAX_CLAP_INTENSITY && + currentIntensity <= MAX_WHISTLE_INTENSITY; + } + + + // Selects the correct sound, then plays it. + var currentSound; + function selectAndPlaySound() { + if (shouldClap()) { + currentSound = "clap"; + playSound(clapSounds[Math.floor(Math.random() * clapSounds.length)]); + } else if (shouldWhistle()) { + currentSound = "whistle"; + playSound(whistleSounds[Math.floor(Math.random() * whistleSounds.length)]); + } + } + + + // If there exists a VR debounce timer (used for not playing sounds too often), + // clear it. + function maybeClearVRDebounceTimer() { + if (vrDebounceTimer) { + Script.clearTimeout(vrDebounceTimer); + vrDebounceTimer = false; + } + } + + + // Calculates the current intensity of appreciation based on the user's + // hand velocity (rotational and linear). + // Each type of velocity is weighted differently when determining the final intensity. + // The VR debounce timer length changes based on current intensity. This forces + // sounds to play further apart when the user isn't appreciating hard. + var MAX_VELOCITY_CM_PER_SEC = 110; // determined empirically + var MAX_ANGULAR_VELOCITY_LENGTH = 1.5; // determined empirically + var LINEAR_VELOCITY_WEIGHT = 0.7; // This and the line below must add up to 1.0 + var ANGULAR_VELOCITY_LENGTH_WEIGHT = 0.3; // This and the line below must add up to 1.0 + var vrDebounceTimer = false; + var VR_DEBOUNCE_TIMER_TIMEOUT_MIN_MS = 20; // determined empirically + var VR_DEBOUNCE_TIMER_TIMEOUT_MAX_MS = 200; // determined empirically + function calculateHandEffect(linearVelocity, angularVelocity){ + var leftHandLinearVelocityCMPerSec = linearVelocity.left; + var rightHandLinearVelocityCMPerSec = linearVelocity.right; + var averageLinearVelocity = (leftHandLinearVelocityCMPerSec + rightHandLinearVelocityCMPerSec) / 2; + averageLinearVelocity = Math.min(averageLinearVelocity, MAX_VELOCITY_CM_PER_SEC); + + var leftHandAngularVelocityLength = Vec3.length(angularVelocity.left); + var rightHandAngularVelocityLength = Vec3.length(angularVelocity.right); + var averageAngularVelocityIntensity = (leftHandAngularVelocityLength + rightHandAngularVelocityLength) / 2; + averageAngularVelocityIntensity = Math.min(averageAngularVelocityIntensity, MAX_ANGULAR_VELOCITY_LENGTH); + + var appreciationIntensity = + averageLinearVelocity / MAX_VELOCITY_CM_PER_SEC * LINEAR_VELOCITY_WEIGHT + + averageAngularVelocityIntensity / MAX_ANGULAR_VELOCITY_LENGTH * ANGULAR_VELOCITY_LENGTH_WEIGHT; + + fadeIntensity(appreciationIntensity); + + var vrDebounceTimeout = VR_DEBOUNCE_TIMER_TIMEOUT_MIN_MS + + (VR_DEBOUNCE_TIMER_TIMEOUT_MAX_MS - VR_DEBOUNCE_TIMER_TIMEOUT_MIN_MS) * (1.0 - appreciationIntensity); + // This timer forces a minimum tail duration for all sound clips + if (!vrDebounceTimer) { + selectAndPlaySound(); + vrDebounceTimer = Script.setTimeout(function() { + vrDebounceTimer = false; + }, vrDebounceTimeout); + } + } + + + // Gets both hands' linear velocity. + var lastLeftHandPosition = false; + var lastRightHandPosition = false; + function getHandsLinearVelocity() { + var linearVelocity = { + left: 0, + right: 0 + }; + + var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); + var rightHandPosition = MyAvatar.getJointPosition("RightHand"); + + if (!lastLeftHandPosition || !lastRightHandPosition) { + lastLeftHandPosition = leftHandPosition; + lastRightHandPosition = rightHandPosition; + return linearVelocity; + } + + var leftHandDistanceCM = Vec3.distance(leftHandPosition, lastLeftHandPosition) * CM_PER_M; + var rightHandDistanceCM = Vec3.distance(rightHandPosition, lastRightHandPosition) * CM_PER_M; + + linearVelocity.left = leftHandDistanceCM / HAND_VELOCITY_CHECK_INTERVAL_MS * MS_PER_S; + linearVelocity.right = rightHandDistanceCM / HAND_VELOCITY_CHECK_INTERVAL_MS * MS_PER_S; + + lastLeftHandPosition = leftHandPosition; + lastRightHandPosition = rightHandPosition; + + return linearVelocity; + } + + + // Gets both hands' angular velocity. + var lastLeftHandRotation = false; + var lastRightHandRotation = false; + function getHandsAngularVelocity() { + var angularVelocity = { + left: {x: 0, y: 0, z: 0}, + right: {x: 0, y: 0, z: 0} + }; + + var leftHandRotation = MyAvatar.getJointRotation(MyAvatar.getJointIndex("LeftHand")); + var rightHandRotation = MyAvatar.getJointRotation(MyAvatar.getJointIndex("RightHand")); + + if (!lastLeftHandRotation || !lastRightHandRotation) { + lastLeftHandRotation = leftHandRotation; + lastRightHandRotation = rightHandRotation; + return angularVelocity; + } + + var leftHandAngleDelta = Quat.multiply(leftHandRotation, Quat.inverse(lastLeftHandRotation)); + var rightHandAngleDelta = Quat.multiply(rightHandRotation, Quat.inverse(lastRightHandRotation)); + + leftHandAngleDelta = Quat.safeEulerAngles(leftHandAngleDelta); + rightHandAngleDelta = Quat.safeEulerAngles(rightHandAngleDelta); + + angularVelocity.left = Vec3.multiply(leftHandAngleDelta, 1 / HAND_VELOCITY_CHECK_INTERVAL_MS); + angularVelocity.right = Vec3.multiply(rightHandAngleDelta, 1 / HAND_VELOCITY_CHECK_INTERVAL_MS); + + lastLeftHandRotation = leftHandRotation; + lastRightHandRotation = rightHandRotation; + + return angularVelocity; + } + + + // Calculates the hand effect (see above). Gets called on an interval, + // but only if the user's hands are above their head. This saves processing power. + // Also sets up the `updateIntensityEntity` interval. + function handVelocityCheck() { + if (!handsAreAboveHead) { + return; + } + + var handsLinearVelocity = getHandsLinearVelocity(); + var handsAngularVelocity = getHandsAngularVelocity(); + + calculateHandEffect(handsLinearVelocity, handsAngularVelocity); + + if (!updateIntensityEntityInterval && showAppreciationEntity) { + updateIntensityEntityInterval = Script.setInterval(updateIntensityEntity, UPDATE_INTENSITY_ENTITY_INTERVAL_MS); + } + } + + + // If handVelocityCheckInterval is set up, clear it. + function maybeClearHandVelocityCheck() { + if (handVelocityCheckInterval) { + Script.clearInterval(handVelocityCheckInterval); + handVelocityCheckInterval = false; + } + } + + + // If handVelocityCheckInterval is set up, clear it. + // Also stop the sound injector and set currentIntensity to 0. + function maybeClearHandVelocityCheckIntervalAndStopSound() { + maybeClearHandVelocityCheck(); + + if (soundInjector) { + soundInjector.stop(); + soundInjector = false; + } + + currentIntensity = 0.0; + } + + + // Sets up an interval that'll check the avatar's hand's velocities. + // This is used for calculating the effect. + // If the user isn't in HMD, we'll never set up this interval. + var handVelocityCheckInterval = false; + var HAND_VELOCITY_CHECK_INTERVAL_MS = 10; + function maybeSetupHandVelocityCheckInterval() { + // `!HMD.active` clause isn't really necessary, just extra protection + if (handVelocityCheckInterval || !HMD.active) { + return; + } + + handVelocityCheckInterval = Script.setInterval(handVelocityCheck, HAND_VELOCITY_CHECK_INTERVAL_MS); + } + + + // Checks the position of the user's hands to determine if they're above their head. + // If they are, sets up the hand velocity check interval (see above). + // If they aren't, clears that interval and stops the apprecation sound. + var handsAreAboveHead = false; + function handPositionCheck() { + var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); + var rightHandPosition = MyAvatar.getJointPosition("RightHand"); + var headJointPosition = MyAvatar.getJointPosition("Head"); + + var headY = headJointPosition.y; + + handsAreAboveHead = (rightHandPosition.y > headY && leftHandPosition.y > headY); + + if (handsAreAboveHead) { + maybeSetupHandVelocityCheckInterval(); + } else { + maybeClearHandVelocityCheck(); + fadeOutAndStopSound(); + } + } + + + // If handPositionCheckInterval is set up, clear it. + function maybeClearHandPositionCheckInterval() { + if (handPositionCheckInterval) { + Script.clearInterval(handPositionCheckInterval); + handPositionCheckInterval = false; + } + } + + + // If the app is enabled, sets up an interval that'll check if the avatar's hands are above their head. + var handPositionCheckInterval = false; + var HAND_POSITION_CHECK_INTERVAL_MS = 200; + function maybeSetupHandPositionCheckInterval() { + if (!appreciateEnabled || !HMD.active) { + return; + } + + maybeClearHandPositionCheckInterval(); + + handPositionCheckInterval = Script.setInterval(handPositionCheck, HAND_POSITION_CHECK_INTERVAL_MS); + } + + + // If the interval that periodically lowers the apprecation volume is set up, clear it. + function maybeClearSlowAppreciationInterval() { + if (slowAppreciationInterval) { + Script.clearInterval(slowAppreciationInterval); + slowAppreciationInterval = false; + } + } + + + // Stop appreciating. Called when Appreciating from Desktop mode. + function stopAppreciating() { + maybeClearStopAppreciatingTimeout(); + maybeClearSlowAppreciationInterval(); + maybeClearUpdateIntensityEntityInterval(); + MyAvatar.restoreAnimation(); + currentAnimationFPS = INITIAL_ANIMATION_FPS; + currentlyPlayingFrame = 0; + currentAnimationTimestamp = 0; + } + + + // If the timeout that stops the user's apprecation is set up, clear it. + function maybeClearStopAppreciatingTimeout() { + if (stopAppreciatingTimeout) { + Script.clearTimeout(stopAppreciatingTimeout); + stopAppreciatingTimeout = false; + } + } + + + function calculateCurrentAnimationFPS(frameCount) { + var animationTimestampDeltaMS = Date.now() - currentAnimationTimestamp; + var frameDelta = animationTimestampDeltaMS / MS_PER_S * currentAnimationFPS; + + currentlyPlayingFrame = (currentlyPlayingFrame + frameDelta) % frameCount; + + currentAnimationFPS = currentIntensity * CHEERING_FPS_MAX + INITIAL_ANIMATION_FPS; + + currentAnimationFPS = Math.min(currentAnimationFPS, CHEERING_FPS_MAX); + + if (currentAnimation === clappingAnimation) { + currentAnimationFPS += CLAP_ANIMATION_FPS_BOOST; + } + } + + + // Called on an interval. Slows down the user's appreciation! + var VOLUME_STEP_DOWN_DESKTOP = 0.01; // Unitless, determined empirically + function slowAppreciation() { + currentIntensity -= VOLUME_STEP_DOWN_DESKTOP; + fadeIntensity(currentIntensity, INTENSITY_MAX_STEP_SIZE_DESKTOP); + + currentAnimation = selectAnimation(); + + if (!currentAnimation) { + stopAppreciating(); + return; + } + + var frameCount = currentAnimation.animation.frames.length; + + calculateCurrentAnimationFPS(frameCount); + + MyAvatar.overrideAnimation(currentAnimation.url, currentAnimationFPS, true, currentlyPlayingFrame, frameCount); + + currentAnimationTimestamp = Date.now(); + } + + + // Selects the proper animation to use when Appreciating in Desktop mode. + function selectAnimation() { + if (shouldClap()) { + if (currentAnimation === whistlingAnimation) { + currentAnimationTimestamp = 0; + } + return clappingAnimation; + } else if (shouldWhistle()) { + if (currentAnimation === clappingAnimation) { + currentAnimationTimestamp = 0; + } + return whistlingAnimation; + } else { + return false; + } + } + + + // Called when the Z key is pressed (and some other conditions are met). + // 1. (Maybe) clears old intervals + // 2. Steps up the global currentIntensity, then forces the effect/sound to fade/play immediately + // 3. Selects an animation to play based on various factors, then plays it + // - Stops appreciating if the selected animation is falsey + // 4. Sets up the "Slow Appreciation" interval which slows appreciation over time + // 5. Modifies the avatar's animation based on the current appreciation intensity + // - Since there's no way to modify animation FPS on-the-fly, we have to calculate + // where the animation should start based on where it was before changing FPS + // 6. Sets up the `updateIntensityEntity` interval if one isn't already setup + var INITIAL_ANIMATION_FPS = 7; + var SLOW_APPRECIATION_INTERVAL_MS = 100; + var CHEERING_FPS_MAX = 80; + var VOLUME_STEP_UP_DESKTOP = 0.035; // Unitless, determined empirically + var CLAP_ANIMATION_FPS_BOOST = 15; + var currentAnimation = false; + var currentAnimationFPS = INITIAL_ANIMATION_FPS; + var slowAppreciationInterval = false; + var currentlyPlayingFrame = 0; + var currentAnimationTimestamp; + function keyPressed() { + // Don't do anything if the animations aren't cached. + if (!whistlingAnimation || !clappingAnimation) { + return; + } + + maybeClearSoundFadeInterval(); + maybeClearStopAppreciatingTimeout(); + + currentIntensity += VOLUME_STEP_UP_DESKTOP; + fadeIntensity(currentIntensity, INTENSITY_MAX_STEP_SIZE_DESKTOP); + selectAndPlaySound(); + + currentAnimation = selectAnimation(); + + if (!currentAnimation) { + stopAppreciating(); + return; + } + + if (!slowAppreciationInterval) { + slowAppreciationInterval = Script.setInterval(slowAppreciation, SLOW_APPRECIATION_INTERVAL_MS); + } + + var frameCount = currentAnimation.animation.frames.length; + + if (currentAnimationTimestamp > 0) { + calculateCurrentAnimationFPS(frameCount); + } else { + currentlyPlayingFrame = 0; + } + + MyAvatar.overrideAnimation(currentAnimation.url, currentAnimationFPS, true, currentlyPlayingFrame, frameCount); + currentAnimationTimestamp = Date.now(); + + if (!updateIntensityEntityInterval && showAppreciationEntity) { + updateIntensityEntityInterval = Script.setInterval(updateIntensityEntity, UPDATE_INTENSITY_ENTITY_INTERVAL_MS); + } + } + + + // The listener for all in-app keypresses. Listens for an unshifted, un-alted, un-ctrl'd + // "Z" keypress. Only listens when in Desktop mode. If the user is holding the key down, + // we make sure not to call the `keyPressed()` handler too often using the `desktopDebounceTimer`. + var desktopDebounceTimer = false; + var DESKTOP_DEBOUNCE_TIMEOUT_MS = 160; + function keyPressEvent(event) { + if (!appreciateEnabled) { + return; + } + + if ((event.text.toUpperCase() === "Z") && + !event.isShifted && + !event.isMeta && + !event.isControl && + !event.isAlt && + !HMD.active) { + + if (event.isAutoRepeat) { + if (!desktopDebounceTimer) { + keyPressed(); + + desktopDebounceTimer = Script.setTimeout(function() { + desktopDebounceTimer = false; + }, DESKTOP_DEBOUNCE_TIMEOUT_MS); + } + } else { + keyPressed(); + } + } + } + + + // Sets up a timeout that will fade out the appreciation sound, then stop it. + var stopAppreciatingTimeout = false; + var STOP_APPRECIATING_TIMEOUT_MS = 1000; + function stopAppreciatingSoon() { + maybeClearStopAppreciatingTimeout(); + + if (currentIntensity > 0) { + stopAppreciatingTimeout = Script.setTimeout(fadeOutAndStopSound, STOP_APPRECIATING_TIMEOUT_MS); + } + } + + + // When the "Z" key is released, we want to stop appreciating a short time later. + function keyReleaseEvent(event) { + if (!appreciateEnabled) { + return; + } + + if ((event.text.toUpperCase() === "Z") && + !event.isAutoRepeat) { + stopAppreciatingSoon(); + } + } + + + // Enables or disables the app's main functionality + var appreciateEnabled = Settings.getValue("appreciate/enabled", false); + var neverWhistleEnabled = Settings.getValue("appreciate/neverWhistle", false); + var showAppreciationEntity = Settings.getValue("appreciate/showAppreciationEntity", true); + var keyEventsWired = false; + function enableOrDisableAppreciate() { + maybeClearHandPositionCheckInterval(); + maybeClearHandVelocityCheckIntervalAndStopSound(); + + if (appreciateEnabled) { + maybeSetupHandPositionCheckInterval(); + + if (!keyEventsWired && !HMD.active) { + Controller.keyPressEvent.connect(keyPressEvent); + Controller.keyReleaseEvent.connect(keyReleaseEvent); + keyEventsWired = true; + } + } else { + stopAppreciating(); + + if (keyEventsWired) { + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + keyEventsWired = false; + } + } + } + + + // Handles incoming messages from the UI + // - "eventBridgeReady" - The App's UI will send this when it's ready to + // receive events over the Event Bridge + // - "appreciateSwitchClicked" - The App's UI will send this when the user + // clicks the main toggle switch in the top right of the app + // - "neverWhistleCheckboxClicked" - Sent when the user clicks the + // "Never Whistle" checkbox + // - "setEntityColor" - Sent when the user chooses a new Entity Color. + function onMessage(message) { + if (message.app !== "appreciate") { + return; + } + + switch (message.method) { + case "eventBridgeReady": + ui.sendMessage({ + method: "updateUI", + appreciateEnabled: appreciateEnabled, + neverWhistleEnabled: neverWhistleEnabled, + showAppreciationEntity: showAppreciationEntity, + isFirstRun: Settings.getValue("appreciate/firstRun", true), + entityColor: intensityEntityColorMax + }); + break; + + case "appreciateSwitchClicked": + Settings.setValue("appreciate/firstRun", false); + appreciateEnabled = message.appreciateEnabled; + Settings.setValue("appreciate/enabled", appreciateEnabled); + enableOrDisableAppreciate(); + break; + + case "neverWhistleCheckboxClicked": + neverWhistleEnabled = message.neverWhistle; + Settings.setValue("appreciate/neverWhistle", neverWhistleEnabled); + break; + + case "showAppreciationEntityCheckboxClicked": + showAppreciationEntity = message.showAppreciationEntity; + Settings.setValue("appreciate/showAppreciationEntity", showAppreciationEntity); + break; + + case "setEntityColor": + intensityEntityColorMax = message.entityColor; + Settings.setValue("appreciate/entityColor", JSON.stringify(intensityEntityColorMax)); + break; + + case "zKeyDown": + var pressEvent = { + "text": "Z", + "isShifted": false, + "isMeta": false, + "isControl": false, + "isAlt": false, + "isAutoRepeat": message.repeat + }; + keyPressEvent(pressEvent); + break; + + case "zKeyUp": + var releaseEvent = { + "text": "Z", + "isShifted": false, + "isMeta": false, + "isControl": false, + "isAlt": false, + "isAutoRepeat": false + }; + keyReleaseEvent(releaseEvent); + break; + + default: + console.log("Unhandled message from appreciate_ui.js: " + JSON.stringify(message)); + break; + } + } + + + // Searches through all of your avatar entities and deletes any with the name + // that equals the one set when rezzing the Intensity Entity + function cleanupOldIntensityEntities() { + MyAvatar.getAvatarEntitiesVariant().forEach(function(avatarEntity) { + var name = Entities.getEntityProperties(avatarEntity.id, 'name').name; + if (name === INTENSITY_ENTITY_NAME && avatarEntity.id !== intensityEntity) { + Entities.deleteEntity(avatarEntity.id); + } + }); + } + + + // Called when the script is stopped. STOP ALL THE THINGS! + function onScriptEnding() { + maybeClearHandPositionCheckInterval(); + maybeClearHandVelocityCheckIntervalAndStopSound(); + maybeClearSoundFadeInterval(); + maybeClearVRDebounceTimer(); + maybeClearUpdateIntensityEntityInterval(); + cleanupOldIntensityEntities(); + + maybeClearStopAppreciatingTimeout(); + stopAppreciating(); + + if (desktopDebounceTimer) { + Script.clearTimeout(desktopDebounceTimer); + desktopDebounceTimer = false; + } + + if (keyEventsWired) { + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + keyEventsWired = false; + } + + if (intensityEntity) { + Entities.deleteEntity(intensityEntity); + intensityEntity = false; + } + + HMD.displayModeChanged.disconnect(enableOrDisableAppreciate); + } + + + // When called, this function will stop the versions of this script that are + // baked into the client installation IF there's another version of the script + // running that ISN'T the baked version. + function maybeStopBakedScriptVersions() { + var THIS_SCRIPT_FILENAME = "appreciate_app.js"; + var RELATIVE_PATH_TO_BAKED_SCRIPT = "system/experiences/appreciate/appResources/appData/" + THIS_SCRIPT_FILENAME; + var bakedLocalScriptPaths = []; + var alsoRunningNonBakedVersion = false; + + var runningScripts = ScriptDiscoveryService.getRunning(); + runningScripts.forEach(function(scriptObject) { + if (scriptObject.local && scriptObject.url.indexOf(RELATIVE_PATH_TO_BAKED_SCRIPT) > -1) { + bakedLocalScriptPaths.push(scriptObject.path); + } + + if (scriptObject.name === THIS_SCRIPT_FILENAME && scriptObject.url.indexOf(RELATIVE_PATH_TO_BAKED_SCRIPT) === -1) { + alsoRunningNonBakedVersion = true; + } + }); + + if (alsoRunningNonBakedVersion && bakedLocalScriptPaths.length > 0) { + for (var i = 0; i < bakedLocalScriptPaths.length; i++) { + ScriptDiscoveryService.stopScript(bakedLocalScriptPaths[i]); + } + } + } + + + // Called when the script starts up + var BUTTON_NAME = "APPRECIATE"; + var APP_UI_URL = Script.resolvePath('resources/appreciate_ui.html'); + var CLEANUP_INTENSITY_ENTITIES_STARTUP_DELAY_MS = 5000; + var AppUI = Script.require('appUi'); + var ui; + function startup() { + ui = new AppUI({ + buttonName: BUTTON_NAME, + home: APP_UI_URL, + // clap by Rena from the Noun Project + graphicsDirectory: Script.resolvePath("./resources/images/icons/"), + onOpened: onOpened, + onMessage: onMessage + }); + + cleanupOldIntensityEntities(); + // We need this because sometimes avatar entities load after this script does. + Script.setTimeout(cleanupOldIntensityEntities, CLEANUP_INTENSITY_ENTITIES_STARTUP_DELAY_MS); + enableOrDisableAppreciate(); + getSounds(); + getAnimations(); + HMD.displayModeChanged.connect(enableOrDisableAppreciate); + maybeStopBakedScriptVersions(); + } + + + Script.scriptEnding.connect(onScriptEnding); + startup(); +})(); + diff --git a/scripts/system/appreciate/resource.json b/scripts/system/appreciate/resource.json new file mode 100644 index 0000000000..a83500fe12 --- /dev/null +++ b/scripts/system/appreciate/resource.json @@ -0,0 +1,41 @@ +{ + "name": "Appreciate App", + "version": "1.5.0", + "description": "Show someone else that you like what they're doing. Open the app to see usage instructions and some options!", + "homepage": "http://www.vircadia.com", + "bugs": "", + "keywords": [ + "Clapping", + "Clap", + "Applause" + ], + "icon": "/appreciate.jpg", + "images": [ + "/appreciate.jpg" + ], + "author": { + "name": "High Fidelity", + "email": "", + "url": "", + "license": "Apache 2.0" + }, + "sublicense": [ + ], + "contributors": [ + ], + "repository": { + "type": "git", + "url": "https://github.com/vircadia/vircadia-content.git" + }, + "main": "/appreciate_app.js", + "type": "app", + "meta": { + }, + "dependencies": { + }, + "engines": { + }, + "resource": { + "version": 1.0.0 + } +} \ No newline at end of file diff --git a/scripts/system/appreciate/resources/animations/Cheering.fbx b/scripts/system/appreciate/resources/animations/Cheering.fbx new file mode 100644 index 0000000000..8787bf4bd8 Binary files /dev/null and b/scripts/system/appreciate/resources/animations/Cheering.fbx differ diff --git a/scripts/system/appreciate/resources/animations/Clapping.fbx b/scripts/system/appreciate/resources/animations/Clapping.fbx new file mode 100644 index 0000000000..d05b41866d Binary files /dev/null and b/scripts/system/appreciate/resources/animations/Clapping.fbx differ diff --git a/scripts/system/appreciate/resources/appreciate_ui.html b/scripts/system/appreciate/resources/appreciate_ui.html new file mode 100644 index 0000000000..24ffb6fb64 --- /dev/null +++ b/scripts/system/appreciate/resources/appreciate_ui.html @@ -0,0 +1,84 @@ + + + + + + Appreciate + + + + + + + + +
+
+ +
+
+
+ Appreciate v1.5 +
+ +
+ + + +
+ Intensity Meter +
+ +
+
+
+ +
+ Options + + + + + +
+ + +
+
+ +
+
+ Desktop Mode:
Tap or hold the Z key on your keyboard! +
+
+ VR Mode:
Raise your hands above your head and shake them! +
+
+
+ + + + + diff --git a/scripts/system/appreciate/resources/css/style.css b/scripts/system/appreciate/resources/css/style.css new file mode 100644 index 0000000000..abd58b1b22 --- /dev/null +++ b/scripts/system/appreciate/resources/css/style.css @@ -0,0 +1,284 @@ +*, *:before, *:after { + -webkit-box-sizing: inherit; + -moz-box-sizing: inherit; + box-sizing: inherit; +} + +html { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +body { + font-family: 'Raleway', sans-serif; + background-color: #393939; + color: #afafaf; + overflow: hidden; + margin: 0; + padding: 0; +} + +#mainContainer { + width: 100vw; + height: 100vh; +} + +#loadingContainer { + background-color: rgba(0, 0, 0, 0.8); + background-image: url('../images/loadingSpinner.svg'); + background-repeat: no-repeat; + background-position: center center; + width: 100vw; + height: 100vh; + position: fixed; + z-index: 999; +} + +#firstRun { + background-color: rgba(0, 0, 0, 0.9); + width: 100vw; + height: calc(100vh - 60px); + position: fixed; + z-index: 998; + padding: 8px 12px 0 50%; + font-size: 24px; + text-align: right; +} + +#tutorialArrow { + border: solid #00b4ef; + border-width: 0 5px 5px 0; + margin-right: 28px; + display: inline-block; + padding: 5px; + transform: rotate(-135deg); + -webkit-transform: rotate(-135deg); +} + +/* START SWITCH CSS +Mostly from: https://www.w3schools.com/howto/howto_css_switch.asp +*/ +#titleBarContainer { + display: flex; + align-items: center; + height: 60px; + padding: 0 16px; + font-size: 24px; + background-color: #121212; + color: #ffffff; + justify-content: space-between; +} + +/* The switch - the box around the slider */ +.switch { + position: relative; + display: block; + width: 70px; + height: 34px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #00b4ef; +} + +input:focus + .slider { + box-shadow: 0 0 1px #00b4ef; +} + +input:checked + .slider:before { + -webkit-transform: translateX(35px); + -ms-transform: translateX(35px); + transform: translateX(35px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} +/* END SWITCH CSS */ + +/* START PROGRESS BAR CSS */ +#progressBarContainer { + width: calc(100vw - 24px); + margin: 24px auto 0 auto; +} + +#progressBarContainer > span { + font-size: 18px; +} + +#currentIntensityDisplay { + width: 100%; + height: 175px; + margin-top: 8px; + background: #FFFFFF; + background-image: linear-gradient(to right, #EEE 0, #EEE 55%, #FFF 55%, #FFF 100%); +} + +#crosshatch { + display: none; + float: right; + position: relative; + top: -175px; + height: 175px; + width: 45%; + background: repeating-linear-gradient(45deg, transparent 0px, transparent 4px, rgba(0, 0, 0, 0.1) 4px, rgba(0, 0, 0, 0.1) 8px); +} + +#currentIntensity { + display: block; + height: 100%; + background-color: #1ac567; + background-image: linear-gradient(to right,#1ac567 0, #C62147 100%); + position: relative; + overflow: hidden; +} +/* END PROGRESS BAR CSS */ + +#optionsContainer { + display: flex; + flex-direction: column; + height: 150px; + width: calc(100vw - 24px); + margin: 12px 12px 0 12px; + position: absolute; +} + +#colorPickerContainer { + margin: 8px 0 0 0; + visibility: hidden; +} + +#colorPickerContainer > input { + font-family: 'Raleway', sans-serif; + height: 34px; + font-size: 18px; + min-width: 185px; +} + +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 25px; + width: 25px; + background-color: #eee; +} + +/* Create the checkmark/indicator (hidden when not checked) */ +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +#neverWhistleContainer, +#showAppreciationEntityContainer { + display: block; + margin: 8px 0 0 0; + height: 25px; + position: relative; + padding-left: 35px; + cursor: pointer; + font-size: 18px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +#showAppreciationEntityContainer { + margin-top: 16px; +} + +/* Hide the browser's default checkbox */ +#neverWhistleContainer input, +#showAppreciationEntityContainer input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +/* On mouse-over, add a grey background color */ +#neverWhistleContainer:hover input ~ .checkmark, +#showAppreciationEntityContainer:hover input ~ .checkmark { + background-color: #ccc; +} + +/* When the checkbox is checked, add a blue background */ +#neverWhistleContainer input:checked ~ .checkmark, +#showAppreciationEntityContainer input:checked ~ .checkmark { + background-color: #0093C5; +} + +/* Show the checkmark when checked */ +#neverWhistleContainer input:checked ~ .checkmark:after, +#showAppreciationEntityContainer input:checked ~ .checkmark:after { + display: block; +} + +/* Style the checkmark/indicator */ +#neverWhistleContainer .checkmark:after, +#showAppreciationEntityContainer .checkmark:after { + left: 9px; + top: 3px; + width: 8px; + height: 15px; + border: solid white; + border-width: 0 3px 3px 0; + transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} + +#instructions { + position: fixed; + height: 150px; + bottom: 0; + left: 0; + right: 0; + margin: 0 12px; + font-size: 18px; +} + +#instructions > div { + margin-top: 16px; +} \ No newline at end of file diff --git a/scripts/system/appreciate/resources/images/icons/appreciate-a.svg b/scripts/system/appreciate/resources/images/icons/appreciate-a.svg new file mode 100644 index 0000000000..44cd326ea1 --- /dev/null +++ b/scripts/system/appreciate/resources/images/icons/appreciate-a.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + SCHOOL_ICONS_100 + + + + + + SCHOOL_ICONS_100 + + + + + Created by Rena + from the Noun Project + diff --git a/scripts/system/appreciate/resources/images/icons/appreciate-i.svg b/scripts/system/appreciate/resources/images/icons/appreciate-i.svg new file mode 100644 index 0000000000..74ae65b19f --- /dev/null +++ b/scripts/system/appreciate/resources/images/icons/appreciate-i.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + SCHOOL_ICONS_100 + + + + + + SCHOOL_ICONS_100 + + + + + Created by Rena + from the Noun Project + diff --git a/scripts/system/appreciate/resources/images/loadingSpinner.svg b/scripts/system/appreciate/resources/images/loadingSpinner.svg new file mode 100644 index 0000000000..a290bb8c60 --- /dev/null +++ b/scripts/system/appreciate/resources/images/loadingSpinner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/system/appreciate/resources/js/appreciate_ui.js b/scripts/system/appreciate/resources/js/appreciate_ui.js new file mode 100644 index 0000000000..b955193b45 --- /dev/null +++ b/scripts/system/appreciate/resources/js/appreciate_ui.js @@ -0,0 +1,188 @@ +/* + Appreciate + Created by Zach Fox on 2019-01-30 + Copyright 2019 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 +*/ + +/* globals document EventBridge setTimeout */ + +// Called when the user clicks the switch in the top right of the app. +// Sends an event to the App JS and clears the `firstRun` `div`. +function appreciateSwitchClicked(checkbox) { + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "appreciateSwitchClicked", + appreciateEnabled: checkbox.checked + })); + document.getElementById("firstRun").style.display = "none"; +} + +// Called when the user checks/unchecks the Never Whistle checkbox. +// Adds the crosshatch div to the UI and sends an event to the App JS. +function neverWhistleCheckboxClicked(checkbox) { + var crosshatch = document.getElementById("crosshatch"); + if (checkbox.checked) { + crosshatch.style.display = "inline-block"; + } else { + crosshatch.style.display = "none"; + } + + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "neverWhistleCheckboxClicked", + neverWhistle: checkbox.checked + })); +} + +// Called when the user checks/unchecks the Show Appreciation Entity checkbox. +// Sends an event to the App JS. +function showAppreciationEntityCheckboxClicked(checkbox) { + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "showAppreciationEntityCheckboxClicked", + showAppreciationEntity: checkbox.checked + })); + + if (checkbox.checked) { + document.getElementById("colorPickerContainer").style.visibility = "visible"; + } else { + document.getElementById("colorPickerContainer").style.visibility = "hidden"; + } +} + +// Called when the user changes the entity's color using the jscolor picker. +// Modifies the color of the Intensity Meter gradient and sends a message to the App JS. +var START_COLOR_MULTIPLIER = 0.2; +function setEntityColor(jscolor) { + var newEntityColor = { + "red": jscolor.rgb[0], + "green": jscolor.rgb[1], + "blue": jscolor.rgb[2] + }; + + var startColor = { + "red": Math.floor(newEntityColor.red * START_COLOR_MULTIPLIER), + "green": Math.floor(newEntityColor.green * START_COLOR_MULTIPLIER), + "blue": Math.floor(newEntityColor.blue * START_COLOR_MULTIPLIER) + }; + + var currentIntensityDisplayWidth = document.getElementById("currentIntensityDisplay").offsetWidth; + var bgString = "linear-gradient(to right, rgb(" + startColor.red + ", " + + startColor.green + ", " + startColor.blue + ") 0, " + + jscolor.toHEXString() + " " + currentIntensityDisplayWidth + "px)"; + document.getElementById("currentIntensity").style.backgroundImage = bgString; + + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "setEntityColor", + entityColor: newEntityColor + })); +} + +// Handle EventBridge messages from *_app.js. +function onScriptEventReceived(message) { + try { + message = JSON.parse(message); + } catch (error) { + console.log("Couldn't parse script event message: " + error); + return; + } + + // This message gets sent by `entityList.js` when it shouldn't! + if (message.type === "removeEntities") { + return; + } + + switch (message.method) { + case "updateUI": + if (message.isFirstRun) { + document.getElementById("firstRun").style.display = "block"; + } + document.getElementById("appreciateSwitch").checked = message.appreciateEnabled; + document.getElementById("neverWhistleCheckbox").checked = message.neverWhistleEnabled; + + var showAppreciationEntityCheckbox = document.getElementById("showAppreciationEntityCheckbox"); + showAppreciationEntityCheckbox.checked = message.showAppreciationEntity; + if (showAppreciationEntityCheckbox.checked) { + document.getElementById("colorPickerContainer").style.visibility = "visible"; + } else { + document.getElementById("colorPickerContainer").style.visibility = "hidden"; + } + + if (message.neverWhistleEnabled) { + var crosshatch = document.getElementById("crosshatch"); + crosshatch.style.display = "inline-block"; + } + + document.getElementById("loadingContainer").style.display = "none"; + + var color = document.getElementById("colorPicker").jscolor; + color.fromRGB(message.entityColor.red, message.entityColor.green, message.entityColor.blue); + + var startColor = { + "red": Math.floor(color.rgb[0] * START_COLOR_MULTIPLIER), + "green": Math.floor(color.rgb[1] * START_COLOR_MULTIPLIER), + "blue": Math.floor(color.rgb[2] * START_COLOR_MULTIPLIER) + }; + var currentIntensityDisplayWidth = document.getElementById("currentIntensityDisplay").offsetWidth; + document.getElementById("currentIntensity").style.backgroundImage = + "linear-gradient(to right, rgb(" + startColor.red + ", " + + startColor.green + ", " + startColor.blue + ") 0, " + + color.toHEXString() + " " + currentIntensityDisplayWidth + "px)"; + break; + + case "updateCurrentIntensityUI": + document.getElementById("currentIntensity").style.width = message.currentIntensity * 100 + "%"; + break; + + default: + console.log("Unknown message received from appreciate_app.js! " + JSON.stringify(message)); + break; + } +} + +// This function detects a keydown on the document, which enables the app +// to forward these keypress events to the app JS. +function onKeyDown(e) { + var key = e.key.toUpperCase(); + if (key === "Z") { + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "zKeyDown", + repeat: e.repeat + })); + } +} + +// This function detects a keyup on the document, which enables the app +// to forward these keypress events to the app JS. +function onKeyUp(e) { + var key = e.key.toUpperCase(); + if (key === "Z") { + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "zKeyUp" + })); + } +} + +// This delay is necessary to allow for the JS EventBridge to become active. +// The delay is still necessary for HTML apps in RC78+. +var EVENTBRIDGE_SETUP_DELAY = 500; +function onLoad() { + setTimeout(function() { + EventBridge.scriptEventReceived.connect(onScriptEventReceived); + EventBridge.emitWebEvent(JSON.stringify({ + app: "appreciate", + method: "eventBridgeReady" + })); + }, EVENTBRIDGE_SETUP_DELAY); + + document.addEventListener("keydown", onKeyDown); + document.addEventListener("keyup", onKeyUp); +} + +onLoad(); \ No newline at end of file diff --git a/scripts/system/appreciate/resources/js/jscolor.js b/scripts/system/appreciate/resources/js/jscolor.js new file mode 100644 index 0000000000..9b5e8e6cbc --- /dev/null +++ b/scripts/system/appreciate/resources/js/jscolor.js @@ -0,0 +1,1855 @@ +/** + * jscolor - JavaScript Color Picker + * + * @link http://jscolor.com + * @license For open source use: GPLv3 + * For commercial use: JSColor Commercial License + * @author Jan Odvarko + * @version 2.0.5 + * + * See usage examples at http://jscolor.com/examples/ + */ + + +"use strict"; + + +if (!window.jscolor) { window.jscolor = (function () { + + +var jsc = { + + + register : function () { + jsc.attachDOMReadyEvent(jsc.init); + jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown); + jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart); + jsc.attachEvent(window, 'resize', jsc.onWindowResize); + }, + + + init : function () { + if (jsc.jscolor.lookupClass) { + jsc.jscolor.installByClassName(jsc.jscolor.lookupClass); + } + }, + + + tryInstallOnElements : function (elms, className) { + var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); + + for (var i = 0; i < elms.length; i += 1) { + if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') { + if (jsc.isColorAttrSupported) { + // skip inputs of type 'color' if supported by the browser + continue; + } + } + var m; + if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) { + var targetElm = elms[i]; + var optsStr = null; + + var dataOptions = jsc.getDataAttr(targetElm, 'jscolor'); + if (dataOptions !== null) { + optsStr = dataOptions; + } else if (m[4]) { + optsStr = m[4]; + } + + var opts = {}; + if (optsStr) { + try { + opts = (new Function ('return (' + optsStr + ')'))(); + } catch(eParseError) { + jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr); + } + } + targetElm.jscolor = new jsc.jscolor(targetElm, opts); + } + } + }, + + + isColorAttrSupported : (function () { + var elm = document.createElement('input'); + if (elm.setAttribute) { + elm.setAttribute('type', 'color'); + if (elm.type.toLowerCase() == 'color') { + return true; + } + } + return false; + })(), + + + isCanvasSupported : (function () { + var elm = document.createElement('canvas'); + return !!(elm.getContext && elm.getContext('2d')); + })(), + + + fetchElement : function (mixed) { + return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; + }, + + + isElementType : function (elm, type) { + return elm.nodeName.toLowerCase() === type.toLowerCase(); + }, + + + getDataAttr : function (el, name) { + var attrName = 'data-' + name; + var attrValue = el.getAttribute(attrName); + if (attrValue !== null) { + return attrValue; + } + return null; + }, + + + attachEvent : function (el, evnt, func) { + if (el.addEventListener) { + el.addEventListener(evnt, func, false); + } else if (el.attachEvent) { + el.attachEvent('on' + evnt, func); + } + }, + + + detachEvent : function (el, evnt, func) { + if (el.removeEventListener) { + el.removeEventListener(evnt, func, false); + } else if (el.detachEvent) { + el.detachEvent('on' + evnt, func); + } + }, + + + _attachedGroupEvents : {}, + + + attachGroupEvent : function (groupName, el, evnt, func) { + if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { + jsc._attachedGroupEvents[groupName] = []; + } + jsc._attachedGroupEvents[groupName].push([el, evnt, func]); + jsc.attachEvent(el, evnt, func); + }, + + + detachGroupEvents : function (groupName) { + if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { + for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { + var evt = jsc._attachedGroupEvents[groupName][i]; + jsc.detachEvent(evt[0], evt[1], evt[2]); + } + delete jsc._attachedGroupEvents[groupName]; + } + }, + + + attachDOMReadyEvent : function (func) { + var fired = false; + var fireOnce = function () { + if (!fired) { + fired = true; + func(); + } + }; + + if (document.readyState === 'complete') { + setTimeout(fireOnce, 1); // async + return; + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireOnce, false); + + // Fallback + window.addEventListener('load', fireOnce, false); + + } else if (document.attachEvent) { + // IE + document.attachEvent('onreadystatechange', function () { + if (document.readyState === 'complete') { + document.detachEvent('onreadystatechange', arguments.callee); + fireOnce(); + } + }) + + // Fallback + window.attachEvent('onload', fireOnce); + + // IE7/8 + if (document.documentElement.doScroll && window == window.top) { + var tryScroll = function () { + if (!document.body) { return; } + try { + document.documentElement.doScroll('left'); + fireOnce(); + } catch (e) { + setTimeout(tryScroll, 1); + } + }; + tryScroll(); + } + } + }, + + + warn : function (msg) { + if (window.console && window.console.warn) { + window.console.warn(msg); + } + }, + + + preventDefault : function (e) { + if (e.preventDefault) { e.preventDefault(); } + e.returnValue = false; + }, + + + captureTarget : function (target) { + // IE + if (target.setCapture) { + jsc._capturedTarget = target; + jsc._capturedTarget.setCapture(); + } + }, + + + releaseTarget : function () { + // IE + if (jsc._capturedTarget) { + jsc._capturedTarget.releaseCapture(); + jsc._capturedTarget = null; + } + }, + + + fireEvent : function (el, evnt) { + if (!el) { + return; + } + if (document.createEvent) { + var ev = document.createEvent('HTMLEvents'); + ev.initEvent(evnt, true, true); + el.dispatchEvent(ev); + } else if (document.createEventObject) { + var ev = document.createEventObject(); + el.fireEvent('on' + evnt, ev); + } else if (el['on' + evnt]) { // alternatively use the traditional event model + el['on' + evnt](); + } + }, + + + classNameToList : function (className) { + return className.replace(/^\s+|\s+$/g, '').split(/\s+/); + }, + + + // The className parameter (str) can only contain a single class name + hasClass : function (elm, className) { + if (!className) { + return false; + } + return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); + }, + + + // The className parameter (str) can contain multiple class names separated by whitespace + setClass : function (elm, className) { + var classList = jsc.classNameToList(className); + for (var i = 0; i < classList.length; i += 1) { + if (!jsc.hasClass(elm, classList[i])) { + elm.className += (elm.className ? ' ' : '') + classList[i]; + } + } + }, + + + // The className parameter (str) can contain multiple class names separated by whitespace + unsetClass : function (elm, className) { + var classList = jsc.classNameToList(className); + for (var i = 0; i < classList.length; i += 1) { + var repl = new RegExp( + '^\\s*' + classList[i] + '\\s*|' + + '\\s*' + classList[i] + '\\s*$|' + + '\\s+' + classList[i] + '(\\s+)', + 'g' + ); + elm.className = elm.className.replace(repl, '$1'); + } + }, + + + getStyle : function (elm) { + return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle; + }, + + + setStyle : (function () { + var helper = document.createElement('div'); + var getSupportedProp = function (names) { + for (var i = 0; i < names.length; i += 1) { + if (names[i] in helper.style) { + return names[i]; + } + } + }; + var props = { + borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']), + boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow']) + }; + return function (elm, prop, value) { + switch (prop.toLowerCase()) { + case 'opacity': + var alphaOpacity = Math.round(parseFloat(value) * 100); + elm.style.opacity = value; + elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')'; + break; + default: + elm.style[props[prop]] = value; + break; + } + }; + })(), + + + setBorderRadius : function (elm, value) { + jsc.setStyle(elm, 'borderRadius', value || '0'); + }, + + + setBoxShadow : function (elm, value) { + jsc.setStyle(elm, 'boxShadow', value || 'none'); + }, + + + getElementPos : function (e, relativeToViewport) { + var x=0, y=0; + var rect = e.getBoundingClientRect(); + x = rect.left; + y = rect.top; + if (!relativeToViewport) { + var viewPos = jsc.getViewPos(); + x += viewPos[0]; + y += viewPos[1]; + } + return [x, y]; + }, + + + getElementSize : function (e) { + return [e.offsetWidth, e.offsetHeight]; + }, + + + // get pointer's X/Y coordinates relative to viewport + getAbsPointerPos : function (e) { + if (!e) { e = window.event; } + var x = 0, y = 0; + if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { + // touch devices + x = e.changedTouches[0].clientX; + y = e.changedTouches[0].clientY; + } else if (typeof e.clientX === 'number') { + x = e.clientX; + y = e.clientY; + } + return { x: x, y: y }; + }, + + + // get pointer's X/Y coordinates relative to target element + getRelPointerPos : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + var targetRect = target.getBoundingClientRect(); + + var x = 0, y = 0; + + var clientX = 0, clientY = 0; + if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { + // touch devices + clientX = e.changedTouches[0].clientX; + clientY = e.changedTouches[0].clientY; + } else if (typeof e.clientX === 'number') { + clientX = e.clientX; + clientY = e.clientY; + } + + x = clientX - targetRect.left; + y = clientY - targetRect.top; + return { x: x, y: y }; + }, + + + getViewPos : function () { + var doc = document.documentElement; + return [ + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0), + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) + ]; + }, + + + getViewSize : function () { + var doc = document.documentElement; + return [ + (window.innerWidth || doc.clientWidth), + (window.innerHeight || doc.clientHeight), + ]; + }, + + + redrawPosition : function () { + + if (jsc.picker && jsc.picker.owner) { + var thisObj = jsc.picker.owner; + + var tp, vp; + + if (thisObj.fixed) { + // Fixed elements are positioned relative to viewport, + // therefore we can ignore the scroll offset + tp = jsc.getElementPos(thisObj.targetElement, true); // target pos + vp = [0, 0]; // view pos + } else { + tp = jsc.getElementPos(thisObj.targetElement); // target pos + vp = jsc.getViewPos(); // view pos + } + + var ts = jsc.getElementSize(thisObj.targetElement); // target size + var vs = jsc.getViewSize(); // view size + var ps = jsc.getPickerOuterDims(thisObj); // picker size + var a, b, c; + switch (thisObj.position.toLowerCase()) { + case 'left': a=1; b=0; c=-1; break; + case 'right':a=1; b=0; c=1; break; + case 'top': a=0; b=1; c=-1; break; + default: a=0; b=1; c=1; break; + } + var l = (ts[b]+ps[b])/2; + + // compute picker position + if (!thisObj.smartPosition) { + var pp = [ + tp[a], + tp[b]+ts[b]-l+l*c + ]; + } else { + var pp = [ + -vp[a]+tp[a]+ps[a] > vs[a] ? + (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : + tp[a], + -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? + (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : + (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) + ]; + } + + var x = pp[a]; + var y = pp[b]; + var positionValue = thisObj.fixed ? 'fixed' : 'absolute'; + var contractShadow = + (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) && + (pp[1] + ps[1] < tp[1] + ts[1]); + + jsc._drawPosition(thisObj, x, y, positionValue, contractShadow); + } + }, + + + _drawPosition : function (thisObj, x, y, positionValue, contractShadow) { + var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px + + jsc.picker.wrap.style.position = positionValue; + jsc.picker.wrap.style.left = x + 'px'; + jsc.picker.wrap.style.top = y + 'px'; + + jsc.setBoxShadow( + jsc.picker.boxS, + thisObj.shadow ? + new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) : + null); + }, + + + getPickerDims : function (thisObj) { + var displaySlider = !!jsc.getSliderComponent(thisObj); + var dims = [ + 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width + + (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0), + 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height + + (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0) + ]; + return dims; + }, + + + getPickerOuterDims : function (thisObj) { + var dims = jsc.getPickerDims(thisObj); + return [ + dims[0] + 2 * thisObj.borderWidth, + dims[1] + 2 * thisObj.borderWidth + ]; + }, + + + getPadToSliderPadding : function (thisObj) { + return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness)); + }, + + + getPadYComponent : function (thisObj) { + switch (thisObj.mode.charAt(1).toLowerCase()) { + case 'v': return 'v'; break; + } + return 's'; + }, + + + getSliderComponent : function (thisObj) { + if (thisObj.mode.length > 2) { + switch (thisObj.mode.charAt(2).toLowerCase()) { + case 's': return 's'; break; + case 'v': return 'v'; break; + } + } + return null; + }, + + + onDocumentMouseDown : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + + if (target._jscLinkedInstance) { + if (target._jscLinkedInstance.showOnClick) { + target._jscLinkedInstance.show(); + } + } else if (target._jscControlName) { + jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse'); + } else { + // Mouse is outside the picker controls -> hide the color picker! + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + } + }, + + + onDocumentTouchStart : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + + if (target._jscLinkedInstance) { + if (target._jscLinkedInstance.showOnClick) { + target._jscLinkedInstance.show(); + } + } else if (target._jscControlName) { + jsc.onControlPointerStart(e, target, target._jscControlName, 'touch'); + } else { + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + } + }, + + + onWindowResize : function (e) { + jsc.redrawPosition(); + }, + + + onParentScroll : function (e) { + // hide the picker when one of the parent elements is scrolled + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + }, + + + _pointerMoveEvent : { + mouse: 'mousemove', + touch: 'touchmove' + }, + _pointerEndEvent : { + mouse: 'mouseup', + touch: 'touchend' + }, + + + _pointerOrigin : null, + _capturedTarget : null, + + + onControlPointerStart : function (e, target, controlName, pointerType) { + var thisObj = target._jscInstance; + + jsc.preventDefault(e); + jsc.captureTarget(target); + + var registerDragEvents = function (doc, offset) { + jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType], + jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset)); + jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType], + jsc.onDocumentPointerEnd(e, target, controlName, pointerType)); + }; + + registerDragEvents(document, [0, 0]); + + if (window.parent && window.frameElement) { + var rect = window.frameElement.getBoundingClientRect(); + var ofs = [-rect.left, -rect.top]; + registerDragEvents(window.parent.window.document, ofs); + } + + var abs = jsc.getAbsPointerPos(e); + var rel = jsc.getRelPointerPos(e); + jsc._pointerOrigin = { + x: abs.x - rel.x, + y: abs.y - rel.y + }; + + switch (controlName) { + case 'pad': + // if the slider is at the bottom, move it up + switch (jsc.getSliderComponent(thisObj)) { + case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break; + case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break; + } + jsc.setPad(thisObj, e, 0, 0); + break; + + case 'sld': + jsc.setSld(thisObj, e, 0); + break; + } + + jsc.dispatchFineChange(thisObj); + }, + + + onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { + return function (e) { + var thisObj = target._jscInstance; + switch (controlName) { + case 'pad': + if (!e) { e = window.event; } + jsc.setPad(thisObj, e, offset[0], offset[1]); + jsc.dispatchFineChange(thisObj); + break; + + case 'sld': + if (!e) { e = window.event; } + jsc.setSld(thisObj, e, offset[1]); + jsc.dispatchFineChange(thisObj); + break; + } + } + }, + + + onDocumentPointerEnd : function (e, target, controlName, pointerType) { + return function (e) { + var thisObj = target._jscInstance; + jsc.detachGroupEvents('drag'); + jsc.releaseTarget(); + // Always dispatch changes after detaching outstanding mouse handlers, + // in case some user interaction will occur in user's onchange callback + // that would intrude with current mouse events + jsc.dispatchChange(thisObj); + }; + }, + + + dispatchChange : function (thisObj) { + if (thisObj.valueElement) { + if (jsc.isElementType(thisObj.valueElement, 'input')) { + jsc.fireEvent(thisObj.valueElement, 'change'); + } + } + }, + + + dispatchFineChange : function (thisObj) { + if (thisObj.onFineChange) { + var callback; + if (typeof thisObj.onFineChange === 'string') { + callback = new Function (thisObj.onFineChange); + } else { + callback = thisObj.onFineChange; + } + callback.call(thisObj); + } + }, + + + setPad : function (thisObj, e, ofsX, ofsY) { + var pointerAbs = jsc.getAbsPointerPos(e); + var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth; + var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; + + var xVal = x * (360 / (thisObj.width - 1)); + var yVal = 100 - (y * (100 / (thisObj.height - 1))); + + switch (jsc.getPadYComponent(thisObj)) { + case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break; + case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break; + } + }, + + + setSld : function (thisObj, e, ofsY) { + var pointerAbs = jsc.getAbsPointerPos(e); + var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; + + var yVal = 100 - (y * (100 / (thisObj.height - 1))); + + switch (jsc.getSliderComponent(thisObj)) { + case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break; + case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break; + } + }, + + + _vmlNS : 'jsc_vml_', + _vmlCSS : 'jsc_vml_css_', + _vmlReady : false, + + + initVML : function () { + if (!jsc._vmlReady) { + // init VML namespace + var doc = document; + if (!doc.namespaces[jsc._vmlNS]) { + doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml'); + } + if (!doc.styleSheets[jsc._vmlCSS]) { + var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image']; + var ss = doc.createStyleSheet(); + ss.owningElement.id = jsc._vmlCSS; + for (var i = 0; i < tags.length; i += 1) { + ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);'); + } + } + jsc._vmlReady = true; + } + }, + + + createPalette : function () { + + var paletteObj = { + elm: null, + draw: null + }; + + if (jsc.isCanvasSupported) { + // Canvas implementation for modern browsers + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var drawFunc = function (width, height, type) { + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0); + hGrad.addColorStop(0 / 6, '#F00'); + hGrad.addColorStop(1 / 6, '#FF0'); + hGrad.addColorStop(2 / 6, '#0F0'); + hGrad.addColorStop(3 / 6, '#0FF'); + hGrad.addColorStop(4 / 6, '#00F'); + hGrad.addColorStop(5 / 6, '#F0F'); + hGrad.addColorStop(6 / 6, '#F00'); + + ctx.fillStyle = hGrad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height); + switch (type.toLowerCase()) { + case 's': + vGrad.addColorStop(0, 'rgba(255,255,255,0)'); + vGrad.addColorStop(1, 'rgba(255,255,255,1)'); + break; + case 'v': + vGrad.addColorStop(0, 'rgba(0,0,0,0)'); + vGrad.addColorStop(1, 'rgba(0,0,0,1)'); + break; + } + ctx.fillStyle = vGrad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + paletteObj.elm = canvas; + paletteObj.draw = drawFunc; + + } else { + // VML fallback for IE 7 and 8 + + jsc.initVML(); + + var vmlContainer = document.createElement('div'); + vmlContainer.style.position = 'relative'; + vmlContainer.style.overflow = 'hidden'; + + var hGrad = document.createElement(jsc._vmlNS + ':fill'); + hGrad.type = 'gradient'; + hGrad.method = 'linear'; + hGrad.angle = '90'; + hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0' + + var hRect = document.createElement(jsc._vmlNS + ':rect'); + hRect.style.position = 'absolute'; + hRect.style.left = -1 + 'px'; + hRect.style.top = -1 + 'px'; + hRect.stroked = false; + hRect.appendChild(hGrad); + vmlContainer.appendChild(hRect); + + var vGrad = document.createElement(jsc._vmlNS + ':fill'); + vGrad.type = 'gradient'; + vGrad.method = 'linear'; + vGrad.angle = '180'; + vGrad.opacity = '0'; + + var vRect = document.createElement(jsc._vmlNS + ':rect'); + vRect.style.position = 'absolute'; + vRect.style.left = -1 + 'px'; + vRect.style.top = -1 + 'px'; + vRect.stroked = false; + vRect.appendChild(vGrad); + vmlContainer.appendChild(vRect); + + var drawFunc = function (width, height, type) { + vmlContainer.style.width = width + 'px'; + vmlContainer.style.height = height + 'px'; + + hRect.style.width = + vRect.style.width = + (width + 1) + 'px'; + hRect.style.height = + vRect.style.height = + (height + 1) + 'px'; + + // Colors must be specified during every redraw, otherwise IE won't display + // a full gradient during a subsequential redraw + hGrad.color = '#F00'; + hGrad.color2 = '#F00'; + + switch (type.toLowerCase()) { + case 's': + vGrad.color = vGrad.color2 = '#FFF'; + break; + case 'v': + vGrad.color = vGrad.color2 = '#000'; + break; + } + }; + + paletteObj.elm = vmlContainer; + paletteObj.draw = drawFunc; + } + + return paletteObj; + }, + + + createSliderGradient : function () { + + var sliderObj = { + elm: null, + draw: null + }; + + if (jsc.isCanvasSupported) { + // Canvas implementation for modern browsers + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var drawFunc = function (width, height, color1, color2) { + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); + grad.addColorStop(0, color1); + grad.addColorStop(1, color2); + + ctx.fillStyle = grad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + sliderObj.elm = canvas; + sliderObj.draw = drawFunc; + + } else { + // VML fallback for IE 7 and 8 + + jsc.initVML(); + + var vmlContainer = document.createElement('div'); + vmlContainer.style.position = 'relative'; + vmlContainer.style.overflow = 'hidden'; + + var grad = document.createElement(jsc._vmlNS + ':fill'); + grad.type = 'gradient'; + grad.method = 'linear'; + grad.angle = '180'; + + var rect = document.createElement(jsc._vmlNS + ':rect'); + rect.style.position = 'absolute'; + rect.style.left = -1 + 'px'; + rect.style.top = -1 + 'px'; + rect.stroked = false; + rect.appendChild(grad); + vmlContainer.appendChild(rect); + + var drawFunc = function (width, height, color1, color2) { + vmlContainer.style.width = width + 'px'; + vmlContainer.style.height = height + 'px'; + + rect.style.width = (width + 1) + 'px'; + rect.style.height = (height + 1) + 'px'; + + grad.color = color1; + grad.color2 = color2; + }; + + sliderObj.elm = vmlContainer; + sliderObj.draw = drawFunc; + } + + return sliderObj; + }, + + + leaveValue : 1<<0, + leaveStyle : 1<<1, + leavePad : 1<<2, + leaveSld : 1<<3, + + + BoxShadow : (function () { + var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) { + this.hShadow = hShadow; + this.vShadow = vShadow; + this.blur = blur; + this.spread = spread; + this.color = color; + this.inset = !!inset; + }; + + BoxShadow.prototype.toString = function () { + var vals = [ + Math.round(this.hShadow) + 'px', + Math.round(this.vShadow) + 'px', + Math.round(this.blur) + 'px', + Math.round(this.spread) + 'px', + this.color + ]; + if (this.inset) { + vals.push('inset'); + } + return vals.join(' '); + }; + + return BoxShadow; + })(), + + + // + // Usage: + // var myColor = new jscolor( [, ]) + // + + jscolor : function (targetElement, options) { + + // General options + // + this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB() + this.valueElement = targetElement; // element that will be used to display and input the color code + this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor + this.required = true; // whether the associated text can be left empty + this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace) + this.hash = false; // whether to prefix the HEX color code with # symbol + this.uppercase = true; // whether to show the color code in upper case + this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code) + this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it + this.overwriteImportant = false; // whether to overwrite colors of styleElement using !important + this.minS = 0; // min allowed saturation (0 - 100) + this.maxS = 100; // max allowed saturation (0 - 100) + this.minV = 0; // min allowed value (brightness) (0 - 100) + this.maxV = 100; // max allowed value (brightness) (0 - 100) + + // Accessing the picked color + // + this.hsv = [0, 0, 100]; // read-only [0-360, 0-100, 0-100] + this.rgb = [255, 255, 255]; // read-only [0-255, 0-255, 0-255] + + // Color Picker options + // + this.width = 181; // width of color palette (in px) + this.height = 101; // height of color palette (in px) + this.showOnClick = true; // whether to display the color picker when user clicks on its target element + this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls + this.position = 'bottom'; // left | right | top | bottom - position relative to the target element + this.smartPosition = true; // automatically change picker position when there is not enough space for it + this.sliderSize = 16; // px + this.crossSize = 8; // px + this.closable = false; // whether to display the Close button + this.closeText = 'Close'; + this.buttonColor = '#000000'; // CSS color + this.buttonHeight = 18; // px + this.padding = 12; // px + this.backgroundColor = '#FFFFFF'; // CSS color + this.borderWidth = 1; // px + this.borderColor = '#BBBBBB'; // CSS color + this.borderRadius = 8; // px + this.insetWidth = 1; // px + this.insetColor = '#BBBBBB'; // CSS color + this.shadow = true; // whether to display shadow + this.shadowBlur = 15; // px + this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color + this.pointerColor = '#4C4C4C'; // px + this.pointerBorderColor = '#FFFFFF'; // px + this.pointerBorderWidth = 1; // px + this.pointerThickness = 2; // px + this.zIndex = 1000; + this.container = null; // where to append the color picker (BODY element by default) + + + for (var opt in options) { + if (options.hasOwnProperty(opt)) { + this[opt] = options[opt]; + } + } + + + this.hide = function () { + if (isPickerOwner()) { + detachPicker(); + } + }; + + + this.show = function () { + drawPicker(); + }; + + + this.redraw = function () { + if (isPickerOwner()) { + drawPicker(); + } + }; + + + this.importColor = function () { + if (!this.valueElement) { + this.exportColor(); + } else { + if (jsc.isElementType(this.valueElement, 'input')) { + if (!this.refine) { + if (!this.fromString(this.valueElement.value, jsc.leaveValue)) { + if (this.styleElement) { + this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; + this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; + this.styleElement.style.color = this.styleElement._jscOrigStyle.color; + } + this.exportColor(jsc.leaveValue | jsc.leaveStyle); + } + } else if (!this.required && /^\s*$/.test(this.valueElement.value)) { + this.valueElement.value = ''; + if (this.styleElement) { + this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; + this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; + this.styleElement.style.color = this.styleElement._jscOrigStyle.color; + } + this.exportColor(jsc.leaveValue | jsc.leaveStyle); + + } else if (this.fromString(this.valueElement.value)) { + // managed to import color successfully from the value -> OK, don't do anything + } else { + this.exportColor(); + } + } else { + // not an input element -> doesn't have any value + this.exportColor(); + } + } + }; + + + this.exportColor = function (flags) { + if (!(flags & jsc.leaveValue) && this.valueElement) { + var value = this.toString(); + if (this.uppercase) { value = value.toUpperCase(); } + if (this.hash) { value = '#' + value; } + + // if (jsc.isElementType(this.valueElement, 'input')) { + // this.valueElement.value = value; + // } else { + // this.valueElement.innerHTML = value; + // } + } + if (!(flags & jsc.leaveStyle)) { + if (this.styleElement) { + var bgColor = '#' + this.toString(); + var fgColor = this.isLight() ? '#000' : '#FFF'; + + this.styleElement.style.backgroundImage = 'none'; + this.styleElement.style.backgroundColor = bgColor; + this.styleElement.style.color = fgColor; + + if (this.overwriteImportant) { + this.styleElement.setAttribute('style', + 'background: ' + bgColor + ' !important; ' + + 'color: ' + fgColor + ' !important;' + ); + } + } + } + if (!(flags & jsc.leavePad) && isPickerOwner()) { + redrawPad(); + } + if (!(flags & jsc.leaveSld) && isPickerOwner()) { + redrawSld(); + } + }; + + + // h: 0-360 + // s: 0-100 + // v: 0-100 + // + this.fromHSV = function (h, s, v, flags) { // null = don't change + if (h !== null) { + if (isNaN(h)) { return false; } + h = Math.max(0, Math.min(360, h)); + } + if (s !== null) { + if (isNaN(s)) { return false; } + s = Math.max(0, Math.min(100, this.maxS, s), this.minS); + } + if (v !== null) { + if (isNaN(v)) { return false; } + v = Math.max(0, Math.min(100, this.maxV, v), this.minV); + } + + this.rgb = HSV_RGB( + h===null ? this.hsv[0] : (this.hsv[0]=h), + s===null ? this.hsv[1] : (this.hsv[1]=s), + v===null ? this.hsv[2] : (this.hsv[2]=v) + ); + + this.exportColor(flags); + }; + + + // r: 0-255 + // g: 0-255 + // b: 0-255 + // + this.fromRGB = function (r, g, b, flags) { // null = don't change + if (r !== null) { + if (isNaN(r)) { return false; } + r = Math.max(0, Math.min(255, r)); + } + if (g !== null) { + if (isNaN(g)) { return false; } + g = Math.max(0, Math.min(255, g)); + } + if (b !== null) { + if (isNaN(b)) { return false; } + b = Math.max(0, Math.min(255, b)); + } + + var hsv = RGB_HSV( + r===null ? this.rgb[0] : r, + g===null ? this.rgb[1] : g, + b===null ? this.rgb[2] : b + ); + if (hsv[0] !== null) { + this.hsv[0] = Math.max(0, Math.min(360, hsv[0])); + } + if (hsv[2] !== 0) { + this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1])); + } + this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2])); + + // update RGB according to final HSV, as some values might be trimmed + var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); + this.rgb[0] = rgb[0]; + this.rgb[1] = rgb[1]; + this.rgb[2] = rgb[2]; + + this.exportColor(flags); + }; + + + this.fromString = function (str, flags) { + var m; + if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) { + // HEX notation + // + + if (m[1].length === 6) { + // 6-char notation + this.fromRGB( + parseInt(m[1].substr(0,2),16), + parseInt(m[1].substr(2,2),16), + parseInt(m[1].substr(4,2),16), + flags + ); + } else { + // 3-char notation + this.fromRGB( + parseInt(m[1].charAt(0) + m[1].charAt(0),16), + parseInt(m[1].charAt(1) + m[1].charAt(1),16), + parseInt(m[1].charAt(2) + m[1].charAt(2),16), + flags + ); + } + return true; + + } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) { + var params = m[1].split(','); + var re = /^\s*(\d*)(\.\d+)?\s*$/; + var mR, mG, mB; + if ( + params.length >= 3 && + (mR = params[0].match(re)) && + (mG = params[1].match(re)) && + (mB = params[2].match(re)) + ) { + var r = parseFloat((mR[1] || '0') + (mR[2] || '')); + var g = parseFloat((mG[1] || '0') + (mG[2] || '')); + var b = parseFloat((mB[1] || '0') + (mB[2] || '')); + this.fromRGB(r, g, b, flags); + return true; + } + } + return false; + }; + + + this.toString = function () { + return ( + (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) + + (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) + + (0x100 | Math.round(this.rgb[2])).toString(16).substr(1) + ); + }; + + + this.toHEXString = function () { + return '#' + this.toString().toUpperCase(); + }; + + + this.toRGBString = function () { + return ('rgb(' + + Math.round(this.rgb[0]) + ',' + + Math.round(this.rgb[1]) + ',' + + Math.round(this.rgb[2]) + ')' + ); + }; + + + this.isLight = function () { + return ( + 0.213 * this.rgb[0] + + 0.715 * this.rgb[1] + + 0.072 * this.rgb[2] > + 255 / 2 + ); + }; + + + this._processParentElementsInDOM = function () { + if (this._linkedElementsProcessed) { return; } + this._linkedElementsProcessed = true; + + var elm = this.targetElement; + do { + // If the target element or one of its parent nodes has fixed position, + // then use fixed positioning instead + // + // Note: In Firefox, getComputedStyle returns null in a hidden iframe, + // that's why we need to check if the returned style object is non-empty + var currStyle = jsc.getStyle(elm); + if (currStyle && currStyle.position.toLowerCase() === 'fixed') { + this.fixed = true; + } + + if (elm !== this.targetElement) { + // Ensure to attach onParentScroll only once to each parent element + // (multiple targetElements can share the same parent nodes) + // + // Note: It's not just offsetParents that can be scrollable, + // that's why we loop through all parent nodes + if (!elm._jscEventsAttached) { + jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); + elm._jscEventsAttached = true; + } + } + } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body')); + }; + + + // r: 0-255 + // g: 0-255 + // b: 0-255 + // + // returns: [ 0-360, 0-100, 0-100 ] + // + function RGB_HSV (r, g, b) { + r /= 255; + g /= 255; + b /= 255; + var n = Math.min(Math.min(r,g),b); + var v = Math.max(Math.max(r,g),b); + var m = v - n; + if (m === 0) { return [ null, 0, 100 * v ]; } + var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); + return [ + 60 * (h===6?0:h), + 100 * (m/v), + 100 * v + ]; + } + + + // h: 0-360 + // s: 0-100 + // v: 0-100 + // + // returns: [ 0-255, 0-255, 0-255 ] + // + function HSV_RGB (h, s, v) { + var u = 255 * (v / 100); + + if (h === null) { + return [ u, u, u ]; + } + + h /= 60; + s /= 100; + + var i = Math.floor(h); + var f = i%2 ? h-i : 1-(h-i); + var m = u * (1 - s); + var n = u * (1 - s * f); + switch (i) { + case 6: + case 0: return [u,n,m]; + case 1: return [n,u,m]; + case 2: return [m,u,n]; + case 3: return [m,n,u]; + case 4: return [n,m,u]; + case 5: return [u,m,n]; + } + } + + + function detachPicker () { + jsc.unsetClass(THIS.targetElement, THIS.activeClass); + jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); + delete jsc.picker.owner; + } + + + function drawPicker () { + + // At this point, when drawing the picker, we know what the parent elements are + // and we can do all related DOM operations, such as registering events on them + // or checking their positioning + THIS._processParentElementsInDOM(); + + if (!jsc.picker) { + jsc.picker = { + owner: null, + wrap : document.createElement('div'), + box : document.createElement('div'), + boxS : document.createElement('div'), // shadow area + boxB : document.createElement('div'), // border + pad : document.createElement('div'), + padB : document.createElement('div'), // border + padM : document.createElement('div'), // mouse/touch area + padPal : jsc.createPalette(), + cross : document.createElement('div'), + crossBY : document.createElement('div'), // border Y + crossBX : document.createElement('div'), // border X + crossLY : document.createElement('div'), // line Y + crossLX : document.createElement('div'), // line X + sld : document.createElement('div'), + sldB : document.createElement('div'), // border + sldM : document.createElement('div'), // mouse/touch area + sldGrad : jsc.createSliderGradient(), + sldPtrS : document.createElement('div'), // slider pointer spacer + sldPtrIB : document.createElement('div'), // slider pointer inner border + sldPtrMB : document.createElement('div'), // slider pointer middle border + sldPtrOB : document.createElement('div'), // slider pointer outer border + btn : document.createElement('div'), + btnT : document.createElement('span') // text + }; + + jsc.picker.pad.appendChild(jsc.picker.padPal.elm); + jsc.picker.padB.appendChild(jsc.picker.pad); + jsc.picker.cross.appendChild(jsc.picker.crossBY); + jsc.picker.cross.appendChild(jsc.picker.crossBX); + jsc.picker.cross.appendChild(jsc.picker.crossLY); + jsc.picker.cross.appendChild(jsc.picker.crossLX); + jsc.picker.padB.appendChild(jsc.picker.cross); + jsc.picker.box.appendChild(jsc.picker.padB); + jsc.picker.box.appendChild(jsc.picker.padM); + + jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm); + jsc.picker.sldB.appendChild(jsc.picker.sld); + jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB); + jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB); + jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB); + jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS); + jsc.picker.box.appendChild(jsc.picker.sldB); + jsc.picker.box.appendChild(jsc.picker.sldM); + + jsc.picker.btn.appendChild(jsc.picker.btnT); + jsc.picker.box.appendChild(jsc.picker.btn); + + jsc.picker.boxB.appendChild(jsc.picker.box); + jsc.picker.wrap.appendChild(jsc.picker.boxS); + jsc.picker.wrap.appendChild(jsc.picker.boxB); + } + + var p = jsc.picker; + + var displaySlider = !!jsc.getSliderComponent(THIS); + var dims = jsc.getPickerDims(THIS); + var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); + var padToSliderPadding = jsc.getPadToSliderPadding(THIS); + var borderRadius = Math.min( + THIS.borderRadius, + Math.round(THIS.padding * Math.PI)); // px + var padCursor = 'crosshair'; + + // wrap + p.wrap.style.clear = 'both'; + p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px'; + p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px'; + p.wrap.style.zIndex = THIS.zIndex; + + // picker + p.box.style.width = dims[0] + 'px'; + p.box.style.height = dims[1] + 'px'; + + p.boxS.style.position = 'absolute'; + p.boxS.style.left = '0'; + p.boxS.style.top = '0'; + p.boxS.style.width = '100%'; + p.boxS.style.height = '100%'; + jsc.setBorderRadius(p.boxS, borderRadius + 'px'); + + // picker border + p.boxB.style.position = 'relative'; + p.boxB.style.border = THIS.borderWidth + 'px solid'; + p.boxB.style.borderColor = THIS.borderColor; + p.boxB.style.background = THIS.backgroundColor; + jsc.setBorderRadius(p.boxB, borderRadius + 'px'); + + // IE hack: + // If the element is transparent, IE will trigger the event on the elements under it, + // e.g. on Canvas or on elements with border + p.padM.style.background = + p.sldM.style.background = + '#FFF'; + jsc.setStyle(p.padM, 'opacity', '0'); + jsc.setStyle(p.sldM, 'opacity', '0'); + + // pad + p.pad.style.position = 'relative'; + p.pad.style.width = THIS.width + 'px'; + p.pad.style.height = THIS.height + 'px'; + + // pad palettes (HSV and HVS) + p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS)); + + // pad border + p.padB.style.position = 'absolute'; + p.padB.style.left = THIS.padding + 'px'; + p.padB.style.top = THIS.padding + 'px'; + p.padB.style.border = THIS.insetWidth + 'px solid'; + p.padB.style.borderColor = THIS.insetColor; + + // pad mouse area + p.padM._jscInstance = THIS; + p.padM._jscControlName = 'pad'; + p.padM.style.position = 'absolute'; + p.padM.style.left = '0'; + p.padM.style.top = '0'; + p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px'; + p.padM.style.height = dims[1] + 'px'; + p.padM.style.cursor = padCursor; + + // pad cross + p.cross.style.position = 'absolute'; + p.cross.style.left = + p.cross.style.top = + '0'; + p.cross.style.width = + p.cross.style.height = + crossOuterSize + 'px'; + + // pad cross border Y and X + p.crossBY.style.position = + p.crossBX.style.position = + 'absolute'; + p.crossBY.style.background = + p.crossBX.style.background = + THIS.pointerBorderColor; + p.crossBY.style.width = + p.crossBX.style.height = + (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; + p.crossBY.style.height = + p.crossBX.style.width = + crossOuterSize + 'px'; + p.crossBY.style.left = + p.crossBX.style.top = + (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px'; + p.crossBY.style.top = + p.crossBX.style.left = + '0'; + + // pad cross line Y and X + p.crossLY.style.position = + p.crossLX.style.position = + 'absolute'; + p.crossLY.style.background = + p.crossLX.style.background = + THIS.pointerColor; + p.crossLY.style.height = + p.crossLX.style.width = + (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px'; + p.crossLY.style.width = + p.crossLX.style.height = + THIS.pointerThickness + 'px'; + p.crossLY.style.left = + p.crossLX.style.top = + (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px'; + p.crossLY.style.top = + p.crossLX.style.left = + THIS.pointerBorderWidth + 'px'; + + // slider + p.sld.style.overflow = 'hidden'; + p.sld.style.width = THIS.sliderSize + 'px'; + p.sld.style.height = THIS.height + 'px'; + + // slider gradient + p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000'); + + // slider border + p.sldB.style.display = displaySlider ? 'block' : 'none'; + p.sldB.style.position = 'absolute'; + p.sldB.style.right = THIS.padding + 'px'; + p.sldB.style.top = THIS.padding + 'px'; + p.sldB.style.border = THIS.insetWidth + 'px solid'; + p.sldB.style.borderColor = THIS.insetColor; + + // slider mouse area + p.sldM._jscInstance = THIS; + p.sldM._jscControlName = 'sld'; + p.sldM.style.display = displaySlider ? 'block' : 'none'; + p.sldM.style.position = 'absolute'; + p.sldM.style.right = '0'; + p.sldM.style.top = '0'; + p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px'; + p.sldM.style.height = dims[1] + 'px'; + p.sldM.style.cursor = 'default'; + + // slider pointer inner and outer border + p.sldPtrIB.style.border = + p.sldPtrOB.style.border = + THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; + + // slider pointer outer border + p.sldPtrOB.style.position = 'absolute'; + p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; + p.sldPtrOB.style.top = '0'; + + // slider pointer middle border + p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; + + // slider pointer spacer + p.sldPtrS.style.width = THIS.sliderSize + 'px'; + p.sldPtrS.style.height = sliderPtrSpace + 'px'; + + // the Close button + function setBtnBorder () { + var insetColors = THIS.insetColor.split(/\s+/); + var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; + p.btn.style.borderColor = outsetColor; + } + p.btn.style.display = THIS.closable ? 'block' : 'none'; + p.btn.style.position = 'absolute'; + p.btn.style.left = THIS.padding + 'px'; + p.btn.style.bottom = THIS.padding + 'px'; + p.btn.style.padding = '0 15px'; + p.btn.style.height = THIS.buttonHeight + 'px'; + p.btn.style.border = THIS.insetWidth + 'px solid'; + setBtnBorder(); + p.btn.style.color = THIS.buttonColor; + p.btn.style.font = '12px sans-serif'; + p.btn.style.textAlign = 'center'; + try { + p.btn.style.cursor = 'pointer'; + } catch(eOldIE) { + p.btn.style.cursor = 'hand'; + } + p.btn.onmousedown = function () { + THIS.hide(); + }; + p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; + p.btnT.innerHTML = ''; + p.btnT.appendChild(document.createTextNode(THIS.closeText)); + + // place pointers + redrawPad(); + redrawSld(); + + // If we are changing the owner without first closing the picker, + // make sure to first deal with the old owner + if (jsc.picker.owner && jsc.picker.owner !== THIS) { + jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass); + } + + // Set the new picker owner + jsc.picker.owner = THIS; + + // The redrawPosition() method needs picker.owner to be set, that's why we call it here, + // after setting the owner + if (jsc.isElementType(container, 'body')) { + jsc.redrawPosition(); + } else { + jsc._drawPosition(THIS, 0, 0, 'relative', false); + } + + if (p.wrap.parentNode != container) { + container.appendChild(p.wrap); + } + + jsc.setClass(THIS.targetElement, THIS.activeClass); + } + + + function redrawPad () { + // redraw the pad pointer + switch (jsc.getPadYComponent(THIS)) { + case 's': var yComponent = 1; break; + case 'v': var yComponent = 2; break; + } + var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1)); + var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); + var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); + var ofs = -Math.floor(crossOuterSize / 2); + jsc.picker.cross.style.left = (x + ofs) + 'px'; + jsc.picker.cross.style.top = (y + ofs) + 'px'; + + // redraw the slider + switch (jsc.getSliderComponent(THIS)) { + case 's': + var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]); + var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]); + var color1 = 'rgb(' + + Math.round(rgb1[0]) + ',' + + Math.round(rgb1[1]) + ',' + + Math.round(rgb1[2]) + ')'; + var color2 = 'rgb(' + + Math.round(rgb2[0]) + ',' + + Math.round(rgb2[1]) + ',' + + Math.round(rgb2[2]) + ')'; + jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); + break; + case 'v': + var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100); + var color1 = 'rgb(' + + Math.round(rgb[0]) + ',' + + Math.round(rgb[1]) + ',' + + Math.round(rgb[2]) + ')'; + var color2 = '#000'; + jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); + break; + } + } + + + function redrawSld () { + var sldComponent = jsc.getSliderComponent(THIS); + if (sldComponent) { + // redraw the slider pointer + switch (sldComponent) { + case 's': var yComponent = 1; break; + case 'v': var yComponent = 2; break; + } + var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); + jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px'; + } + } + + + function isPickerOwner () { + return jsc.picker && jsc.picker.owner === THIS; + } + + + function blurValue () { + THIS.importColor(); + } + + + // Find the target element + if (typeof targetElement === 'string') { + var id = targetElement; + var elm = document.getElementById(id); + if (elm) { + this.targetElement = elm; + } else { + jsc.warn('Could not find target element with ID \'' + id + '\''); + } + } else if (targetElement) { + this.targetElement = targetElement; + } else { + jsc.warn('Invalid target element: \'' + targetElement + '\''); + } + + if (this.targetElement._jscLinkedInstance) { + jsc.warn('Cannot link jscolor twice to the same element. Skipping.'); + return; + } + this.targetElement._jscLinkedInstance = this; + + // Find the value element + this.valueElement = jsc.fetchElement(this.valueElement); + // Find the style element + this.styleElement = jsc.fetchElement(this.styleElement); + + var THIS = this; + var container = + this.container ? + jsc.fetchElement(this.container) : + document.getElementsByTagName('body')[0]; + var sliderPtrSpace = 3; // px + + // For BUTTON elements it's important to stop them from sending the form when clicked + // (e.g. in Safari) + if (jsc.isElementType(this.targetElement, 'button')) { + if (this.targetElement.onclick) { + var origCallback = this.targetElement.onclick; + this.targetElement.onclick = function (evt) { + origCallback.call(this, evt); + return false; + }; + } else { + this.targetElement.onclick = function () { return false; }; + } + } + + /* + var elm = this.targetElement; + do { + // If the target element or one of its offsetParents has fixed position, + // then use fixed positioning instead + // + // Note: In Firefox, getComputedStyle returns null in a hidden iframe, + // that's why we need to check if the returned style object is non-empty + var currStyle = jsc.getStyle(elm); + if (currStyle && currStyle.position.toLowerCase() === 'fixed') { + this.fixed = true; + } + + if (elm !== this.targetElement) { + // attach onParentScroll so that we can recompute the picker position + // when one of the offsetParents is scrolled + if (!elm._jscEventsAttached) { + jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); + elm._jscEventsAttached = true; + } + } + } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body')); + */ + + // valueElement + if (this.valueElement) { + if (jsc.isElementType(this.valueElement, 'input')) { + var updateField = function () { + THIS.fromString(THIS.valueElement.value, jsc.leaveValue); + jsc.dispatchFineChange(THIS); + }; + jsc.attachEvent(this.valueElement, 'keyup', updateField); + jsc.attachEvent(this.valueElement, 'input', updateField); + jsc.attachEvent(this.valueElement, 'blur', blurValue); + this.valueElement.setAttribute('autocomplete', 'off'); + } + } + + // styleElement + if (this.styleElement) { + this.styleElement._jscOrigStyle = { + backgroundImage : this.styleElement.style.backgroundImage, + backgroundColor : this.styleElement.style.backgroundColor, + color : this.styleElement.style.color + }; + } + + if (this.value) { + // Try to set the color from the .value option and if unsuccessful, + // export the current color + this.fromString(this.value) || this.exportColor(); + } else { + this.importColor(); + } + } + +}; + + +//================================ +// Public properties and methods +//================================ + + +// By default, search for all elements with class="jscolor" and install a color picker on them. +// +// You can change what class name will be looked for by setting the property jscolor.lookupClass +// anywhere in your HTML document. To completely disable the automatic lookup, set it to null. +// +jsc.jscolor.lookupClass = 'jscolor'; + + +jsc.jscolor.installByClassName = function (className) { + var inputElms = document.getElementsByTagName('input'); + var buttonElms = document.getElementsByTagName('button'); + + jsc.tryInstallOnElements(inputElms, className); + jsc.tryInstallOnElements(buttonElms, className); +}; + + +jsc.register(); + + +return jsc.jscolor; + + +})(); } diff --git a/scripts/system/appreciate/resources/sounds/claps/01.wav b/scripts/system/appreciate/resources/sounds/claps/01.wav new file mode 100644 index 0000000000..b4baa2888a Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/01.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/02.wav b/scripts/system/appreciate/resources/sounds/claps/02.wav new file mode 100644 index 0000000000..21cbf3fa4a Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/02.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/03.wav b/scripts/system/appreciate/resources/sounds/claps/03.wav new file mode 100644 index 0000000000..bc5d35760d Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/03.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/04.wav b/scripts/system/appreciate/resources/sounds/claps/04.wav new file mode 100644 index 0000000000..cacaf44b6e Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/04.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/05.wav b/scripts/system/appreciate/resources/sounds/claps/05.wav new file mode 100644 index 0000000000..ffb688b5b2 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/05.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/06.wav b/scripts/system/appreciate/resources/sounds/claps/06.wav new file mode 100644 index 0000000000..81716f26be Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/06.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/07.wav b/scripts/system/appreciate/resources/sounds/claps/07.wav new file mode 100644 index 0000000000..4c20ceba2c Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/07.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/08.wav b/scripts/system/appreciate/resources/sounds/claps/08.wav new file mode 100644 index 0000000000..c39e4b1dfc Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/08.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/09.wav b/scripts/system/appreciate/resources/sounds/claps/09.wav new file mode 100644 index 0000000000..791433c024 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/09.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/10.wav b/scripts/system/appreciate/resources/sounds/claps/10.wav new file mode 100644 index 0000000000..b359475b51 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/10.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/11.wav b/scripts/system/appreciate/resources/sounds/claps/11.wav new file mode 100644 index 0000000000..40b8415725 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/11.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/12.wav b/scripts/system/appreciate/resources/sounds/claps/12.wav new file mode 100644 index 0000000000..68656f7c21 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/12.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/13.wav b/scripts/system/appreciate/resources/sounds/claps/13.wav new file mode 100644 index 0000000000..e6a716ccdd Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/13.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/14.wav b/scripts/system/appreciate/resources/sounds/claps/14.wav new file mode 100644 index 0000000000..a5e8b5ad49 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/14.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/15.wav b/scripts/system/appreciate/resources/sounds/claps/15.wav new file mode 100644 index 0000000000..7f3072e3e0 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/15.wav differ diff --git a/scripts/system/appreciate/resources/sounds/claps/16.wav b/scripts/system/appreciate/resources/sounds/claps/16.wav new file mode 100644 index 0000000000..f76bee0429 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/claps/16.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/01.wav b/scripts/system/appreciate/resources/sounds/whistles/01.wav new file mode 100644 index 0000000000..d8b27da990 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/01.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/02.wav b/scripts/system/appreciate/resources/sounds/whistles/02.wav new file mode 100644 index 0000000000..0c0874087e Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/02.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/03.wav b/scripts/system/appreciate/resources/sounds/whistles/03.wav new file mode 100644 index 0000000000..5a78406601 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/03.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/04.wav b/scripts/system/appreciate/resources/sounds/whistles/04.wav new file mode 100644 index 0000000000..eb4cb40675 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/04.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/05.wav b/scripts/system/appreciate/resources/sounds/whistles/05.wav new file mode 100644 index 0000000000..f261e29aac Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/05.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/06.wav b/scripts/system/appreciate/resources/sounds/whistles/06.wav new file mode 100644 index 0000000000..7194c02d9b Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/06.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/07.wav b/scripts/system/appreciate/resources/sounds/whistles/07.wav new file mode 100644 index 0000000000..43d65f3af7 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/07.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/08.wav b/scripts/system/appreciate/resources/sounds/whistles/08.wav new file mode 100644 index 0000000000..612bc5b5fa Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/08.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/09.wav b/scripts/system/appreciate/resources/sounds/whistles/09.wav new file mode 100644 index 0000000000..d900d59f03 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/09.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/10.wav b/scripts/system/appreciate/resources/sounds/whistles/10.wav new file mode 100644 index 0000000000..3c9b7fa9dc Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/10.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/11.wav b/scripts/system/appreciate/resources/sounds/whistles/11.wav new file mode 100644 index 0000000000..8315b21cca Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/11.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/12.wav b/scripts/system/appreciate/resources/sounds/whistles/12.wav new file mode 100644 index 0000000000..c787336068 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/12.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/13.wav b/scripts/system/appreciate/resources/sounds/whistles/13.wav new file mode 100644 index 0000000000..fd303d27fc Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/13.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/14.wav b/scripts/system/appreciate/resources/sounds/whistles/14.wav new file mode 100644 index 0000000000..eda14dc291 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/14.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/15.wav b/scripts/system/appreciate/resources/sounds/whistles/15.wav new file mode 100644 index 0000000000..f86e9e2cb2 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/15.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/16.wav b/scripts/system/appreciate/resources/sounds/whistles/16.wav new file mode 100644 index 0000000000..22ff07a650 Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/16.wav differ diff --git a/scripts/system/appreciate/resources/sounds/whistles/17.wav b/scripts/system/appreciate/resources/sounds/whistles/17.wav new file mode 100644 index 0000000000..498d6df56a Binary files /dev/null and b/scripts/system/appreciate/resources/sounds/whistles/17.wav differ diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 5709b19efe..8453a7d8d3 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -2,6 +2,9 @@ // inEditMode.js // +// Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -80,7 +83,9 @@ Script.include("/~/system/libraries/utils.js"); Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "selectEntity", entityID: this.selectedTarget.objectID, - hand: hand + hand: hand, + surfaceNormal: this.selectedTarget.surfaceNormal, + intersection: this.selectedTarget.intersection })); } else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) { Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 478793ab78..21b41bd354 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -38,9 +38,8 @@ "textEffectThickness": { "tooltip": "The magnitude of the text effect." }, - "textBillboardMode": { - "tooltip": "If enabled, determines how the entity will face the camera.", - "jsPropertyName": "billboardMode" + "textAlignment": { + "tooltip": "How the text is aligned within its left and right bounds." }, "topMargin": { "tooltip": "The top margin, in meters." @@ -223,10 +222,6 @@ "subImage": { "tooltip": "The area of the image that is displayed." }, - "imageBillboardMode": { - "tooltip": "If enabled, determines how the entity will face the camera.", - "jsPropertyName": "billboardMode" - }, "keepAspectRatio": { "tooltip": "If enabled, the image will maintain its original aspect ratio." }, @@ -236,10 +231,6 @@ "dpi": { "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." }, - "webBillboardMode": { - "tooltip": "If enabled, determines how the entity will face the camera.", - "jsPropertyName": "billboardMode" - }, "inputMode": { "tooltip": "The user input mode to use." }, @@ -595,12 +586,18 @@ "primitiveMode": { "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"." }, + "billboardMode": { + "tooltip": "Determines if and how the entity will face the camera.", + }, "renderWithZones": { "tooltip": "If set, this entity will only render when your avatar is inside of a zone in this list." }, "groupCulled": { "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all." }, + "useOriginalPivot": { + "tooltip": "If false, the model will be centered based on its content, ignoring any offset in the model itself. If true, the model will respect its original offset." + }, "webColor": { "tooltip": "The tint of the web entity." }, @@ -616,6 +613,9 @@ "scriptURL": { "tooltip": "The URL of a script to inject into the web page." }, + "userAgent": { + "tooltip": "The user agent that the web entity will use when visiting web pages." + }, "alignToGrid": { "tooltip": "Used to align entities to the grid, or floor of the environment.", "skipJSProperty": true diff --git a/scripts/system/create/assets/images/icon-material.svg b/scripts/system/create/assets/images/icon-material.svg new file mode 100644 index 0000000000..470fed5dbd --- /dev/null +++ b/scripts/system/create/assets/images/icon-material.svg @@ -0,0 +1,5 @@ + + + + +Layer 1 \ No newline at end of file diff --git a/scripts/system/create/assets/images/icon-particles.svg b/scripts/system/create/assets/images/icon-particles.svg index 5e0105d7cd..2b3f365889 100644 --- a/scripts/system/create/assets/images/icon-particles.svg +++ b/scripts/system/create/assets/images/icon-particles.svg @@ -1,29 +1,14 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + +Layer 1 \ No newline at end of file diff --git a/scripts/system/create/assets/images/icon-point-light.svg b/scripts/system/create/assets/images/icon-point-light.svg index 896c35b63b..0f2e91d55b 100644 --- a/scripts/system/create/assets/images/icon-point-light.svg +++ b/scripts/system/create/assets/images/icon-point-light.svg @@ -1,57 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/system/create/assets/images/icon-spot-light.svg b/scripts/system/create/assets/images/icon-spot-light.svg index ac2f87bb27..d8e196ffd7 100644 --- a/scripts/system/create/assets/images/icon-spot-light.svg +++ b/scripts/system/create/assets/images/icon-spot-light.svg @@ -1,37 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +Layer 1 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/system/create/assets/images/icon-zone.svg b/scripts/system/create/assets/images/icon-zone.svg index 41aeac4951..bfd8e07160 100644 --- a/scripts/system/create/assets/images/icon-zone.svg +++ b/scripts/system/create/assets/images/icon-zone.svg @@ -1,73 +1,5 @@ - - - -image/svg+xmlLayer 1 \ No newline at end of file diff --git a/scripts/system/create/audioFeedback/audioFeedback.js b/scripts/system/create/audioFeedback/audioFeedback.js index f1900d5716..881afddfaf 100644 --- a/scripts/system/create/audioFeedback/audioFeedback.js +++ b/scripts/system/create/audioFeedback/audioFeedback.js @@ -12,9 +12,10 @@ audioFeedback = (function() { var that = {}; - + var confirmationSound = SoundCache.getSound(Script.resolvePath("./sounds/confirmation.mp3")); var rejectionSound = SoundCache.getSound(Script.resolvePath("./sounds/rejection.mp3")); + var actionSound = SoundCache.getSound(Script.resolvePath("./sounds/action.mp3")); that.confirmation = function() { //Play a confirmation sound var injector = Audio.playSound(confirmationSound, { @@ -25,8 +26,15 @@ audioFeedback = (function() { that.rejection = function() { //Play a rejection sound var injector = Audio.playSound(rejectionSound, { - "volume": 0.3, - "localOnly": true + "volume": 0.3, + "localOnly": true + }); + } + + that.action = function() { //Play an action sound + var injector = Audio.playSound(actionSound, { + "volume": 0.3, + "localOnly": true }); } diff --git a/scripts/system/create/audioFeedback/sounds/action.mp3 b/scripts/system/create/audioFeedback/sounds/action.mp3 new file mode 100644 index 0000000000..fd004847b0 Binary files /dev/null and b/scripts/system/create/audioFeedback/sounds/action.mp3 differ diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index fc3d4fc10f..9bd0147002 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -87,25 +87,30 @@ var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg") var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); +var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg"); -var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { - var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); - if (properties.type === 'Light') { - return { - url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, - }; - } else if (properties.type === 'Zone') { - return { - url: ZONE_URL, +var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material"], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]); + if (properties.type === "Light") { + return { + imageURL: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL }; + } else if (properties.type === "Zone") { + return { imageURL: ZONE_URL }; + } else if (properties.type === "Material") { + if (properties.parentID !== Uuid.NULL && properties.name !== "MATERIAL_" + entityShapeVisualizerSessionName) { + return { imageURL: MATERIAL_URL }; + } else { + return { imageURL: "" }; + } } else { - return { - url: PARTICLE_SYSTEM_URL, - }; + return { imageURL: PARTICLE_SYSTEM_URL }; } }); var hmdMultiSelectMode = false; +var expectingRotateAsClickedSurface = false; +var keepSelectedOnNextClick = false; var cameraManager = new CameraManager(); @@ -154,8 +159,7 @@ var MENU_CREATE_SEPARATOR = "Create Application"; var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Preferences"; var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; -var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; -var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; +var MENU_SHOW_ICONS_IN_CREATE_MODE = "Show Icons in Create Mode"; var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; @@ -737,6 +741,7 @@ var toolBar = (function () { grabbable: result.grabbable }, dynamic: dynamic, + useOriginalPivot: result.useOriginalPivot }); } } @@ -1041,8 +1046,7 @@ var toolBar = (function () { // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); } - entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); - Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); }; initialize(); @@ -1105,25 +1109,50 @@ function findClickedEntity(event) { } var result; - - if (iconResult.intersects) { - result = iconResult; - } else if (entityResult.intersects) { - result = entityResult; + if (expectingRotateAsClickedSurface) { + if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected, or the selection is locked."); + expectingRotateAsClickedSurface = false; + } else { + //Rotate Selection according the Surface Normal + var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(entityResult.surfaceNormal, -1)); + selectionDisplay.rotateSelection(normalRotation); + //Translate Selection according the clicked Surface + var distanceFromSurface; + if (selectionDisplay.getSpaceMode() === "world"){ + distanceFromSurface = SelectionManager.worldDimensions.z / 2; + } else { + distanceFromSurface = SelectionManager.localDimensions.z / 2; + } + selectionDisplay.moveSelection(Vec3.sum(entityResult.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); + selectionManager._update(false, this); + pushCommandForSelections(); + expectingRotateAsClickedSurface = false; + audioFeedback.action(); + } + keepSelectedOnNextClick = true; + return null; } else { - return null; - } + if (iconResult.intersects) { + result = iconResult; + } else if (entityResult.intersects) { + result = entityResult; + } else { + return null; + } - if (!result.accurate) { - return null; - } + if (!result.accurate) { + return null; + } - var foundEntity = result.entityID; - return { - pickRay: pickRay, - entityID: foundEntity, - intersection: result.intersection - }; + var foundEntity = result.entityID; + return { + pickRay: pickRay, + entityID: foundEntity, + intersection: result.intersection + }; + } } // Handles selections on overlays while in edit mode by querying entities from @@ -1294,7 +1323,10 @@ function mouseClickEvent(event) { if (result === null || result === undefined) { if (!event.isShifted) { - selectionManager.clearSelections(this); + if (!keepSelectedOnNextClick) { + selectionManager.clearSelections(this); + } + keepSelectedOnNextClick = false; } return; } @@ -1465,22 +1497,15 @@ function setupModelMenus() { }); Menu.addMenuItem({ menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + menuItemName: MENU_SHOW_ICONS_IN_CREATE_MODE, afterItem: MENU_EASE_ON_FOCUS, isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE, - afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, - isCheckable: true, - isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false" - }); Menu.addMenuItem({ menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS, - afterItem: MENU_SHOW_ZONES_IN_EDIT_MODE + afterItem: MENU_SHOW_ICONS_IN_CREATE_MODE }); Entities.setLightsArePickable(false); @@ -1497,8 +1522,7 @@ function cleanupModelMenus() { Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS); Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT); Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ICONS_IN_CREATE_MODE); Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE); Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS); Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); @@ -1511,8 +1535,7 @@ Script.scriptEnding.connect(function () { toolBar.setActive(false); Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); - Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); @@ -1869,10 +1892,8 @@ function handleMenuEvent(menuItem) { undoHistory.undo(); } else if (menuItem === "Redo") { undoHistory.redo(); - } else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) { - entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); - } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { - Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + } else if (menuItem === MENU_SHOW_ICONS_IN_CREATE_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); } else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) { @@ -2062,6 +2083,26 @@ function gridToAvatarKey(value) { alignGridToAvatar(); } } +function rotateAsNextClickedSurfaceKey(value) { + if (value === 0) { // on release + rotateAsNextClickedSurface(); + } +} +function quickRotate90xKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("X"); + } +} +function quickRotate90yKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("Y"); + } +} +function quickRotate90zKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("Z"); + } +} function recursiveAdd(newParentID, parentData) { if (parentData.children !== undefined) { var children = parentData.children; @@ -2829,6 +2870,10 @@ mapping.from([Controller.Hardware.Keyboard.J]).to(gridKey); mapping.from([Controller.Hardware.Keyboard.G]).to(viewGridKey); mapping.from([Controller.Hardware.Keyboard.H]).to(snapKey); mapping.from([Controller.Hardware.Keyboard.K]).to(gridToAvatarKey); +mapping.from([Controller.Hardware.Keyboard["0"]]).to(rotateAsNextClickedSurfaceKey); +mapping.from([Controller.Hardware.Keyboard["7"]]).to(quickRotate90xKey); +mapping.from([Controller.Hardware.Keyboard["8"]]).to(quickRotate90yKey); +mapping.from([Controller.Hardware.Keyboard["9"]]).to(quickRotate90zKey); mapping.from([Controller.Hardware.Keyboard.X]) .when([Controller.Hardware.Keyboard.Control]) .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); @@ -2877,6 +2922,14 @@ keyUpEventFromUIWindow = function(keyUpEvent) { snapKey(pressedValue); } else if (keyUpEvent.keyCodeString === "K") { gridToAvatarKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "0") { + rotateAsNextClickedSurfaceKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "7") { + quickRotate90xKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "8") { + quickRotate90yKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "9") { + quickRotate90zKey(pressedValue); } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { selectionManager.cutSelectedEntities(); } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { @@ -3025,4 +3078,14 @@ function toggleGridVisibility() { } } +function rotateAsNextClickedSurface() { + if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected, or the selection is locked."); + expectingRotateAsClickedSurface = false; + } else { + expectingRotateAsClickedSurface = true; + } +} + }()); // END LOCAL_SCOPE diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index a4d4decedb..5119d7d3da 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -383,6 +383,14 @@ EntityListTool = function(shouldUseEditTabletApp) { SelectionManager.selectTopFamily(); } else if (data.type === 'teleportToEntity') { SelectionManager.teleportToEntity(); + } else if (data.type === 'rotateAsTheNextClickedSurface') { + rotateAsNextClickedSurface(); + } else if (data.type === 'quickRotate90x') { + selectionDisplay.rotate90degreeSelection("X"); + } else if (data.type === 'quickRotate90y') { + selectionDisplay.rotate90degreeSelection("Y"); + } else if (data.type === 'quickRotate90z') { + selectionDisplay.rotate90degreeSelection("Z"); } else if (data.type === 'moveEntitySelectionToAvatar') { SelectionManager.moveEntitiesSelectionToAvatar(); } else if (data.type === 'loadConfigSetting') { diff --git a/scripts/system/create/entityList/html/entityList.html b/scripts/system/create/entityList/html/entityList.html index 9817f9ddf9..e054ca121b 100644 --- a/scripts/system/create/entityList/html/entityList.html +++ b/scripts/system/create/entityList/html/entityList.html @@ -24,6 +24,12 @@ +
+ + + + +
@@ -32,9 +38,7 @@
- - - +
@@ -58,8 +62,7 @@
- - + D
@@ -130,12 +133,6 @@
- - + + @@ -221,6 +230,40 @@ +
+ + + + + + + +
- - - -