Unified · Split
| @@ -1,4 +1,4 @@ | |||
| 1 | ###Dependencies | 1 | ### Dependencies |
| 2 | 2 | ||
| 3 | * [cmake](https://cmake.org/download/) ~> 3.3.2 | 3 | * [cmake](https://cmake.org/download/) ~> 3.3.2 |
| 4 | * [Qt](https://www.qt.io/download-open-source) ~> 5.6.2 | 4 | * [Qt](https://www.qt.io/download-open-source) ~> 5.6.2 |
| @@ -6,7 +6,7 @@ · 1 unmodified line | |||
| 6 | * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. | 6 | * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. |
| 7 | * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) | 7 | * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) |
| 8 | 8 | ||
| 9 | ####CMake External Project Dependencies | 9 | #### CMake External Project Dependencies |
| 10 | 10 | ||
| 11 | * [boostconfig](https://github.com/boostorg/config) ~> 1.58 | 11 | * [boostconfig](https://github.com/boostorg/config) ~> 1.58 |
| 12 | * [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83 | 12 | * [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83 |
| @@ -30,16 +30,19 @@ These are not placed in your normal build tree when doing an out of source build · 17 unmodified lines | |||
| 30 | 30 | ||
| 31 | If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. | 31 | If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. |
| 32 | 32 | ||
| 33 | ###OS Specific Build Guides | 33 | ### OS Specific Build Guides |
| 34 | |||
| 34 | * [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. | 35 | * [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. |
| 35 | * [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. | 36 | * [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. |
| 36 | * [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows. | 37 | * [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows. |
| 37 | * [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android | 38 | * [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android |
| 38 | 39 | ||
| 39 | ###CMake | 40 | ### CMake |
| 41 | |||
| 40 | Hifi uses CMake to generate build files and project files for your platform. | 42 | Hifi uses CMake to generate build files and project files for your platform. |
| 41 | 43 | ||
| 42 | ####Qt | 44 | #### Qt |
| 45 | |||
| 43 | In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation. | 46 | In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation. |
| 44 | 47 | ||
| 45 | This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). | 48 | This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). |
| @@ -50,7 +53,8 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e · 4 unmodified lines | |||
| 50 | export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake | 53 | export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake |
| 51 | export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake | 54 | export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake |
| 52 | 55 | ||
| 53 | ####Generating build files | 56 | #### Generating build files |
| 57 | |||
| 54 | Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean. | 58 | Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean. |
| 55 | 59 | ||
| 56 | mkdir build | 60 | mkdir build |
| @@ -59,14 +63,15 @@ Create a build directory in the root of your checkout and then run the CMake bui · 2 unmodified lines | |||
| 59 | 63 | ||
| 60 | If cmake gives you the same error message repeatedly after the build fails (e.g. you had a typo in the QT_CMAKE_PREFIX_PATH that you fixed but the `.cmake` files still cannot be found), try removing `CMakeCache.txt`. | 64 | If cmake gives you the same error message repeatedly after the build fails (e.g. you had a typo in the QT_CMAKE_PREFIX_PATH that you fixed but the `.cmake` files still cannot be found), try removing `CMakeCache.txt`. |
| 61 | 65 | ||
| 62 | ####Variables | 66 | #### Variables |
| 67 | |||
| 63 | Any variables that need to be set for CMake to find dependencies can be set as ENV variables in your shell profile, or passed directly to CMake with a `-D` flag appended to the `cmake ..` command. | 68 | Any variables that need to be set for CMake to find dependencies can be set as ENV variables in your shell profile, or passed directly to CMake with a `-D` flag appended to the `cmake ..` command. |
| 64 | 69 | ||
| 65 | For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: | 70 | For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: |
| 66 | 71 | ||
| 67 | cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake | 72 | cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake |
| 68 | 73 | ||
| 69 | ####Finding Dependencies | 74 | #### Finding Dependencies |
| 70 | 75 | ||
| 71 | The following applies for dependencies we do not grab via CMake ExternalProject (OpenSSL is an example), or for dependencies you have opted not to grab as a CMake ExternalProject (via -DUSE_LOCAL_$NAME=0). The list of dependencies we grab by default as external projects can be found in [the CMake External Project Dependencies section](#cmake-external-project-dependencies). | 76 | The following applies for dependencies we do not grab via CMake ExternalProject (OpenSSL is an example), or for dependencies you have opted not to grab as a CMake ExternalProject (via -DUSE_LOCAL_$NAME=0). The list of dependencies we grab by default as external projects can be found in [the CMake External Project Dependencies section](#cmake-external-project-dependencies). |
| 72 | 77 | ||
| @@ -78,8 +83,8 @@ In the examples below the variable $NAME would be replaced by the name of the de · 5 unmodified lines | |||
| 78 | * $NAME_ROOT_DIR - set this variable in your ENV | 83 | * $NAME_ROOT_DIR - set this variable in your ENV |
| 79 | * HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name' | 84 | * HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name' |
| 80 | 85 | ||
| 81 | ###Optional Components | 86 | ### Optional Components |
| 82 | 87 | ||
| 83 | ####Devices | 88 | #### Devices |
| 84 | 89 | ||
| 85 | You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device. | 90 | You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device. |
| @@ -1,6 +1,6 @@ | |||
| 1 | Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file. | 1 | Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file. |
| 2 | 2 | ||
| 3 | ###Android Dependencies | 3 | ### Android Dependencies |
| 4 | 4 | ||
| 5 | You will need the following tools to build our Android targets. | 5 | You will need the following tools to build our Android targets. |
| 6 | 6 | ||
| @@ -17,23 +17,23 @@ You will need the following tools to build our Android targets. · 10 unmodified lines | |||
| 17 | 17 | ||
| 18 | You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine. | 18 | You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine. |
| 19 | 19 | ||
| 20 | ####Scribe | 20 | #### Scribe |
| 21 | 21 | ||
| 22 | High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable. | 22 | High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable. |
| 23 | 23 | ||
| 24 | CMake will fatally error if it does not find the scribe executable while using the android toolchain. | 24 | CMake will fatally error if it does not find the scribe executable while using the android toolchain. |
| 25 | 25 | ||
| 26 | ####Optional Components | 26 | #### Optional Components |
| 27 | 27 | ||
| 28 | * [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2 | 28 | * [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2 |
| 29 | 29 | ||
| 30 | ####ANDROID_LIB_DIR | 30 | #### ANDROID_LIB_DIR |
| 31 | 31 | ||
| 32 | Since you won't be installing Android dependencies to system paths on your development machine, CMake will need a little help tracking down your Android dependencies. | 32 | Since you won't be installing Android dependencies to system paths on your development machine, CMake will need a little help tracking down your Android dependencies. |
| 33 | 33 | ||
| 34 | This is most easily accomplished by installing all Android dependencies in the same folder. You can place this folder wherever you like on your machine. In this build guide and across our CMakeLists files this folder is referred to as `ANDROID_LIB_DIR`. You can set `ANDROID_LIB_DIR` in your environment or by passing when you run CMake. | 34 | This is most easily accomplished by installing all Android dependencies in the same folder. You can place this folder wherever you like on your machine. In this build guide and across our CMakeLists files this folder is referred to as `ANDROID_LIB_DIR`. You can set `ANDROID_LIB_DIR` in your environment or by passing when you run CMake. |
| 35 | 35 | ||
| 36 | ####Qt | 36 | #### Qt |
| 37 | 37 | ||
| 38 | Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation. | 38 | Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation. |
| 39 | 39 | ||
| @@ -41,7 +41,7 @@ The component required for the Android build is the `Android armv7` component. · 1 unmodified line | |||
| 41 | 41 | ||
| 42 | If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`. | 42 | If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`. |
| 43 | 43 | ||
| 44 | ####OpenSSL | 44 | #### OpenSSL |
| 45 | 45 | ||
| 46 | Cross-compilation of OpenSSL has been tested from an OS X machine running 10.10 compiling OpenSSL 1.0.2. It is likely that the steps below will work for other OpenSSL versions than 1.0.2. | 46 | Cross-compilation of OpenSSL has been tested from an OS X machine running 10.10 compiling OpenSSL 1.0.2. It is likely that the steps below will work for other OpenSSL versions than 1.0.2. |
| 47 | 47 | ||
| @@ -76,7 +76,7 @@ This should generate libcrypto and libssl in the root of the OpenSSL directory. · 28 unmodified lines | |||
| 76 | 76 | ||
| 77 | If you have been building other components it is possible that the OpenSSL compile will fail based on the values other cross-compilations (tbb, bullet) have set. Ensure that you are in a new terminal window to avoid compilation errors from previously set environment variables. | 77 | If you have been building other components it is possible that the OpenSSL compile will fail based on the values other cross-compilations (tbb, bullet) have set. Ensure that you are in a new terminal window to avoid compilation errors from previously set environment variables. |
| 78 | 78 | ||
| 79 | ####Oculus Mobile SDK | 79 | #### Oculus Mobile SDK |
| 80 | 80 | ||
| 81 | The Oculus Mobile SDK is optional, for Gear VR support. It is not required to compile gvr-interface. | 81 | The Oculus Mobile SDK is optional, for Gear VR support. It is not required to compile gvr-interface. |
| 82 | 82 | ||
| @@ -91,7 +91,7 @@ ndk-build · 8 unmodified lines | |||
| 91 | 91 | ||
| 92 | This will create the liboculus.a archive that our FindLibOVR module will look for when cmake is run. | 92 | This will create the liboculus.a archive that our FindLibOVR module will look for when cmake is run. |
| 93 | 93 | ||
| 94 | #####Hybrid testing | 94 | ##### Hybrid testing |
| 95 | 95 | ||
| 96 | Currently the 'vr_dual' mode that would allow us to run a hybrid app has limited support in the Oculus Mobile SDK. The best way to have an application we can launch without having to connect to the GearVR is to put the Gear VR Service into developer mode. This stops Oculus Home from taking over the device when it is plugged into the Gear VR headset, and allows the application to be launched from the Applications page. | 96 | Currently the 'vr_dual' mode that would allow us to run a hybrid app has limited support in the Oculus Mobile SDK. The best way to have an application we can launch without having to connect to the GearVR is to put the Gear VR Service into developer mode. This stops Oculus Home from taking over the device when it is plugged into the Gear VR headset, and allows the application to be launched from the Applications page. |
| 97 | 97 | ||
| @@ -99,7 +99,7 @@ To put the Gear VR Service into developer mode you need an application with an O · 1 unmodified line | |||
| 99 | 99 | ||
| 100 | Once the application is on your device, go to `Settings->Application Manager->Gear VR Service->Manage Storage`. Tap on `VR Service Version` six times. It will scan your device to verify that you have an osig file in an application on your device, and then it will let you enable Developer mode. | 100 | Once the application is on your device, go to `Settings->Application Manager->Gear VR Service->Manage Storage`. Tap on `VR Service Version` six times. It will scan your device to verify that you have an osig file in an application on your device, and then it will let you enable Developer mode. |
| 101 | 101 | ||
| 102 | ###CMake | 102 | ### CMake |
| 103 | 103 | ||
| 104 | We use CMake to generate the makefiles that compile and deploy the Android APKs to your device. In order to create Makefiles for the Android targets, CMake requires that some environment variables are set, and that other variables are passed to it when it is run. | 104 | We use CMake to generate the makefiles that compile and deploy the Android APKs to your device. In order to create Makefiles for the Android targets, CMake requires that some environment variables are set, and that other variables are passed to it when it is run. |
| 105 | 105 | ||
| @@ -1,6 +1,7 @@ | |||
| 1 | Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file. | 1 | Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file. |
| 2 | 2 | ||
| 3 | ###Qt5 Dependencies | 3 | ### Qt5 Dependencies |
| 4 | |||
| 4 | Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: | 5 | Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: |
| 5 | 6 | ||
| 6 | libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev | 7 | libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev |
| @@ -1,12 +1,13 @@ | |||
| 1 | Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file. | 1 | Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file. |
| 2 | 2 | ||
| 3 | ###Homebrew | 3 | ### Homebrew |
| 4 | |||
| 4 | [Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. | 5 | [Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. |
| 5 | 6 | ||
| 6 | brew tap homebrew/versions | 7 | brew tap homebrew/versions |
| 7 | brew install cmake openssl | 8 | brew install cmake openssl |
| 8 | 9 | ||
| 9 | ###OpenSSL | 10 | ### OpenSSL |
| 10 | 11 | ||
| 11 | Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations. | 12 | Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations. |
| 12 | For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: | 13 | For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: |
| @@ -15,7 +16,8 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: · 2 unmodified lines | |||
| 15 | 16 | ||
| 16 | Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. | 17 | Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. |
| 17 | 18 | ||
| 18 | ###Qt | 19 | ### Qt |
| 20 | |||
| 19 | Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg). | 21 | Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg). |
| 20 | 22 | ||
| 21 | Keep the default components checked when going through the installer. | 23 | Keep the default components checked when going through the installer. |
| @@ -23,7 +25,8 @@ Keep the default components checked when going through the installer. · 1 unmodified line | |||
| 23 | Once Qt is installed, you need to manually configure the following: | 25 | Once Qt is installed, you need to manually configure the following: |
| 24 | * Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory. | 26 | * Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory. |
| 25 | 27 | ||
| 26 | ###Xcode | 28 | ### Xcode |
| 29 | |||
| 27 | If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. | 30 | If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. |
| 28 | 31 | ||
| 29 | cmake .. -GXcode | 32 | cmake .. -GXcode |
| @@ -1,62 +1,66 @@ | |||
| 1 | This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit. | 1 | This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit. |
| 2 | 2 | ||
| 3 | ###Step 1. Installing Visual Studio 2013 | 3 | ## Building High Fidelity |
| 4 | |||
| 5 | ### Step 1. Installing Visual Studio 2013 | ||
| 4 | 6 | ||
| 5 | If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer. | 7 | If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer. |
| 6 | 8 | ||
| 7 | Note: Newer versions of Visual Studio are not yet compatible. | 9 | Note: Newer versions of Visual Studio are not yet compatible. |
| 8 | 10 | ||
| 9 | ###Step 2. Installing CMake | 11 | ### Step 2. Installing CMake |
| 10 | 12 | ||
| 11 | Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. | 13 | Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. |
| 12 | 14 | ||
| 13 | ###Step 3. Installing Qt | 15 | ### Step 3. Installing Qt |
| 14 | 16 | ||
| 15 | Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe). | 17 | Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe). |
| 16 | 18 | ||
| 17 | Keep the default components checked when going through the installer. | 19 | Keep the default components checked when going through the installer. |
| 18 | 20 | ||
| 19 | ###Step 4. Setting Qt Environment Variable | 21 | ### Step 4. Setting Qt Environment Variable |
| 20 | 22 | ||
| 21 | Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search). | 23 | Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search). |
| 22 | * Set "Variable name": QT_CMAKE_PREFIX_PATH | 24 | * Set "Variable name": `QT_CMAKE_PREFIX_PATH` |
| 23 | * Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake` | 25 | * Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake` |
| 24 | 26 | ||
| 25 | ###Step 5. Installing OpenSSL | 27 | ### Step 5. Installing OpenSSL |
| 26 | 28 | ||
| 27 | Download and install the [Win64 OpenSSL v1.0.2k Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2k.exe). | 29 | Download and install the [Win64 OpenSSL v1.0.2L Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe). |
| 28 | 30 | ||
| 29 | ###Step 6. Running CMake to Generate Build Files | 31 | ### Step 6. Running CMake to Generate Build Files |
| 30 | 32 | ||
| 31 | Run Command Prompt from Start and run the following commands: | 33 | Run Command Prompt from Start and run the following commands: |
| 32 | cd "%HIFI_DIR%" | 34 | ```` |
| 33 | mkdir build | 35 | cd "%HIFI_DIR%" |
| 34 | cd build | 36 | mkdir build |
| 35 | cmake .. -G "Visual Studio 12 Win64" | 37 | cd build |
| 38 | cmake .. -G "Visual Studio 12 Win64" | ||
| 39 | ```` | ||
| 36 | 40 | ||
| 37 | Where %HIFI_DIR% is the directory for the highfidelity repository. | 41 | Where `%HIFI_DIR%` is the directory for the highfidelity repository. |
| 38 | 42 | ||
| 39 | ###Step 7. Making a Build | 43 | ### Step 7. Making a Build |
| 40 | 44 | ||
| 41 | Open '%HIFI_DIR%\build\hifi.sln' using Visual Studio. | 45 | Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio. |
| 42 | 46 | ||
| 43 | Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance. | 47 | Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance. |
| 44 | 48 | ||
| 45 | Run Build > Build Solution. | 49 | Run `Build > Build Solution`. |
| 46 | 50 | ||
| 47 | ###Step 8. Testing Interface | 51 | ### Step 8. Testing Interface |
| 48 | 52 | ||
| 49 | Create another environment variable (see Step #4) | 53 | Create another environment variable (see Step #4) |
| 50 | * Set "Variable name": _NO_DEBUG_HEAP | 54 | * Set "Variable name": `_NO_DEBUG_HEAP` |
| 51 | * Set "Variable value": 1 | 55 | * Set "Variable value": `1` |
| 52 | 56 | ||
| 53 | In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run Debug > Start Debugging. | 57 | In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run `Debug > Start Debugging`. |
| 54 | 58 | ||
| 55 | Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow. | 59 | Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow. |
| 56 | 60 | ||
| 57 | Note: You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Release\interface.exe | 61 | Note: You can also run Interface by launching it from command line or File Explorer from `%HIFI_DIR%\build\interface\Release\interface.exe` |
| 58 | 62 | ||
| 59 | ###Troubleshooting | 63 | ## Troubleshooting |
| 60 | 64 | ||
| 61 | For any problems after Step #6, first try this: | 65 | For any problems after Step #6, first try this: |
| 62 | * Delete your locally cloned copy of the highfidelity repository | 66 | * Delete your locally cloned copy of the highfidelity repository |
| @@ -64,18 +68,18 @@ For any problems after Step #6, first try this: · 1 unmodified line | |||
| 64 | * Redownload the [repository](https://github.com/highfidelity/hifi) | 68 | * Redownload the [repository](https://github.com/highfidelity/hifi) |
| 65 | * Restart directions from Step #6 | 69 | * Restart directions from Step #6 |
| 66 | 70 | ||
| 67 | ####CMake gives you the same error message repeatedly after the build fails | 71 | #### CMake gives you the same error message repeatedly after the build fails |
| 68 | 72 | ||
| 69 | Remove `CMakeCache.txt` found in the '%HIFI_DIR%\build' directory | 73 | Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. |
| 70 | 74 | ||
| 71 | ####nmake cannot be found | 75 | #### nmake cannot be found |
| 72 | 76 | ||
| 73 | Make sure nmake.exe is located at the following path: | 77 | Make sure nmake.exe is located at the following path: |
| 78 | |||
| 74 | C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin | 79 | C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin |
| 75 | 80 | ||
| 76 | If not, add the directory where nmake is located to the PATH environment variable. | 81 | If not, add the directory where nmake is located to the PATH environment variable. |
| 77 | 82 | ||
| 78 | ####Qt is throwing an error | 83 | #### Qt is throwing an error |
| 79 | |||
| 80 | Make sure you have the correct version (5.6.2) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly. | ||
| 81 | 84 | ||
| 85 | Make sure you have the correct version (5.6.2) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. | ||
| @@ -2,15 +2,15 @@ Follow the [build guide](BUILD.md) to figure out how to build High Fidelity for · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | During generation, CMake should produce an `install` target and a `package` target. | 3 | During generation, CMake should produce an `install` target and a `package` target. |
| 4 | 4 | ||
| 5 | ###Install | 5 | ### Install |
| 6 | 6 | ||
| 7 | The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`. | 7 | The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`. |
| 8 | 8 | ||
| 9 | ###Packaging | 9 | ### Packaging |
| 10 | 10 | ||
| 11 | To produce an installer, run the `package` target. | 11 | To produce an installer, run the `package` target. |
| 12 | 12 | ||
| 13 | ####Windows | 13 | #### Windows |
| 14 | 14 | ||
| 15 | To produce an executable installer on Windows, the following are required: | 15 | To produce an executable installer on Windows, the following are required: |
| 16 | 16 | ||
| @@ -20,6 +20,6 @@ To produce an executable installer on Windows, the following are required: · 3 unmodified lines | |||
| 20 | 20 | ||
| 21 | Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. | 21 | Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. |
| 22 | 22 | ||
| 23 | ####OS X | 23 | #### OS X |
| 24 | 24 | ||
| 25 | Run the `package` target to create an Apple Disk Image (.dmg). | 25 | Run the `package` target to create an Apple Disk Image (.dmg). |
| @@ -166,11 +166,7 @@ void Agent::run() { · 165 unmodified lines | |||
| 166 | 166 | ||
| 167 | // Setup MessagesClient | 167 | // Setup MessagesClient |
| 168 | auto messagesClient = DependencyManager::set<MessagesClient>(); | 168 | auto messagesClient = DependencyManager::set<MessagesClient>(); |
| 169 | QThread* messagesThread = new QThread; | 169 | messagesClient->startThread(); |
| 170 | messagesThread->setObjectName("Messages Client Thread"); | ||
| 171 | messagesClient->moveToThread(messagesThread); | ||
| 172 | connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); | ||
| 173 | messagesThread->start(); | ||
| 174 | 170 | ||
| 175 | // make sure we hear about connected nodes so we can grab an ATP script if a request is pending | 171 | // make sure we hear about connected nodes so we can grab an ATP script if a request is pending |
| 176 | connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated); | 172 | connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated); |
| @@ -410,6 +406,7 @@ void Agent::executeScript() { · 233 unmodified lines | |||
| 410 | bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened | 406 | bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened |
| 411 | bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed | 407 | bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed |
| 412 | _audioGateOpen = audioGateOpen; | 408 | _audioGateOpen = audioGateOpen; |
| 409 | Q_UNUSED(openedInLastBlock); | ||
| 413 | 410 | ||
| 414 | // the codec must be flushed to silence before sending silent packets, | 411 | // the codec must be flushed to silence before sending silent packets, |
| 415 | // so delay the transition to silent packets by one packet after becoming silent. | 412 | // so delay the transition to silent packets by one packet after becoming silent. |
| @@ -69,17 +69,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri · 68 unmodified lines | |||
| 69 | DependencyManager::set<ResourceScriptingInterface>(); | 69 | DependencyManager::set<ResourceScriptingInterface>(); |
| 70 | DependencyManager::set<UserActivityLoggerScriptingInterface>(); | 70 | DependencyManager::set<UserActivityLoggerScriptingInterface>(); |
| 71 | 71 | ||
| 72 | // setup a thread for the NodeList and its PacketReceiver | 72 | nodeList->startThread(); |
| 73 | QThread* nodeThread = new QThread(this); | ||
| 74 | nodeThread->setObjectName("NodeList Thread"); | ||
| 75 | nodeThread->start(); | ||
| 76 | |||
| 77 | // make sure the node thread is given highest priority | ||
| 78 | nodeThread->setPriority(QThread::TimeCriticalPriority); | ||
| 79 | |||
| 80 | // put the NodeList on the node thread | ||
| 81 | nodeList->moveToThread(nodeThread); | ||
| 82 | |||
| 83 | // set the logging target to the the CHILD_TARGET_NAME | 73 | // set the logging target to the the CHILD_TARGET_NAME |
| 84 | LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); | 74 | LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); |
| 85 | 75 | ||
| @@ -166,14 +156,8 @@ void AssignmentClient::stopAssignmentClient() { · 80 unmodified lines | |||
| 166 | } | 156 | } |
| 167 | 157 | ||
| 168 | AssignmentClient::~AssignmentClient() { | 158 | AssignmentClient::~AssignmentClient() { |
| 169 | QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); | ||
| 170 | |||
| 171 | // remove the NodeList from the DependencyManager | 159 | // remove the NodeList from the DependencyManager |
| 172 | DependencyManager::destroy<NodeList>(); | 160 | DependencyManager::destroy<NodeList>(); |
| 173 | |||
| 174 | // ask the node thread to quit and wait until it is done | ||
| 175 | nodeThread->quit(); | ||
| 176 | nodeThread->wait(); | ||
| 177 | } | 161 | } |
| 178 | 162 | ||
| 179 | void AssignmentClient::aboutToQuit() { | 163 | void AssignmentClient::aboutToQuit() { |
| @@ -16,10 +16,10 @@ · 15 unmodified lines | |||
| 16 | #include <mutex> | 16 | #include <mutex> |
| 17 | #include <vector> | 17 | #include <vector> |
| 18 | 18 | ||
| 19 | #include <tbb/concurrent_queue.h> | ||
| 20 | |||
| 21 | #include <QThread> | 19 | #include <QThread> |
| 22 | 20 | ||
| 21 | #include <TBBHelpers.h> | ||
| 22 | |||
| 23 | #include "AudioMixerSlave.h" | 23 | #include "AudioMixerSlave.h" |
| 24 | 24 | ||
| 25 | class AudioMixerSlavePool; | 25 | class AudioMixerSlavePool; |
| @@ -16,10 +16,9 @@ · 15 unmodified lines | |||
| 16 | #include <mutex> | 16 | #include <mutex> |
| 17 | #include <vector> | 17 | #include <vector> |
| 18 | 18 | ||
| 19 | #include <tbb/concurrent_queue.h> | ||
| 20 | |||
| 21 | #include <QThread> | 19 | #include <QThread> |
| 22 | 20 | ||
| 21 | #include <TBBHelpers.h> | ||
| 23 | #include <NodeList.h> | 22 | #include <NodeList.h> |
| 24 | 23 | ||
| 25 | #include "AvatarMixerSlave.h" | 24 | #include "AvatarMixerSlave.h" |
| @@ -236,11 +236,7 @@ void EntityScriptServer::run() { · 235 unmodified lines | |||
| 236 | 236 | ||
| 237 | // Setup MessagesClient | 237 | // Setup MessagesClient |
| 238 | auto messagesClient = DependencyManager::set<MessagesClient>(); | 238 | auto messagesClient = DependencyManager::set<MessagesClient>(); |
| 239 | QThread* messagesThread = new QThread; | 239 | messagesClient->startThread(); |
| 240 | messagesThread->setObjectName("Messages Client Thread"); | ||
| 241 | messagesClient->moveToThread(messagesThread); | ||
| 242 | connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); | ||
| 243 | messagesThread->start(); | ||
| 244 | 240 | ||
| 245 | DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); | 241 | DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); |
| 246 | connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); | 242 | connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); |
| @@ -8,8 +8,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) · 7 unmodified lines | |||
| 8 | if (WIN32) | 8 | if (WIN32) |
| 9 | ExternalProject_Add( | 9 | ExternalProject_Add( |
| 10 | ${EXTERNAL_NAME} | 10 | ${EXTERNAL_NAME} |
| 11 | URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.zip | 11 | URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.hifi.zip |
| 12 | URL_MD5 3ea6eeadbcc69071acf9c49ba565760e | 12 | URL_MD5 10da01cf601f88f6dc12a6bc13c89136 |
| 13 | CONFIGURE_COMMAND "" | 13 | CONFIGURE_COMMAND "" |
| 14 | BUILD_COMMAND "" | 14 | BUILD_COMMAND "" |
| 15 | INSTALL_COMMAND "" | 15 | INSTALL_COMMAND "" |
| @@ -29,8 +29,8 @@ else () · 13 unmodified lines | |||
| 29 | 29 | ||
| 30 | ExternalProject_Add( | 30 | ExternalProject_Add( |
| 31 | ${EXTERNAL_NAME} | 31 | ${EXTERNAL_NAME} |
| 32 | URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.zip | 32 | URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip |
| 33 | URL_MD5 81b8fa6a9ee3f986088eb6e2215d6a57 | 33 | URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb |
| 34 | CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> | 34 | CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> |
| 35 | LOG_DOWNLOAD 1 | 35 | LOG_DOWNLOAD 1 |
| 36 | LOG_CONFIGURE 1 | 36 | LOG_CONFIGURE 1 |
| @@ -21,7 +21,7 @@ · 20 unmodified lines | |||
| 21 | </p> | 21 | </p> |
| 22 | <p> | 22 | <p> |
| 23 | If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br> | 23 | If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br> |
| 24 | <pre>C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz</pre> | 24 | <pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre> |
| 25 | <pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre> | 25 | <pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre> |
| 26 | <pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre> | 26 | <pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre> |
| 27 | </p> | 27 | </p> |
| @@ -37,20 +37,12 @@ RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString · 36 unmodified lines | |||
| 37 | DependencyManager::set<AvatarHashMap>(); | 37 | DependencyManager::set<AvatarHashMap>(); |
| 38 | 38 | ||
| 39 | // get our audio client setup on its own thread | 39 | // get our audio client setup on its own thread |
| 40 | QThread* audioThread = new QThread(); | ||
| 41 | auto audioClient = DependencyManager::set<AudioClient>(); | 40 | auto audioClient = DependencyManager::set<AudioClient>(); |
| 42 | |||
| 43 | audioClient->setPositionGetter(getPositionForAudio); | 41 | audioClient->setPositionGetter(getPositionForAudio); |
| 44 | audioClient->setOrientationGetter(getOrientationForAudio); | 42 | audioClient->setOrientationGetter(getOrientationForAudio); |
| 43 | audioClient->startThread(); | ||
| 45 | 44 | ||
| 46 | audioClient->moveToThread(audioThread); | 45 | |
| 47 | connect(audioThread, &QThread::started, audioClient.data(), &AudioClient::start); | ||
| 48 | connect(audioClient.data(), &AudioClient::destroyed, audioThread, &QThread::quit); | ||
| 49 | connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); | ||
| 50 | |||
| 51 | audioThread->start(); | ||
| 52 | |||
| 53 | |||
| 54 | connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket); | 46 | connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket); |
| 55 | _avatarTimer.setInterval(16); // 60 FPS | 47 | _avatarTimer.setInterval(16); // 60 FPS |
| 56 | _avatarTimer.start(); | 48 | _avatarTimer.start(); |
| @@ -103,8 +103,8 @@ · 102 unmodified lines | |||
| 103 | "rotationVar": "spine2Rotation", | 103 | "rotationVar": "spine2Rotation", |
| 104 | "typeVar": "spine2Type", | 104 | "typeVar": "spine2Type", |
| 105 | "weightVar": "spine2Weight", | 105 | "weightVar": "spine2Weight", |
| 106 | "weight": 1.0, | 106 | "weight": 2.0, |
| 107 | "flexCoefficients": [1.0, 0.5, 0.5] | 107 | "flexCoefficients": [1.0, 0.5, 0.25] |
| 108 | }, | 108 | }, |
| 109 | { | 109 | { |
| 110 | "jointName": "Head", | 110 | "jointName": "Head", |
| @@ -113,7 +113,7 @@ · 2 unmodified lines | |||
| 113 | "typeVar": "headType", | 113 | "typeVar": "headType", |
| 114 | "weightVar": "headWeight", | 114 | "weightVar": "headWeight", |
| 115 | "weight": 4.0, | 115 | "weight": 4.0, |
| 116 | "flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25] | 116 | "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] |
| 117 | }, | 117 | }, |
| 118 | { | 118 | { |
| 119 | "jointName": "LeftArm", | 119 | "jointName": "LeftArm", |
| @@ -1,14 +1,16 @@ | |||
| 1 | { | 1 | { |
| 2 | "RenderShadowTask": { | 2 | "RenderMainView": { |
| 3 | "Enabled": { | 3 | "RenderShadowTask": { |
| 4 | "enabled": true | ||
| 5 | } | ||
| 6 | }, | ||
| 7 | "RenderDeferredTask": { | ||
| 8 | "AmbientOcclusion": { | ||
| 9 | "Enabled": { | 4 | "Enabled": { |
| 10 | "enabled": true | 5 | "enabled": true |
| 11 | } | 6 | } |
| 7 | }, | ||
| 8 | "RenderDeferredTask": { | ||
| 9 | "AmbientOcclusion": { | ||
| 10 | "Enabled": { | ||
| 11 | "enabled": true | ||
| 12 | } | ||
| 13 | } | ||
| 12 | } | 14 | } |
| 13 | } | 15 | } |
| 14 | } | 16 | } |
| @@ -57,6 +57,8 @@ · 56 unmodified lines | |||
| 57 | { "from": "OculusTouch.LeftThumbUp", "to": "Standard.LeftThumbUp" }, | 57 | { "from": "OculusTouch.LeftThumbUp", "to": "Standard.LeftThumbUp" }, |
| 58 | { "from": "OculusTouch.RightThumbUp", "to": "Standard.RightThumbUp" }, | 58 | { "from": "OculusTouch.RightThumbUp", "to": "Standard.RightThumbUp" }, |
| 59 | { "from": "OculusTouch.LeftIndexPoint", "to": "Standard.LeftIndexPoint" }, | 59 | { "from": "OculusTouch.LeftIndexPoint", "to": "Standard.LeftIndexPoint" }, |
| 60 | { "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" } | 60 | { "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" }, |
| 61 | |||
| 62 | { "from": "OculusTouch.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] } | ||
| 61 | ] | 63 | ] |
| 62 | } | 64 | } |
| @@ -35,35 +35,35 @@ · 34 unmodified lines | |||
| 35 | { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, | 35 | { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, |
| 36 | 36 | ||
| 37 | { "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] }, | 37 | { "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] }, |
| 38 | { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }, | 38 | { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }, |
| 39 | 39 | ||
| 40 | { | 40 | { |
| 41 | "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", | 41 | "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", |
| 42 | "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], | 42 | "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], |
| 43 | "when": [ "Application.InHMD"] | 43 | "when": [ "Application.InHMD" ] |
| 44 | }, | 44 | }, |
| 45 | 45 | ||
| 46 | { | 46 | { |
| 47 | "from": "Vive.RightFoot", "to" : "Standard.RightFoot", | 47 | "from": "Vive.RightFoot", "to" : "Standard.RightFoot", |
| 48 | "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], | 48 | "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], |
| 49 | "when": [ "Application.InHMD"] | 49 | "when": [ "Application.InHMD" ] |
| 50 | }, | 50 | }, |
| 51 | 51 | ||
| 52 | { | 52 | { |
| 53 | "from": "Vive.Hips", "to" : "Standard.Hips", | 53 | "from": "Vive.Hips", "to" : "Standard.Hips", |
| 54 | "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], | 54 | "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], |
| 55 | "when": [ "Application.InHMD"] | 55 | "when": [ "Application.InHMD" ] |
| 56 | }, | 56 | }, |
| 57 | 57 | ||
| 58 | { | 58 | { |
| 59 | "from": "Vive.Spine2", "to" : "Standard.Spine2", | 59 | "from": "Vive.Spine2", "to" : "Standard.Spine2", |
| 60 | "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], | 60 | "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], |
| 61 | "when": [ "Application.InHMD"] | 61 | "when": [ "Application.InHMD" ] |
| 62 | }, | 62 | }, |
| 63 | 63 | ||
| 64 | { "from": "Vive.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, | 64 | { "from": "Vive.Head", "to" : "Standard.Head", "when": [ "Application.InHMD" ] }, |
| 65 | 65 | ||
| 66 | { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when" : [ "Application.InHMD"] }, | 66 | { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when": [ "Application.InHMD" ] }, |
| 67 | { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when" : [ "Application.InHMD"] } | 67 | { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when": [ "Application.InHMD" ] } |
| 68 | ] | 68 | ] |
| 69 | } | 69 | } |
Binary file not shown.
| @@ -32,7 +32,7 @@ var EventBridge; · 31 unmodified lines | |||
| 32 | var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { | 32 | var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { |
| 33 | // replace the TempEventBridge with the real one. | 33 | // replace the TempEventBridge with the real one. |
| 34 | var tempEventBridge = EventBridge; | 34 | var tempEventBridge = EventBridge; |
| 35 | EventBridge = channel.objects.eventBridgeWrapper.eventBridge; | 35 | EventBridge = channel.objects.eventBridge; |
| 36 | tempEventBridge._callbacks.forEach(function (callback) { | 36 | tempEventBridge._callbacks.forEach(function (callback) { |
| 37 | EventBridge.scriptEventReceived.connect(callback); | 37 | EventBridge.scriptEventReceived.connect(callback); |
| 38 | }); | 38 | }); |
Binary file not shown.
| @@ -1,85 +1,89 @@ | |||
| 1 | name = Jointy3 | 1 | name = mannequin |
| 2 | type = body+head | 2 | type = body+head |
| 3 | scale = 1 | 3 | scale = 1 |
| 4 | filename = Jointy3/Jointy3.fbx | 4 | filename = mannequin/mannequin.baked.fbx |
| 5 | texdir = Jointy3/textures | 5 | joint = jointEyeLeft = LeftEye |
| 6 | joint = jointRightHand = RightHand | ||
| 7 | joint = jointHead = Head | ||
| 8 | joint = jointEyeRight = RightEye | ||
| 9 | joint = jointLean = Spine | ||
| 6 | joint = jointNeck = Neck | 10 | joint = jointNeck = Neck |
| 7 | joint = jointLeftHand = LeftHand | 11 | joint = jointLeftHand = LeftHand |
| 8 | joint = jointEyeRight = RightEye | ||
| 9 | joint = jointHead = Head | ||
| 10 | joint = jointRightHand = RightHand | ||
| 11 | joint = jointRoot = Hips | 12 | joint = jointRoot = Hips |
| 12 | joint = jointLean = Spine | ||
| 13 | joint = jointEyeLeft = LeftEye | ||
| 14 | freeJoint = LeftArm | 13 | freeJoint = LeftArm |
| 15 | freeJoint = LeftForeArm | 14 | freeJoint = LeftForeArm |
| 16 | freeJoint = RightArm | 15 | freeJoint = RightArm |
| 17 | freeJoint = RightForeArm | 16 | freeJoint = RightForeArm |
| 18 | jointIndex = RightHand = 17 | 17 | bs = EyeBlink_L = blink = 1 |
| 19 | jointIndex = LeftHandIndex3 = 56 | 18 | bs = JawOpen = mouth_Open = 1 |
| 20 | jointIndex = Hips = 0 | 19 | bs = LipsFunnel = Oo = 1 |
| 21 | jointIndex = LeftHandRing2 = 47 | 20 | bs = BrowsU_L = brow_Up = 1 |
| 22 | jointIndex = LeftHandThumb3 = 60 | 21 | jointIndex = RightHandIndex2 = 27 |
| 23 | jointIndex = RightShoulder = 14 | 22 | jointIndex = LeftHandIndex2 = 51 |
| 24 | jointIndex = RightHandRing1 = 30 | 23 | jointIndex = RightUpLeg = 6 |
| 25 | jointIndex = RightHandRing3 = 32 | 24 | jointIndex = RightToe_End = 10 |
| 26 | jointIndex = LeftHandPinky4 = 45 | 25 | jointIndex = RightEye = 65 |
| 27 | jointIndex = LeftHandRing1 = 46 | 26 | jointIndex = LeftHandPinky1 = 42 |
| 28 | jointIndex = LeftFoot = 8 | 27 | jointIndex = RightHandRing1 = 22 |
| 29 | jointIndex = RightHandIndex2 = 23 | 28 | jointIndex = face = 67 |
| 30 | jointIndex = RightToeBase = 4 | 29 | jointIndex = LeftUpLeg = 1 |
| 31 | jointIndex = RightHandMiddle4 = 29 | ||
| 32 | jointIndex = RightHandPinky4 = 37 | ||
| 33 | jointIndex = LeftToe_End = 10 | ||
| 34 | jointIndex = RightEye = 66 | ||
| 35 | jointIndex = RightHandPinky2 = 35 | ||
| 36 | jointIndex = RightHandRing2 = 31 | ||
| 37 | jointIndex = LeftHand = 41 | 30 | jointIndex = LeftHand = 41 |
| 38 | jointIndex = RightToe_End = 5 | 31 | jointIndex = LeftHandMiddle1 = 58 |
| 39 | jointIndex = LeftEye = 65 | 32 | jointIndex = LeftHandIndex1 = 50 |
| 40 | jointIndex = LeftHandThumb2 = 59 | 33 | jointIndex = LeftEye = 64 |
| 41 | jointIndex = pCylinder73Shape1 = 67 | 34 | jointIndex = RightHandIndex1 = 26 |
| 35 | jointIndex = LeftHandPinky4 = 45 | ||
| 36 | jointIndex = RightArm = 15 | ||
| 42 | jointIndex = LeftShoulder = 38 | 37 | jointIndex = LeftShoulder = 38 |
| 43 | jointIndex = LeftHandIndex2 = 55 | 38 | jointIndex = RightHandPinky2 = 19 |
| 39 | jointIndex = RightHandThumb1 = 30 | ||
| 44 | jointIndex = RightForeArm = 16 | 40 | jointIndex = RightForeArm = 16 |
| 45 | jointIndex = LeftHandMiddle2 = 51 | 41 | jointIndex = LeftHandMiddle3 = 60 |
| 46 | jointIndex = RightHandRing4 = 33 | ||
| 47 | jointIndex = LeftLeg = 7 | ||
| 48 | jointIndex = LeftHandThumb4 = 61 | ||
| 49 | jointIndex = LeftForeArm = 40 | ||
| 50 | jointIndex = HeadTop_End = 64 | ||
| 51 | jointIndex = RightHandPinky1 = 34 | ||
| 52 | jointIndex = RightHandIndex1 = 22 | ||
| 53 | jointIndex = LeftHandIndex1 = 54 | ||
| 54 | jointIndex = RightLeg = 2 | ||
| 55 | jointIndex = RightHandIndex4 = 25 | ||
| 56 | jointIndex = Neck = 62 | 42 | jointIndex = Neck = 62 |
| 57 | jointIndex = LeftHandMiddle1 = 50 | 43 | jointIndex = LeftHandThumb1 = 54 |
| 58 | jointIndex = RightHandPinky3 = 36 | 44 | jointIndex = RightHandMiddle2 = 35 |
| 59 | jointIndex = LeftHandPinky2 = 43 | 45 | jointIndex = LeftHandMiddle4 = 61 |
| 60 | jointIndex = RightHandMiddle3 = 28 | 46 | jointIndex = mannequin = 68 |
| 61 | jointIndex = RightHandThumb4 = 21 | 47 | jointIndex = Spine1 = 12 |
| 62 | jointIndex = LeftUpLeg = 6 | 48 | jointIndex = RightFoot = 8 |
| 63 | jointIndex = RightFoot = 3 | 49 | jointIndex = RightHand = 17 |
| 64 | jointIndex = LeftHandThumb1 = 58 | 50 | jointIndex = LeftHandIndex3 = 52 |
| 65 | jointIndex = LeftArm = 39 | 51 | jointIndex = RightHandIndex3 = 28 |
| 66 | jointIndex = RightHandMiddle1 = 26 | 52 | jointIndex = RightHandMiddle4 = 37 |
| 67 | jointIndex = LeftHandRing3 = 48 | 53 | jointIndex = LeftLeg = 2 |
| 68 | jointIndex = LeftHandMiddle4 = 53 | 54 | jointIndex = RightHandMiddle1 = 34 |
| 69 | jointIndex = RightUpLeg = 1 | ||
| 70 | jointIndex = RightHandMiddle2 = 27 | ||
| 71 | jointIndex = LeftToeBase = 9 | ||
| 72 | jointIndex = RightHandThumb2 = 19 | ||
| 73 | jointIndex = Spine2 = 13 | 55 | jointIndex = Spine2 = 13 |
| 74 | jointIndex = Spine = 11 | 56 | jointIndex = LeftHandMiddle2 = 59 |
| 57 | jointIndex = LeftHandPinky3 = 44 | ||
| 58 | jointIndex = LeftHandThumb3 = 56 | ||
| 75 | jointIndex = LeftHandRing4 = 49 | 59 | jointIndex = LeftHandRing4 = 49 |
| 60 | jointIndex = RightHandThumb2 = 31 | ||
| 61 | jointIndex = LeftHandRing3 = 48 | ||
| 62 | jointIndex = HeadTop_End = 66 | ||
| 63 | jointIndex = LeftHandThumb4 = 57 | ||
| 64 | jointIndex = RightHandThumb3 = 32 | ||
| 65 | jointIndex = RightHandPinky1 = 18 | ||
| 66 | jointIndex = RightLeg = 7 | ||
| 67 | jointIndex = RightHandMiddle3 = 36 | ||
| 68 | jointIndex = RightHandPinky3 = 20 | ||
| 69 | jointIndex = LeftToeBase = 4 | ||
| 70 | jointIndex = LeftForeArm = 40 | ||
| 71 | jointIndex = RightShoulder = 14 | ||
| 72 | jointIndex = LeftHandRing2 = 47 | ||
| 73 | jointIndex = LeftHandThumb2 = 55 | ||
| 76 | jointIndex = Head = 63 | 74 | jointIndex = Head = 63 |
| 77 | jointIndex = LeftHandPinky3 = 44 | 75 | jointIndex = RightHandRing4 = 25 |
| 78 | jointIndex = LeftHandPinky1 = 42 | 76 | jointIndex = LeftHandRing1 = 46 |
| 79 | jointIndex = RightHandThumb1 = 18 | 77 | jointIndex = LeftFoot = 3 |
| 80 | jointIndex = LeftHandIndex4 = 57 | 78 | jointIndex = RightHandRing3 = 24 |
| 81 | jointIndex = LeftHandMiddle3 = 52 | 79 | jointIndex = RightHandThumb4 = 33 |
| 82 | jointIndex = RightHandIndex3 = 24 | 80 | jointIndex = LeftArm = 39 |
| 83 | jointIndex = Spine1 = 12 | 81 | jointIndex = LeftToe_End = 5 |
| 84 | jointIndex = RightArm = 15 | 82 | jointIndex = RightToeBase = 9 |
| 85 | jointIndex = RightHandThumb3 = 20 | 83 | jointIndex = RightHandPinky4 = 21 |
| 84 | jointIndex = Spine = 11 | ||
| 85 | jointIndex = LeftHandIndex4 = 53 | ||
| 86 | jointIndex = LeftHandPinky2 = 43 | ||
| 87 | jointIndex = RightHandIndex4 = 29 | ||
| 88 | jointIndex = Hips = 0 | ||
| 89 | jointIndex = RightHandRing2 = 23 | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
| @@ -177,7 +177,7 @@ ScrollingWindow { · 176 unmodified lines | |||
| 177 | SHAPE_TYPE_STATIC_MESH | 177 | SHAPE_TYPE_STATIC_MESH |
| 178 | ], | 178 | ], |
| 179 | checkStateOnDisable: false, | 179 | checkStateOnDisable: false, |
| 180 | warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic" | 180 | warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" |
| 181 | } | 181 | } |
| 182 | }); | 182 | }); |
| 183 | 183 | ||
| @@ -2,7 +2,6 @@ import QtQuick 2.5 · 1 unmodified line | |||
| 2 | import QtQuick.Controls 1.2 | 2 | import QtQuick.Controls 1.2 |
| 3 | import QtWebChannel 1.0 | 3 | import QtWebChannel 1.0 |
| 4 | import QtWebEngine 1.2 | 4 | import QtWebEngine 1.2 |
| 5 | import FileTypeProfile 1.0 | ||
| 6 | 5 | ||
| 7 | import "controls-uit" | 6 | import "controls-uit" |
| 8 | import "styles" as HifiStyles | 7 | import "styles" as HifiStyles |
| @@ -22,8 +21,6 @@ ScrollingWindow { · 13 unmodified lines | |||
| 22 | property alias url: webview.url | 21 | property alias url: webview.url |
| 23 | property alias webView: webview | 22 | property alias webView: webview |
| 24 | 23 | ||
| 25 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 26 | |||
| 27 | signal loadingChanged(int status) | 24 | signal loadingChanged(int status) |
| 28 | 25 | ||
| 29 | x: 100 | 26 | x: 100 |
| @@ -209,21 +206,7 @@ ScrollingWindow { · 179 unmodified lines | |||
| 209 | WebView { | 206 | WebView { |
| 210 | id: webview | 207 | id: webview |
| 211 | url: "https://highfidelity.com/" | 208 | url: "https://highfidelity.com/" |
| 212 | 209 | profile: FileTypeProfile; | |
| 213 | property alias eventBridgeWrapper: eventBridgeWrapper | ||
| 214 | |||
| 215 | QtObject { | ||
| 216 | id: eventBridgeWrapper | ||
| 217 | WebChannel.id: "eventBridgeWrapper" | ||
| 218 | property var eventBridge; | ||
| 219 | } | ||
| 220 | |||
| 221 | profile: FileTypeProfile { | ||
| 222 | id: webviewProfile | ||
| 223 | storageName: "qmlWebEngine" | ||
| 224 | } | ||
| 225 | |||
| 226 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 227 | 210 | ||
| 228 | // Create a global EventBridge object for raiseAndLowerKeyboard. | 211 | // Create a global EventBridge object for raiseAndLowerKeyboard. |
| 229 | WebEngineScript { | 212 | WebEngineScript { |
| @@ -271,6 +254,8 @@ ScrollingWindow { · 41 unmodified lines | |||
| 271 | } | 254 | } |
| 272 | 255 | ||
| 273 | Component.onCompleted: { | 256 | Component.onCompleted: { |
| 257 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 258 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 274 | desktop.initWebviewProfileHandlers(webview.profile); | 259 | desktop.initWebviewProfileHandlers(webview.profile); |
| 275 | } | 260 | } |
| 276 | } | 261 | } |
| @@ -26,15 +26,8 @@ Windows.ScrollingWindow { · 25 unmodified lines | |||
| 26 | // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer | 26 | // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer |
| 27 | destroyOnCloseButton: false | 27 | destroyOnCloseButton: false |
| 28 | property alias source: webview.url | 28 | property alias source: webview.url |
| 29 | property alias eventBridge: eventBridgeWrapper.eventBridge; | ||
| 30 | property alias scriptUrl: webview.userScriptUrl | 29 | property alias scriptUrl: webview.userScriptUrl |
| 31 | 30 | ||
| 32 | QtObject { | ||
| 33 | id: eventBridgeWrapper | ||
| 34 | WebChannel.id: "eventBridgeWrapper" | ||
| 35 | property var eventBridge; | ||
| 36 | } | ||
| 37 | |||
| 38 | // This is for JS/QML communication, which is unused in a WebWindow, | 31 | // This is for JS/QML communication, which is unused in a WebWindow, |
| 39 | // but not having this here results in spurious warnings about a | 32 | // but not having this here results in spurious warnings about a |
| 40 | // missing signal | 33 | // missing signal |
| @@ -70,7 +63,6 @@ Windows.ScrollingWindow { · 29 unmodified lines | |||
| 70 | url: "about:blank" | 63 | url: "about:blank" |
| 71 | anchors.fill: parent | 64 | anchors.fill: parent |
| 72 | focus: true | 65 | focus: true |
| 73 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 74 | 66 | ||
| 75 | property string userScriptUrl: "" | 67 | property string userScriptUrl: "" |
| 76 | 68 | ||
| @@ -107,6 +99,8 @@ Windows.ScrollingWindow { · 30 unmodified lines | |||
| 107 | } | 99 | } |
| 108 | 100 | ||
| 109 | Component.onCompleted: { | 101 | Component.onCompleted: { |
| 102 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 103 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 110 | eventBridge.webEventReceived.connect(onWebEventReceived); | 104 | eventBridge.webEventReceived.connect(onWebEventReceived); |
| 111 | } | 105 | } |
| 112 | } | 106 | } |
| @@ -30,15 +30,6 @@ Windows.Window { · 29 unmodified lines | |||
| 30 | property bool keyboardRaised: false | 30 | property bool keyboardRaised: false |
| 31 | property bool punctuationMode: false | 31 | property bool punctuationMode: false |
| 32 | 32 | ||
| 33 | // JavaScript event bridge object in case QML content includes Web content. | ||
| 34 | property alias eventBridge: eventBridgeWrapper.eventBridge; | ||
| 35 | |||
| 36 | QtObject { | ||
| 37 | id: eventBridgeWrapper | ||
| 38 | WebChannel.id: "eventBridgeWrapper" | ||
| 39 | property var eventBridge; | ||
| 40 | } | ||
| 41 | |||
| 42 | onSourceChanged: { | 33 | onSourceChanged: { |
| 43 | if (dynamicContent) { | 34 | if (dynamicContent) { |
| 44 | dynamicContent.destroy(); | 35 | dynamicContent.destroy(); |
| @@ -8,7 +8,6 @@ import "controls-uit" as HifiControls · 7 unmodified lines | |||
| 8 | import "styles" as HifiStyles | 8 | import "styles" as HifiStyles |
| 9 | import "styles-uit" | 9 | import "styles-uit" |
| 10 | import "windows" | 10 | import "windows" |
| 11 | import HFTabletWebEngineProfile 1.0 | ||
| 12 | 11 | ||
| 13 | Item { | 12 | Item { |
| 14 | id: root | 13 | id: root |
| @@ -19,7 +18,6 @@ Item { · 4 unmodified lines | |||
| 19 | property variant permissionsBar: {'securityOrigin':'none','feature':'none'} | 18 | property variant permissionsBar: {'securityOrigin':'none','feature':'none'} |
| 20 | property alias url: webview.url | 19 | property alias url: webview.url |
| 21 | property WebEngineView webView: webview | 20 | property WebEngineView webView: webview |
| 22 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 23 | property bool canGoBack: webview.canGoBack | 21 | property bool canGoBack: webview.canGoBack |
| 24 | property bool canGoForward: webview.canGoForward | 22 | property bool canGoForward: webview.canGoForward |
| 25 | 23 | ||
| @@ -33,12 +31,6 @@ Item { · 7 unmodified lines | |||
| 33 | webview.profile = profile; | 31 | webview.profile = profile; |
| 34 | } | 32 | } |
| 35 | 33 | ||
| 36 | QtObject { | ||
| 37 | id: eventBridgeWrapper | ||
| 38 | WebChannel.id: "eventBridgeWrapper" | ||
| 39 | property var eventBridge; | ||
| 40 | } | ||
| 41 | |||
| 42 | WebEngineView { | 34 | WebEngineView { |
| 43 | id: webview | 35 | id: webview |
| 44 | objectName: "webEngineView" | 36 | objectName: "webEngineView" |
| @@ -47,10 +39,7 @@ Item { · 2 unmodified lines | |||
| 47 | width: parent.width | 39 | width: parent.width |
| 48 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height | 40 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height |
| 49 | 41 | ||
| 50 | profile: HFTabletWebEngineProfile { | 42 | profile: HFTabletWebEngineProfile; |
| 51 | id: webviewTabletProfile | ||
| 52 | storageName: "qmlTabletWebEngine" | ||
| 53 | } | ||
| 54 | 43 | ||
| 55 | property string userScriptUrl: "" | 44 | property string userScriptUrl: "" |
| 56 | 45 | ||
| @@ -82,9 +71,10 @@ Item { · 25 unmodified lines | |||
| 82 | 71 | ||
| 83 | property string newUrl: "" | 72 | property string newUrl: "" |
| 84 | 73 | ||
| 85 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 86 | |||
| 87 | Component.onCompleted: { | 74 | Component.onCompleted: { |
| 75 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 76 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 77 | |||
| 88 | // Ensure the JS from the web-engine makes it to our logging | 78 | // Ensure the JS from the web-engine makes it to our logging |
| 89 | webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { | 79 | webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { |
| 90 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); | 80 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); |
| @@ -79,15 +79,11 @@ ScrollingWindow { · 78 unmodified lines | |||
| 79 | id: webView | 79 | id: webView |
| 80 | anchors.fill: parent | 80 | anchors.fill: parent |
| 81 | enabled: false | 81 | enabled: false |
| 82 | property alias eventBridgeWrapper: eventBridgeWrapper | 82 | Component.onCompleted: { |
| 83 | 83 | webChannel.registerObject("eventBridge", eventBridge); | |
| 84 | QtObject { | 84 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); |
| 85 | id: eventBridgeWrapper | ||
| 86 | WebChannel.id: "eventBridgeWrapper" | ||
| 87 | property var eventBridge | ||
| 88 | } | 85 | } |
| 89 | 86 | ||
| 90 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 91 | onEnabledChanged: toolWindow.updateVisiblity() | 87 | onEnabledChanged: toolWindow.updateVisiblity() |
| 92 | } | 88 | } |
| 93 | } | 89 | } |
| @@ -251,12 +247,9 @@ ScrollingWindow { · 157 unmodified lines | |||
| 251 | tab.enabled = true; | 247 | tab.enabled = true; |
| 252 | tab.originalUrl = properties.source; | 248 | tab.originalUrl = properties.source; |
| 253 | 249 | ||
| 254 | var eventBridge = properties.eventBridge; | ||
| 255 | |||
| 256 | var result = tab.item; | 250 | var result = tab.item; |
| 257 | result.enabled = true; | 251 | result.enabled = true; |
| 258 | tabView.tabCount++; | 252 | tabView.tabCount++; |
| 259 | result.eventBridgeWrapper.eventBridge = eventBridge; | ||
| 260 | result.url = properties.source; | 253 | result.url = properties.source; |
| 261 | return result; | 254 | return result; |
| 262 | } | 255 | } |
| @@ -10,13 +10,10 @@ · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | import QtQuick 2.5 | 11 | import QtQuick 2.5 |
| 12 | import QtWebEngine 1.2 | 12 | import QtWebEngine 1.2 |
| 13 | import HFWebEngineProfile 1.0 | ||
| 14 | 13 | ||
| 15 | WebEngineView { | 14 | WebEngineView { |
| 16 | id: root | 15 | id: root |
| 17 | 16 | ||
| 18 | // profile: desktop.browserProfile | ||
| 19 | |||
| 20 | Component.onCompleted: { | 17 | Component.onCompleted: { |
| 21 | console.log("Connecting JS messaging to Hifi Logging") | 18 | console.log("Connecting JS messaging to Hifi Logging") |
| 22 | // Ensure the JS from the web-engine makes it to our logging | 19 | // Ensure the JS from the web-engine makes it to our logging |
| @@ -2,12 +2,10 @@ import QtQuick 2.5 · 1 unmodified line | |||
| 2 | import QtWebEngine 1.1 | 2 | import QtWebEngine 1.1 |
| 3 | import QtWebChannel 1.0 | 3 | import QtWebChannel 1.0 |
| 4 | import "../controls-uit" as HiFiControls | 4 | import "../controls-uit" as HiFiControls |
| 5 | import HFTabletWebEngineProfile 1.0 | ||
| 6 | 5 | ||
| 7 | Item { | 6 | Item { |
| 8 | property alias url: root.url | 7 | property alias url: root.url |
| 9 | property alias scriptURL: root.userScriptUrl | 8 | property alias scriptURL: root.userScriptUrl |
| 10 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 11 | property alias canGoBack: root.canGoBack; | 9 | property alias canGoBack: root.canGoBack; |
| 12 | property var goBack: root.goBack; | 10 | property var goBack: root.goBack; |
| 13 | property alias urlTag: root.urlTag | 11 | property alias urlTag: root.urlTag |
| @@ -23,12 +21,6 @@ Item { · 9 unmodified lines | |||
| 23 | } | 21 | } |
| 24 | */ | 22 | */ |
| 25 | 23 | ||
| 26 | QtObject { | ||
| 27 | id: eventBridgeWrapper | ||
| 28 | WebChannel.id: "eventBridgeWrapper" | ||
| 29 | property var eventBridge; | ||
| 30 | } | ||
| 31 | |||
| 32 | property alias viewProfile: root.profile | 24 | property alias viewProfile: root.profile |
| 33 | 25 | ||
| 34 | WebEngineView { | 26 | WebEngineView { |
| @@ -39,10 +31,7 @@ Item { · 4 unmodified lines | |||
| 39 | width: parent.width | 31 | width: parent.width |
| 40 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height | 32 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height |
| 41 | 33 | ||
| 42 | profile: HFTabletWebEngineProfile { | 34 | profile: HFTabletWebEngineProfile; |
| 43 | id: webviewProfile | ||
| 44 | storageName: "qmlTabletWebEngine" | ||
| 45 | } | ||
| 46 | 35 | ||
| 47 | property string userScriptUrl: "" | 36 | property string userScriptUrl: "" |
| 48 | 37 | ||
| @@ -75,10 +64,11 @@ Item { · 26 unmodified lines | |||
| 75 | userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] | 64 | userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] |
| 76 | 65 | ||
| 77 | property string newUrl: "" | 66 | property string newUrl: "" |
| 78 | 67 | ||
| 79 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 80 | 68 | ||
| 81 | Component.onCompleted: { | 69 | Component.onCompleted: { |
| 70 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 71 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 82 | // Ensure the JS from the web-engine makes it to our logging | 72 | // Ensure the JS from the web-engine makes it to our logging |
| 83 | root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { | 73 | root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { |
| 84 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); | 74 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); |
| @@ -2,7 +2,6 @@ import QtQuick 2.5 · 1 unmodified line | |||
| 2 | import QtQuick.Controls 1.4 | 2 | import QtQuick.Controls 1.4 |
| 3 | import QtWebEngine 1.2 | 3 | import QtWebEngine 1.2 |
| 4 | import QtWebChannel 1.0 | 4 | import QtWebChannel 1.0 |
| 5 | import HFTabletWebEngineProfile 1.0 | ||
| 6 | import "../controls-uit" as HiFiControls | 5 | import "../controls-uit" as HiFiControls |
| 7 | import "../styles" as HifiStyles | 6 | import "../styles" as HifiStyles |
| 8 | import "../styles-uit" | 7 | import "../styles-uit" |
| @@ -18,7 +17,6 @@ Item { · 9 unmodified lines | |||
| 18 | property int headerHeight: 70 | 17 | property int headerHeight: 70 |
| 19 | property string url | 18 | property string url |
| 20 | property string scriptURL | 19 | property string scriptURL |
| 21 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 22 | property bool keyboardEnabled: false | 20 | property bool keyboardEnabled: false |
| 23 | property bool keyboardRaised: false | 21 | property bool keyboardRaised: false |
| 24 | property bool punctuationMode: false | 22 | property bool punctuationMode: false |
| @@ -136,12 +134,6 @@ Item { · 111 unmodified lines | |||
| 136 | loadUrl(url); | 134 | loadUrl(url); |
| 137 | } | 135 | } |
| 138 | 136 | ||
| 139 | QtObject { | ||
| 140 | id: eventBridgeWrapper | ||
| 141 | WebChannel.id: "eventBridgeWrapper" | ||
| 142 | property var eventBridge; | ||
| 143 | } | ||
| 144 | |||
| 145 | WebEngineView { | 137 | WebEngineView { |
| 146 | id: webview | 138 | id: webview |
| 147 | objectName: "webEngineView" | 139 | objectName: "webEngineView" |
| @@ -150,10 +142,7 @@ Item { · 2 unmodified lines | |||
| 150 | width: parent.width | 142 | width: parent.width |
| 151 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height - web.headerHeight : parent.height - web.headerHeight | 143 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height - web.headerHeight : parent.height - web.headerHeight |
| 152 | anchors.top: buttons.bottom | 144 | anchors.top: buttons.bottom |
| 153 | profile: HFTabletWebEngineProfile { | 145 | profile: HFTabletWebEngineProfile; |
| 154 | id: webviewTabletProfile | ||
| 155 | storageName: "qmlTabletWebEngine" | ||
| 156 | } | ||
| 157 | 146 | ||
| 158 | property string userScriptUrl: "" | 147 | property string userScriptUrl: "" |
| 159 | 148 | ||
| @@ -186,9 +175,9 @@ Item { · 26 unmodified lines | |||
| 186 | 175 | ||
| 187 | property string newUrl: "" | 176 | property string newUrl: "" |
| 188 | 177 | ||
| 189 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 190 | |||
| 191 | Component.onCompleted: { | 178 | Component.onCompleted: { |
| 179 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 180 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 192 | // Ensure the JS from the web-engine makes it to our logging | 181 | // Ensure the JS from the web-engine makes it to our logging |
| 193 | webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { | 182 | webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { |
| 194 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); | 183 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); |
| @@ -10,13 +10,9 @@ · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | import QtQuick 2.5 | 11 | import QtQuick 2.5 |
| 12 | import "." | 12 | import "." |
| 13 | import FileTypeProfile 1.0 | ||
| 14 | 13 | ||
| 15 | WebView { | 14 | WebView { |
| 16 | viewProfile: FileTypeProfile { | 15 | viewProfile: FileTypeProfile; |
| 17 | id: webviewProfile | ||
| 18 | storageName: "qmlWebEngine" | ||
| 19 | } | ||
| 20 | 16 | ||
| 21 | urlTag: "noDownload=true"; | 17 | urlTag: "noDownload=true"; |
| 22 | } | 18 | } |
| @@ -2,12 +2,10 @@ import QtQuick 2.5 · 1 unmodified line | |||
| 2 | import QtWebEngine 1.1 | 2 | import QtWebEngine 1.1 |
| 3 | import QtWebChannel 1.0 | 3 | import QtWebChannel 1.0 |
| 4 | import "../controls-uit" as HiFiControls | 4 | import "../controls-uit" as HiFiControls |
| 5 | import HFWebEngineProfile 1.0 | ||
| 6 | 5 | ||
| 7 | Item { | 6 | Item { |
| 8 | property alias url: root.url | 7 | property alias url: root.url |
| 9 | property alias scriptURL: root.userScriptUrl | 8 | property alias scriptURL: root.userScriptUrl |
| 10 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 11 | property alias canGoBack: root.canGoBack; | 9 | property alias canGoBack: root.canGoBack; |
| 12 | property var goBack: root.goBack; | 10 | property var goBack: root.goBack; |
| 13 | property alias urlTag: root.urlTag | 11 | property alias urlTag: root.urlTag |
| @@ -23,12 +21,6 @@ Item { · 9 unmodified lines | |||
| 23 | } | 21 | } |
| 24 | */ | 22 | */ |
| 25 | 23 | ||
| 26 | QtObject { | ||
| 27 | id: eventBridgeWrapper | ||
| 28 | WebChannel.id: "eventBridgeWrapper" | ||
| 29 | property var eventBridge; | ||
| 30 | } | ||
| 31 | |||
| 32 | property alias viewProfile: root.profile | 24 | property alias viewProfile: root.profile |
| 33 | 25 | ||
| 34 | WebEngineView { | 26 | WebEngineView { |
| @@ -39,10 +31,7 @@ Item { · 4 unmodified lines | |||
| 39 | width: parent.width | 31 | width: parent.width |
| 40 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height | 32 | height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height |
| 41 | 33 | ||
| 42 | profile: HFWebEngineProfile { | 34 | profile: HFWebEngineProfile; |
| 43 | id: webviewProfile | ||
| 44 | storageName: "qmlWebEngine" | ||
| 45 | } | ||
| 46 | 35 | ||
| 47 | property string userScriptUrl: "" | 36 | property string userScriptUrl: "" |
| 48 | 37 | ||
| @@ -76,9 +65,9 @@ Item { · 27 unmodified lines | |||
| 76 | 65 | ||
| 77 | property string newUrl: "" | 66 | property string newUrl: "" |
| 78 | 67 | ||
| 79 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 80 | |||
| 81 | Component.onCompleted: { | 68 | Component.onCompleted: { |
| 69 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 70 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 82 | // Ensure the JS from the web-engine makes it to our logging | 71 | // Ensure the JS from the web-engine makes it to our logging |
| 83 | root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { | 72 | root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { |
| 84 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); | 73 | console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); |
| @@ -20,7 +20,6 @@ TabletModalWindow { · 19 unmodified lines | |||
| 20 | id: loginDialogRoot | 20 | id: loginDialogRoot |
| 21 | objectName: "LoginDialog" | 21 | objectName: "LoginDialog" |
| 22 | 22 | ||
| 23 | property var eventBridge; | ||
| 24 | signal sendToScript(var message); | 23 | signal sendToScript(var message); |
| 25 | property bool isHMD: false | 24 | property bool isHMD: false |
| 26 | property bool gotoPreviousApp: false; | 25 | property bool gotoPreviousApp: false; |
| @@ -24,8 +24,6 @@ Window { · 23 unmodified lines | |||
| 24 | resizable: true | 24 | resizable: true |
| 25 | modality: Qt.ApplicationModal | 25 | modality: Qt.ApplicationModal |
| 26 | 26 | ||
| 27 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 28 | |||
| 29 | Item { | 27 | Item { |
| 30 | anchors.fill: parent | 28 | anchors.fill: parent |
| 31 | 29 | ||
| @@ -45,16 +43,6 @@ Window { · 13 unmodified lines | |||
| 45 | bottom: keyboard.top | 43 | bottom: keyboard.top |
| 46 | } | 44 | } |
| 47 | 45 | ||
| 48 | property alias eventBridgeWrapper: eventBridgeWrapper | ||
| 49 | |||
| 50 | QtObject { | ||
| 51 | id: eventBridgeWrapper | ||
| 52 | WebChannel.id: "eventBridgeWrapper" | ||
| 53 | property var eventBridge; | ||
| 54 | } | ||
| 55 | |||
| 56 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 57 | |||
| 58 | // Create a global EventBridge object for raiseAndLowerKeyboard. | 46 | // Create a global EventBridge object for raiseAndLowerKeyboard. |
| 59 | WebEngineScript { | 47 | WebEngineScript { |
| 60 | id: createGlobalEventBridge | 48 | id: createGlobalEventBridge |
| @@ -73,6 +61,10 @@ Window { · 12 unmodified lines | |||
| 73 | 61 | ||
| 74 | userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] | 62 | userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] |
| 75 | 63 | ||
| 64 | Component.onCompleted: { | ||
| 65 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 66 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 67 | } | ||
| 76 | } | 68 | } |
| 77 | 69 | ||
| 78 | Keyboard { | 70 | Keyboard { |
| @@ -116,9 +116,7 @@ Preference { · 115 unmodified lines | |||
| 116 | 116 | ||
| 117 | Component { | 117 | Component { |
| 118 | id: tabletAvatarBrowserBuilder; | 118 | id: tabletAvatarBrowserBuilder; |
| 119 | TabletAvatarBrowser { | 119 | TabletAvatarBrowser { } |
| 120 | eventBridge: tabletRoot.eventBridge | ||
| 121 | } | ||
| 122 | } | 120 | } |
| 123 | 121 | ||
| 124 | } | 122 | } |
| @@ -2,7 +2,6 @@ import QtQuick 2.5 · 1 unmodified line | |||
| 2 | import QtQuick.Controls 1.4 | 2 | import QtQuick.Controls 1.4 |
| 3 | import QtWebEngine 1.1; | 3 | import QtWebEngine 1.1; |
| 4 | import Qt.labs.settings 1.0 | 4 | import Qt.labs.settings 1.0 |
| 5 | import HFWebEngineProfile 1.0 | ||
| 6 | 5 | ||
| 7 | import "../desktop" as OriginalDesktop | 6 | import "../desktop" as OriginalDesktop |
| 8 | import ".." | 7 | import ".." |
| @@ -27,11 +26,6 @@ OriginalDesktop.Desktop { · 18 unmodified lines | |||
| 27 | property alias toolWindow: toolWindow | 26 | property alias toolWindow: toolWindow |
| 28 | ToolWindow { id: toolWindow } | 27 | ToolWindow { id: toolWindow } |
| 29 | 28 | ||
| 30 | property var browserProfile: HFWebEngineProfile { | ||
| 31 | id: webviewProfile | ||
| 32 | storageName: "qmlWebEngine" | ||
| 33 | } | ||
| 34 | |||
| 35 | Action { | 29 | Action { |
| 36 | text: "Open Browser" | 30 | text: "Open Browser" |
| 37 | shortcut: "Ctrl+B" | 31 | shortcut: "Ctrl+B" |
| @@ -44,7 +44,6 @@ Rectangle { · 43 unmodified lines | |||
| 44 | property var activeTab: "nearbyTab"; | 44 | property var activeTab: "nearbyTab"; |
| 45 | property bool currentlyEditingDisplayName: false | 45 | property bool currentlyEditingDisplayName: false |
| 46 | property bool punctuationMode: false; | 46 | property bool punctuationMode: false; |
| 47 | property var eventBridge; | ||
| 48 | 47 | ||
| 49 | HifiConstants { id: hifi; } | 48 | HifiConstants { id: hifi; } |
| 50 | 49 | ||
| @@ -388,8 +387,13 @@ Rectangle { · 337 unmodified lines | |||
| 388 | sortIndicatorColumn: settings.nearbySortIndicatorColumn; | 387 | sortIndicatorColumn: settings.nearbySortIndicatorColumn; |
| 389 | sortIndicatorOrder: settings.nearbySortIndicatorOrder; | 388 | sortIndicatorOrder: settings.nearbySortIndicatorOrder; |
| 390 | onSortIndicatorColumnChanged: { | 389 | onSortIndicatorColumnChanged: { |
| 391 | settings.nearbySortIndicatorColumn = sortIndicatorColumn; | 390 | if (sortIndicatorColumn > 2) { |
| 392 | sortModel(); | 391 | // these are not sortable, switch back to last column |
| 392 | sortIndicatorColumn = settings.nearbySortIndicatorColumn; | ||
| 393 | } else { | ||
| 394 | settings.nearbySortIndicatorColumn = sortIndicatorColumn; | ||
| 395 | sortModel(); | ||
| 396 | } | ||
| 393 | } | 397 | } |
| 394 | onSortIndicatorOrderChanged: { | 398 | onSortIndicatorOrderChanged: { |
| 395 | settings.nearbySortIndicatorOrder = sortIndicatorOrder; | 399 | settings.nearbySortIndicatorOrder = sortIndicatorOrder; |
| @@ -1038,7 +1042,6 @@ Rectangle { · 642 unmodified lines | |||
| 1038 | } // Keyboard | 1042 | } // Keyboard |
| 1039 | 1043 | ||
| 1040 | HifiControls.TabletWebView { | 1044 | HifiControls.TabletWebView { |
| 1041 | eventBridge: pal.eventBridge; | ||
| 1042 | id: userInfoViewer; | 1045 | id: userInfoViewer; |
| 1043 | anchors { | 1046 | anchors { |
| 1044 | top: parent.top; | 1047 | top: parent.top; |
| @@ -24,7 +24,6 @@ Rectangle { · 23 unmodified lines | |||
| 24 | property string title: "Asset Browser" | 24 | property string title: "Asset Browser" |
| 25 | property bool keyboardRaised: false | 25 | property bool keyboardRaised: false |
| 26 | 26 | ||
| 27 | property var eventBridge; | ||
| 28 | signal sendToScript(var message); | 27 | signal sendToScript(var message); |
| 29 | property bool isHMD: false | 28 | property bool isHMD: false |
| 30 | 29 | ||
| @@ -179,7 +178,7 @@ Rectangle { · 148 unmodified lines | |||
| 179 | SHAPE_TYPE_STATIC_MESH | 178 | SHAPE_TYPE_STATIC_MESH |
| 180 | ], | 179 | ], |
| 181 | checkStateOnDisable: false, | 180 | checkStateOnDisable: false, |
| 182 | warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic" | 181 | warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" |
| 183 | } | 182 | } |
| 184 | }); | 183 | }); |
| 185 | 184 | ||
| @@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines | |||
| 20 | id: root | 20 | id: root |
| 21 | objectName: "DCConectionTiming" | 21 | objectName: "DCConectionTiming" |
| 22 | 22 | ||
| 23 | property var eventBridge; | ||
| 24 | signal sendToScript(var message); | 23 | signal sendToScript(var message); |
| 25 | property bool isHMD: false | 24 | property bool isHMD: false |
| 26 | 25 | ||
| @@ -19,7 +19,6 @@ Rectangle { · 18 unmodified lines | |||
| 19 | id: root | 19 | id: root |
| 20 | 20 | ||
| 21 | objectName: "DebugWindow" | 21 | objectName: "DebugWindow" |
| 22 | property var eventBridge; | ||
| 23 | 22 | ||
| 24 | property var title: "Debug Window" | 23 | property var title: "Debug Window" |
| 25 | property bool isHMD: false | 24 | property bool isHMD: false |
| @@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines | |||
| 20 | id: root | 20 | id: root |
| 21 | objectName: "EntityStatistics" | 21 | objectName: "EntityStatistics" |
| 22 | 22 | ||
| 23 | property var eventBridge; | ||
| 24 | signal sendToScript(var message); | 23 | signal sendToScript(var message); |
| 25 | property bool isHMD: false | 24 | property bool isHMD: false |
| 26 | 25 | ||
| @@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines | |||
| 20 | id: root | 20 | id: root |
| 21 | objectName: "LODTools" | 21 | objectName: "LODTools" |
| 22 | 22 | ||
| 23 | property var eventBridge; | ||
| 24 | signal sendToScript(var message); | 23 | signal sendToScript(var message); |
| 25 | property bool isHMD: false | 24 | property bool isHMD: false |
| 26 | 25 | ||
| @@ -23,7 +23,6 @@ Rectangle { · 22 unmodified lines | |||
| 23 | property string title: "Running Scripts" | 23 | property string title: "Running Scripts" |
| 24 | HifiConstants { id: hifi } | 24 | HifiConstants { id: hifi } |
| 25 | signal sendToScript(var message); | 25 | signal sendToScript(var message); |
| 26 | property var eventBridge; | ||
| 27 | property var scripts: ScriptDiscoveryService; | 26 | property var scripts: ScriptDiscoveryService; |
| 28 | property var scriptsModel: scripts.scriptsModelFilter | 27 | property var scriptsModel: scripts.scriptsModelFilter |
| 29 | property var runningScriptsModel: ListModel { } | 28 | property var runningScriptsModel: ListModel { } |
| @@ -7,14 +7,12 @@ StackView { · 6 unmodified lines | |||
| 7 | objectName: "stack" | 7 | objectName: "stack" |
| 8 | initialItem: Qt.resolvedUrl('EditTabView.qml') | 8 | initialItem: Qt.resolvedUrl('EditTabView.qml') |
| 9 | 9 | ||
| 10 | property var eventBridge; | ||
| 11 | signal sendToScript(var message); | 10 | signal sendToScript(var message); |
| 12 | 11 | ||
| 13 | HifiConstants { id: hifi } | 12 | HifiConstants { id: hifi } |
| 14 | 13 | ||
| 15 | function pushSource(path) { | 14 | function pushSource(path) { |
| 16 | editRoot.push(Qt.resolvedUrl(path)); | 15 | editRoot.push(Qt.resolvedUrl(path)); |
| 17 | editRoot.currentItem.eventBridge = editRoot.eventBridge; | ||
| 18 | editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); | 16 | editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); |
| 19 | } | 17 | } |
| 20 | 18 | ||
| @@ -5,7 +5,6 @@ import QtWebChannel 1.0 · 4 unmodified lines | |||
| 5 | import QtQuick.Controls.Styles 1.4 | 5 | import QtQuick.Controls.Styles 1.4 |
| 6 | import "../../controls" | 6 | import "../../controls" |
| 7 | import "../toolbars" | 7 | import "../toolbars" |
| 8 | import HFWebEngineProfile 1.0 | ||
| 9 | import QtGraphicalEffects 1.0 | 8 | import QtGraphicalEffects 1.0 |
| 10 | import "../../controls-uit" as HifiControls | 9 | import "../../controls-uit" as HifiControls |
| 11 | import "../../styles-uit" | 10 | import "../../styles-uit" |
| @@ -182,7 +181,6 @@ TabView { · 170 unmodified lines | |||
| 182 | WebView { | 181 | WebView { |
| 183 | id: entityListToolWebView | 182 | id: entityListToolWebView |
| 184 | url: "../../../../../scripts/system/html/entityList.html" | 183 | url: "../../../../../scripts/system/html/entityList.html" |
| 185 | eventBridge: editRoot.eventBridge | ||
| 186 | anchors.fill: parent | 184 | anchors.fill: parent |
| 187 | enabled: true | 185 | enabled: true |
| 188 | } | 186 | } |
| @@ -197,7 +195,6 @@ TabView { · 8 unmodified lines | |||
| 197 | WebView { | 195 | WebView { |
| 198 | id: entityPropertiesWebView | 196 | id: entityPropertiesWebView |
| 199 | url: "../../../../../scripts/system/html/entityProperties.html" | 197 | url: "../../../../../scripts/system/html/entityProperties.html" |
| 200 | eventBridge: editRoot.eventBridge | ||
| 201 | anchors.fill: parent | 198 | anchors.fill: parent |
| 202 | enabled: true | 199 | enabled: true |
| 203 | } | 200 | } |
| @@ -212,7 +209,6 @@ TabView { · 8 unmodified lines | |||
| 212 | WebView { | 209 | WebView { |
| 213 | id: gridControlsWebView | 210 | id: gridControlsWebView |
| 214 | url: "../../../../../scripts/system/html/gridControls.html" | 211 | url: "../../../../../scripts/system/html/gridControls.html" |
| 215 | eventBridge: editRoot.eventBridge | ||
| 216 | anchors.fill: parent | 212 | anchors.fill: parent |
| 217 | enabled: true | 213 | enabled: true |
| 218 | } | 214 | } |
| @@ -227,7 +223,6 @@ TabView { · 8 unmodified lines | |||
| 227 | WebView { | 223 | WebView { |
| 228 | id: particleExplorerWebView | 224 | id: particleExplorerWebView |
| 229 | url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" | 225 | url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" |
| 230 | eventBridge: editRoot.eventBridge | ||
| 231 | anchors.fill: parent | 226 | anchors.fill: parent |
| 232 | enabled: true | 227 | enabled: true |
| 233 | } | 228 | } |
| @@ -290,7 +285,7 @@ TabView { · 56 unmodified lines | |||
| 290 | editTabView.currentIndex = id; | 285 | editTabView.currentIndex = id; |
| 291 | } else { | 286 | } else { |
| 292 | console.warn('Attempt to switch to invalid tab:', id); | 287 | console.warn('Attempt to switch to invalid tab:', id); |
| 293 | } | 288 | } |
| 294 | } else if (typeof id === 'string'){ | 289 | } else if (typeof id === 'string'){ |
| 295 | switch (id.toLowerCase()) { | 290 | switch (id.toLowerCase()) { |
| 296 | case 'create': | 291 | case 'create': |
| @@ -18,7 +18,6 @@ import "../../dialogs" · 17 unmodified lines | |||
| 18 | 18 | ||
| 19 | Rectangle { | 19 | Rectangle { |
| 20 | id: inputRecorder | 20 | id: inputRecorder |
| 21 | property var eventBridge; | ||
| 22 | HifiConstants { id: hifi } | 21 | HifiConstants { id: hifi } |
| 23 | signal sendToScript(var message); | 22 | signal sendToScript(var message); |
| 24 | color: hifi.colors.baseGray; | 23 | color: hifi.colors.baseGray; |
| @@ -20,7 +20,6 @@ Rectangle { · 19 unmodified lines | |||
| 20 | // height: parent.height | 20 | // height: parent.height |
| 21 | HifiConstants { id: hifi } | 21 | HifiConstants { id: hifi } |
| 22 | color: hifi.colors.baseGray; | 22 | color: hifi.colors.baseGray; |
| 23 | property var eventBridge; | ||
| 24 | signal sendToScript(var message); | 23 | signal sendToScript(var message); |
| 25 | property bool keyboardEnabled: false | 24 | property bool keyboardEnabled: false |
| 26 | property bool punctuationMode: false | 25 | property bool punctuationMode: false |
| @@ -118,7 +117,7 @@ Rectangle { · 91 unmodified lines | |||
| 118 | id: text2 | 117 | id: text2 |
| 119 | width: 160 | 118 | width: 160 |
| 120 | color: "#ffffff" | 119 | color: "#ffffff" |
| 121 | text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic") | 120 | text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") |
| 122 | wrapMode: Text.WordWrap | 121 | wrapMode: Text.WordWrap |
| 123 | font.pixelSize: 12 | 122 | font.pixelSize: 12 |
| 124 | } | 123 | } |
| @@ -29,7 +29,6 @@ StackView { · 28 unmodified lines | |||
| 29 | initialItem: addressBarDialog | 29 | initialItem: addressBarDialog |
| 30 | width: parent !== null ? parent.width : undefined | 30 | width: parent !== null ? parent.width : undefined |
| 31 | height: parent !== null ? parent.height : undefined | 31 | height: parent !== null ? parent.height : undefined |
| 32 | property var eventBridge; | ||
| 33 | property int cardWidth: 212; | 32 | property int cardWidth: 212; |
| 34 | property int cardHeight: 152; | 33 | property int cardHeight: 152; |
| 35 | property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; | 34 | property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; |
| @@ -80,7 +79,6 @@ StackView { · 44 unmodified lines | |||
| 80 | var card = tabletWebView.createObject(); | 79 | var card = tabletWebView.createObject(); |
| 81 | card.url = addressBarDialog.metaverseServerUrl + targetString; | 80 | card.url = addressBarDialog.metaverseServerUrl + targetString; |
| 82 | card.parentStackItem = root; | 81 | card.parentStackItem = root; |
| 83 | card.eventBridge = root.eventBridge; | ||
| 84 | root.push(card); | 82 | root.push(card); |
| 85 | return; | 83 | return; |
| 86 | } | 84 | } |
| @@ -25,7 +25,6 @@ Item { · 24 unmodified lines | |||
| 25 | property bool keyboardRaised: false | 25 | property bool keyboardRaised: false |
| 26 | property bool punctuationMode: false | 26 | property bool punctuationMode: false |
| 27 | 27 | ||
| 28 | property var eventBridge; | ||
| 29 | signal sendToScript(var message); | 28 | signal sendToScript(var message); |
| 30 | 29 | ||
| 31 | anchors.fill: parent | 30 | anchors.fill: parent |
| @@ -20,6 +20,7 @@ StackView { · 19 unmodified lines | |||
| 20 | property string title: "Audio Buffers" | 20 | property string title: "Audio Buffers" |
| 21 | property alias gotoPreviousApp: root.gotoPreviousApp; | 21 | property alias gotoPreviousApp: root.gotoPreviousApp; |
| 22 | property var eventBridge; | 22 | property var eventBridge; |
| 23 | |||
| 23 | signal sendToScript(var message); | 24 | signal sendToScript(var message); |
| 24 | 25 | ||
| 25 | function pushSource(path) { | 26 | function pushSource(path) { |
| @@ -19,7 +19,6 @@ StackView { · 18 unmodified lines | |||
| 19 | objectName: "stack" | 19 | objectName: "stack" |
| 20 | property string title: "Avatar Settings" | 20 | property string title: "Avatar Settings" |
| 21 | 21 | ||
| 22 | property var eventBridge; | ||
| 23 | signal sendToScript(var message); | 22 | signal sendToScript(var message); |
| 24 | 23 | ||
| 25 | function pushSource(path) { | 24 | function pushSource(path) { |
| @@ -19,7 +19,6 @@ StackView { · 18 unmodified lines | |||
| 19 | objectName: "stack" | 19 | objectName: "stack" |
| 20 | property string title: "General Settings" | 20 | property string title: "General Settings" |
| 21 | property alias gotoPreviousApp: root.gotoPreviousApp; | 21 | property alias gotoPreviousApp: root.gotoPreviousApp; |
| 22 | property var eventBridge; | ||
| 23 | signal sendToScript(var message); | 22 | signal sendToScript(var message); |
| 24 | 23 | ||
| 25 | function pushSource(path) { | 24 | function pushSource(path) { |
| @@ -19,7 +19,6 @@ StackView { · 18 unmodified lines | |||
| 19 | objectName: "stack" | 19 | objectName: "stack" |
| 20 | property string title: "Graphics Settings" | 20 | property string title: "Graphics Settings" |
| 21 | 21 | ||
| 22 | property var eventBridge; | ||
| 23 | signal sendToScript(var message); | 22 | signal sendToScript(var message); |
| 24 | 23 | ||
| 25 | function pushSource(path) { | 24 | function pushSource(path) { |
| @@ -19,7 +19,6 @@ StackView { · 18 unmodified lines | |||
| 19 | objectName: "stack" | 19 | objectName: "stack" |
| 20 | property string title: "LOD Settings" | 20 | property string title: "LOD Settings" |
| 21 | 21 | ||
| 22 | property var eventBridge; | ||
| 23 | signal sendToScript(var message); | 22 | signal sendToScript(var message); |
| 24 | 23 | ||
| 25 | function pushSource(path) { | 24 | function pushSource(path) { |
| @@ -4,7 +4,6 @@ import QtQuick.Controls 1.4 · 3 unmodified lines | |||
| 4 | import QtQml 2.2 | 4 | import QtQml 2.2 |
| 5 | import QtWebChannel 1.0 | 5 | import QtWebChannel 1.0 |
| 6 | import QtWebEngine 1.1 | 6 | import QtWebEngine 1.1 |
| 7 | import HFWebEngineProfile 1.0 | ||
| 8 | 7 | ||
| 9 | 8 | ||
| 10 | import "." | 9 | import "." |
| @@ -22,7 +21,6 @@ FocusScope { · 11 unmodified lines | |||
| 22 | property var point: Qt.point(50, 50); | 21 | property var point: Qt.point(50, 50); |
| 23 | TabletMenuStack { id: menuPopperUpper } | 22 | TabletMenuStack { id: menuPopperUpper } |
| 24 | property string subMenu: "" | 23 | property string subMenu: "" |
| 25 | property var eventBridge; | ||
| 26 | signal sendToScript(var message); | 24 | signal sendToScript(var message); |
| 27 | 25 | ||
| 28 | Rectangle { | 26 | Rectangle { |
| @@ -49,7 +49,6 @@ Item { · 48 unmodified lines | |||
| 49 | 49 | ||
| 50 | function pushSource(path) { | 50 | function pushSource(path) { |
| 51 | d.push(Qt.resolvedUrl(path)); | 51 | d.push(Qt.resolvedUrl(path)); |
| 52 | d.currentItem.eventBridge = tabletMenu.eventBridge | ||
| 53 | d.currentItem.sendToScript.connect(tabletMenu.sendToScript); | 52 | d.currentItem.sendToScript.connect(tabletMenu.sendToScript); |
| 54 | d.currentItem.focus = true; | 53 | d.currentItem.focus = true; |
| 55 | d.currentItem.forceActiveFocus(); | 54 | d.currentItem.forceActiveFocus(); |
| @@ -19,7 +19,6 @@ StackView { · 18 unmodified lines | |||
| 19 | objectName: "stack" | 19 | objectName: "stack" |
| 20 | property var title: "Networking Settings" | 20 | property var title: "Networking Settings" |
| 21 | 21 | ||
| 22 | property var eventBridge; | ||
| 23 | signal sendToScript(var message); | 22 | signal sendToScript(var message); |
| 24 | 23 | ||
| 25 | function pushSource(path) { | 24 | function pushSource(path) { |
| @@ -1,7 +1,6 @@ | |||
| 1 | import QtQuick 2.0 | 1 | import QtQuick 2.0 |
| 2 | import Hifi 1.0 | 2 | import Hifi 1.0 |
| 3 | import QtQuick.Controls 1.4 | 3 | import QtQuick.Controls 1.4 |
| 4 | import HFTabletWebEngineProfile 1.0 | ||
| 5 | import "../../dialogs" | 4 | import "../../dialogs" |
| 6 | import "../../controls" | 5 | import "../../controls" |
| 7 | 6 | ||
| @@ -9,7 +8,6 @@ Item { · 1 unmodified line | |||
| 9 | id: tabletRoot | 8 | id: tabletRoot |
| 10 | objectName: "tabletRoot" | 9 | objectName: "tabletRoot" |
| 11 | property string username: "Unknown user" | 10 | property string username: "Unknown user" |
| 12 | property var eventBridge; | ||
| 13 | property var rootMenu; | 11 | property var rootMenu; |
| 14 | property var openModal: null; | 12 | property var openModal: null; |
| 15 | property var openMessage: null; | 13 | property var openMessage: null; |
| @@ -112,7 +110,6 @@ Item { · 96 unmodified lines | |||
| 112 | function openBrowserWindow(request, profile) { | 110 | function openBrowserWindow(request, profile) { |
| 113 | var component = Qt.createComponent("../../controls/TabletWebView.qml"); | 111 | var component = Qt.createComponent("../../controls/TabletWebView.qml"); |
| 114 | var newWindow = component.createObject(tabletRoot); | 112 | var newWindow = component.createObject(tabletRoot); |
| 115 | newWindow.eventBridge = tabletRoot.eventBridge; | ||
| 116 | newWindow.remove = true; | 113 | newWindow.remove = true; |
| 117 | newWindow.profile = profile; | 114 | newWindow.profile = profile; |
| 118 | request.openIn(newWindow.webView); | 115 | request.openIn(newWindow.webView); |
| @@ -166,21 +163,21 @@ Item { · 47 unmodified lines | |||
| 166 | objectName: "loader" | 163 | objectName: "loader" |
| 167 | asynchronous: false | 164 | asynchronous: false |
| 168 | 165 | ||
| 169 | |||
| 170 | width: parent.width | 166 | width: parent.width |
| 171 | height: parent.height | 167 | height: parent.height |
| 172 | 168 | ||
| 173 | onLoaded: { | 169 | // Hook up callback for clara.io download from the marketplace. |
| 174 | if (loader.item.hasOwnProperty("eventBridge")) { | 170 | Connections { |
| 175 | loader.item.eventBridge = eventBridge; | 171 | id: eventBridgeConnection |
| 176 | 172 | target: eventBridge | |
| 177 | // Hook up callback for clara.io download from the marketplace. | 173 | onWebEventReceived: { |
| 178 | eventBridge.webEventReceived.connect(function (event) { | 174 | if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { |
| 179 | if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { | 175 | ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); |
| 180 | ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); | 176 | } |
| 181 | } | ||
| 182 | }); | ||
| 183 | } | 177 | } |
| 178 | } | ||
| 179 | |||
| 180 | onLoaded: { | ||
| 184 | if (loader.item.hasOwnProperty("sendToScript")) { | 181 | if (loader.item.hasOwnProperty("sendToScript")) { |
| 185 | loader.item.sendToScript.connect(tabletRoot.sendToScript); | 182 | loader.item.sendToScript.connect(tabletRoot.sendToScript); |
| 186 | } | 183 | } |
| @@ -18,7 +18,6 @@ Windows.ScrollingWindow { · 17 unmodified lines | |||
| 18 | id: tabletRoot | 18 | id: tabletRoot |
| 19 | objectName: "tabletRoot" | 19 | objectName: "tabletRoot" |
| 20 | property string username: "Unknown user" | 20 | property string username: "Unknown user" |
| 21 | property var eventBridge; | ||
| 22 | 21 | ||
| 23 | property var rootMenu; | 22 | property var rootMenu; |
| 24 | property string subMenu: "" | 23 | property string subMenu: "" |
| @@ -86,17 +85,18 @@ Windows.ScrollingWindow { · 61 unmodified lines | |||
| 86 | anchors.left: parent.left | 85 | anchors.left: parent.left |
| 87 | anchors.top: parent.top | 86 | anchors.top: parent.top |
| 88 | 87 | ||
| 89 | onLoaded: { | 88 | // Hook up callback for clara.io download from the marketplace. |
| 90 | if (loader.item.hasOwnProperty("eventBridge")) { | 89 | Connections { |
| 91 | loader.item.eventBridge = eventBridge; | 90 | id: eventBridgeConnection |
| 92 | 91 | target: eventBridge | |
| 93 | // Hook up callback for clara.io download from the marketplace. | 92 | onWebEventReceived: { |
| 94 | eventBridge.webEventReceived.connect(function (event) { | 93 | if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { |
| 95 | if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { | 94 | ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); |
| 96 | ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); | 95 | } |
| 97 | } | ||
| 98 | }); | ||
| 99 | } | 96 | } |
| 97 | } | ||
| 98 | |||
| 99 | onLoaded: { | ||
| 100 | if (loader.item.hasOwnProperty("sendToScript")) { | 100 | if (loader.item.hasOwnProperty("sendToScript")) { |
| 101 | loader.item.sendToScript.connect(tabletRoot.sendToScript); | 101 | loader.item.sendToScript.connect(tabletRoot.sendToScript); |
| 102 | } | 102 | } |
| @@ -136,7 +136,7 @@ Item { · 135 unmodified lines | |||
| 136 | for (var i = 0; i < sections.length; i++) { | 136 | for (var i = 0; i < sections.length; i++) { |
| 137 | totalHeight += sections[i].height + sections[i].getPreferencesHeight(); | 137 | totalHeight += sections[i].height + sections[i].getPreferencesHeight(); |
| 138 | } | 138 | } |
| 139 | var bottomPadding = 100; | 139 | var bottomPadding = 170; |
| 140 | return (totalHeight + bottomPadding); | 140 | return (totalHeight + bottomPadding); |
| 141 | } | 141 | } |
| 142 | } | 142 | } |
| @@ -27,8 +27,6 @@ Item { · 26 unmodified lines | |||
| 27 | property bool keyboardRaised: false | 27 | property bool keyboardRaised: false |
| 28 | property bool punctuationMode: false | 28 | property bool punctuationMode: false |
| 29 | 29 | ||
| 30 | property alias eventBridge: eventBridgeWrapper.eventBridge | ||
| 31 | |||
| 32 | anchors.fill: parent | 30 | anchors.fill: parent |
| 33 | 31 | ||
| 34 | BaseWebView { | 32 | BaseWebView { |
| @@ -43,14 +41,6 @@ Item { · 8 unmodified lines | |||
| 43 | bottom: footer.top | 41 | bottom: footer.top |
| 44 | } | 42 | } |
| 45 | 43 | ||
| 46 | QtObject { | ||
| 47 | id: eventBridgeWrapper | ||
| 48 | WebChannel.id: "eventBridgeWrapper" | ||
| 49 | property var eventBridge; | ||
| 50 | } | ||
| 51 | |||
| 52 | webChannel.registeredObjects: [eventBridgeWrapper] | ||
| 53 | |||
| 54 | // Create a global EventBridge object for raiseAndLowerKeyboard. | 44 | // Create a global EventBridge object for raiseAndLowerKeyboard. |
| 55 | WebEngineScript { | 45 | WebEngineScript { |
| 56 | id: createGlobalEventBridge | 46 | id: createGlobalEventBridge |
| @@ -68,6 +58,11 @@ Item { · 11 unmodified lines | |||
| 68 | } | 58 | } |
| 69 | 59 | ||
| 70 | userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] | 60 | userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] |
| 61 | |||
| 62 | Component.onCompleted: { | ||
| 63 | webChannel.registerObject("eventBridge", eventBridge); | ||
| 64 | webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); | ||
| 65 | } | ||
| 71 | } | 66 | } |
| 72 | 67 | ||
| 73 | Rectangle { | 68 | Rectangle { |
Binary file not shown.
| @@ -11,6 +11,9 @@ · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | #include "Application.h" | 12 | #include "Application.h" |
| 13 | 13 | ||
| 14 | #include <chrono> | ||
| 15 | #include <thread> | ||
| 16 | |||
| 14 | #include <gl/Config.h> | 17 | #include <gl/Config.h> |
| 15 | #include <glm/glm.hpp> | 18 | #include <glm/glm.hpp> |
| 16 | #include <glm/gtx/component_wise.hpp> | 19 | #include <glm/gtx/component_wise.hpp> |
| @@ -69,6 +72,7 @@ · 52 unmodified lines | |||
| 69 | #include <ErrorDialog.h> | 72 | #include <ErrorDialog.h> |
| 70 | #include <FileScriptingInterface.h> | 73 | #include <FileScriptingInterface.h> |
| 71 | #include <Finally.h> | 74 | #include <Finally.h> |
| 75 | #include <FingerprintUtils.h> | ||
| 72 | #include <FramebufferCache.h> | 76 | #include <FramebufferCache.h> |
| 73 | #include <gpu/Batch.h> | 77 | #include <gpu/Batch.h> |
| 74 | #include <gpu/Context.h> | 78 | #include <gpu/Context.h> |
| @@ -110,7 +114,9 @@ · 35 unmodified lines | |||
| 110 | #include <render/RenderFetchCullSortTask.h> | 114 | #include <render/RenderFetchCullSortTask.h> |
| 111 | #include <RenderDeferredTask.h> | 115 | #include <RenderDeferredTask.h> |
| 112 | #include <RenderForwardTask.h> | 116 | #include <RenderForwardTask.h> |
| 117 | #include <RenderViewTask.h> | ||
| 113 | #include <ResourceCache.h> | 118 | #include <ResourceCache.h> |
| 119 | #include <ResourceRequest.h> | ||
| 114 | #include <SandboxUtils.h> | 120 | #include <SandboxUtils.h> |
| 115 | #include <SceneScriptingInterface.h> | 121 | #include <SceneScriptingInterface.h> |
| 116 | #include <ScriptEngines.h> | 122 | #include <ScriptEngines.h> |
| @@ -144,10 +150,8 @@ · 27 unmodified lines | |||
| 144 | #include "InterfaceLogging.h" | 150 | #include "InterfaceLogging.h" |
| 145 | #include "LODManager.h" | 151 | #include "LODManager.h" |
| 146 | #include "ModelPackager.h" | 152 | #include "ModelPackager.h" |
| 147 | #include "networking/HFWebEngineProfile.h" | ||
| 148 | #include "networking/HFTabletWebEngineProfile.h" | ||
| 149 | #include "networking/FileTypeProfile.h" | ||
| 150 | #include "scripting/Audio.h" | 153 | #include "scripting/Audio.h" |
| 154 | #include "networking/CloseEventSender.h" | ||
| 151 | #include "scripting/TestScriptingInterface.h" | 155 | #include "scripting/TestScriptingInterface.h" |
| 152 | #include "scripting/AccountScriptingInterface.h" | 156 | #include "scripting/AccountScriptingInterface.h" |
| 153 | #include "scripting/AssetMappingsScriptingInterface.h" | 157 | #include "scripting/AssetMappingsScriptingInterface.h" |
| @@ -244,6 +248,8 @@ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStanda · 90 unmodified lines | |||
| 244 | Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); | 248 | Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); |
| 245 | 249 | ||
| 246 | static const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com"; | 250 | static const QString MARKETPLACE_CDN_HOSTNAME = "mpassets.highfidelity.com"; |
| 251 | static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds | ||
| 252 | static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; | ||
| 247 | 253 | ||
| 248 | static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; | 254 | static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; |
| 249 | 255 | ||
| @@ -438,6 +444,10 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 188 unmodified lines | |||
| 438 | const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); | 444 | const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); |
| 439 | const int listenPort = portStr ? atoi(portStr) : INVALID_PORT; | 445 | const int listenPort = portStr ? atoi(portStr) : INVALID_PORT; |
| 440 | 446 | ||
| 447 | static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; | ||
| 448 | bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET); | ||
| 449 | bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt); | ||
| 450 | |||
| 441 | Setting::init(); | 451 | Setting::init(); |
| 442 | 452 | ||
| 443 | if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { | 453 | if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { |
| @@ -458,10 +468,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 14 unmodified lines | |||
| 458 | QCoreApplication::addLibraryPath(audioDLLPath); | 468 | QCoreApplication::addLibraryPath(audioDLLPath); |
| 459 | #endif | 469 | #endif |
| 460 | 470 | ||
| 461 | static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; | ||
| 462 | bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET); | ||
| 463 | bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt); | ||
| 464 | |||
| 465 | DependencyManager::registerInheritance<LimitedNodeList, NodeList>(); | 471 | DependencyManager::registerInheritance<LimitedNodeList, NodeList>(); |
| 466 | DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>(); | 472 | DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>(); |
| 467 | DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>(); | 473 | DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>(); |
| @@ -536,6 +542,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { · 68 unmodified lines | |||
| 536 | DependencyManager::set<AvatarBookmarks>(); | 542 | DependencyManager::set<AvatarBookmarks>(); |
| 537 | DependencyManager::set<LocationBookmarks>(); | 543 | DependencyManager::set<LocationBookmarks>(); |
| 538 | DependencyManager::set<Snapshot>(); | 544 | DependencyManager::set<Snapshot>(); |
| 545 | DependencyManager::set<CloseEventSender>(); | ||
| 539 | 546 | ||
| 540 | return previousSessionCrashed; | 547 | return previousSessionCrashed; |
| 541 | } | 548 | } |
| @@ -649,6 +656,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 107 unmodified lines | |||
| 649 | Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us | 656 | Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us |
| 650 | 657 | ||
| 651 | auto nodeList = DependencyManager::get<NodeList>(); | 658 | auto nodeList = DependencyManager::get<NodeList>(); |
| 659 | nodeList->startThread(); | ||
| 652 | 660 | ||
| 653 | // Set up a watchdog thread to intentionally crash the application on deadlocks | 661 | // Set up a watchdog thread to intentionally crash the application on deadlocks |
| 654 | _deadlockWatchdogThread = new DeadlockWatchdogThread(); | 662 | _deadlockWatchdogThread = new DeadlockWatchdogThread(); |
| @@ -674,25 +682,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 19 unmodified lines | |||
| 674 | 682 | ||
| 675 | updateHeartbeat(); | 683 | updateHeartbeat(); |
| 676 | 684 | ||
| 677 | // start the nodeThread so its event loop is running | ||
| 678 | QThread* nodeThread = new QThread(this); | ||
| 679 | nodeThread->setObjectName("NodeList Thread"); | ||
| 680 | nodeThread->start(); | ||
| 681 | |||
| 682 | // make sure the node thread is given highest priority | ||
| 683 | nodeThread->setPriority(QThread::TimeCriticalPriority); | ||
| 684 | |||
| 685 | // setup a timer for domain-server check ins | 685 | // setup a timer for domain-server check ins |
| 686 | QTimer* domainCheckInTimer = new QTimer(nodeList.data()); | 686 | QTimer* domainCheckInTimer = new QTimer(nodeList.data()); |
| 687 | connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); | 687 | connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); |
| 688 | domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); | 688 | domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); |
| 689 | 689 | ||
| 690 | // put the NodeList and datagram processing on the node thread | ||
| 691 | nodeList->moveToThread(nodeThread); | ||
| 692 | |||
| 693 | // put the audio processing on a separate thread | ||
| 694 | QThread* audioThread = new QThread(); | ||
| 695 | audioThread->setObjectName("Audio Thread"); | ||
| 696 | 690 | ||
| 697 | auto audioIO = DependencyManager::get<AudioClient>(); | 691 | auto audioIO = DependencyManager::get<AudioClient>(); |
| 698 | audioIO->setPositionGetter([]{ | 692 | audioIO->setPositionGetter([]{ |
| @@ -708,7 +702,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 9 unmodified lines | |||
| 708 | return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; | 702 | return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; |
| 709 | }); | 703 | }); |
| 710 | 704 | ||
| 711 | audioIO->moveToThread(audioThread); | ||
| 712 | recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { | 705 | recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { |
| 713 | audioIO->handleRecordedAudioInput(frame->data); | 706 | audioIO->handleRecordedAudioInput(frame->data); |
| 714 | }); | 707 | }); |
| @@ -720,11 +713,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 5 unmodified lines | |||
| 720 | recorder->recordFrame(AUDIO_FRAME_TYPE, audio); | 713 | recorder->recordFrame(AUDIO_FRAME_TYPE, audio); |
| 721 | } | 714 | } |
| 722 | }); | 715 | }); |
| 723 | 716 | audioIO->startThread(); | |
| 724 | connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start); | ||
| 725 | connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit); | ||
| 726 | connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); | ||
| 727 | audioThread->start(); | ||
| 728 | 717 | ||
| 729 | auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface, scripting::Audio>(); | 718 | auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface, scripting::Audio>(); |
| 730 | connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); | 719 | connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); |
| @@ -750,12 +739,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 19 unmodified lines | |||
| 750 | updateHeartbeat(); | 739 | updateHeartbeat(); |
| 751 | 740 | ||
| 752 | // Setup MessagesClient | 741 | // Setup MessagesClient |
| 753 | auto messagesClient = DependencyManager::get<MessagesClient>(); | 742 | DependencyManager::get<MessagesClient>()->startThread(); |
| 754 | QThread* messagesThread = new QThread; | ||
| 755 | messagesThread->setObjectName("Messages Client Thread"); | ||
| 756 | messagesClient->moveToThread(messagesThread); | ||
| 757 | connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); | ||
| 758 | messagesThread->start(); | ||
| 759 | 743 | ||
| 760 | const DomainHandler& domainHandler = nodeList->getDomainHandler(); | 744 | const DomainHandler& domainHandler = nodeList->getDomainHandler(); |
| 761 | 745 | ||
| @@ -950,6 +934,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 188 unmodified lines | |||
| 950 | properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; | 934 | properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; |
| 951 | } | 935 | } |
| 952 | 936 | ||
| 937 | // add firstRun flag from settings to launch event | ||
| 938 | Setting::Handle<bool> firstRun { Settings::firstRun, true }; | ||
| 939 | properties["first_run"] = firstRun.get(); | ||
| 940 | |||
| 941 | // add the user's machine ID to the launch event | ||
| 942 | properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); | ||
| 943 | |||
| 953 | UserActivityLogger::getInstance().logAction("launch", properties); | 944 | UserActivityLogger::getInstance().logAction("launch", properties); |
| 954 | 945 | ||
| 955 | // Tell our entity edit sender about our known jurisdictions | 946 | // Tell our entity edit sender about our known jurisdictions |
| @@ -1309,6 +1300,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 353 unmodified lines | |||
| 1309 | properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); | 1300 | properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); |
| 1310 | properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); | 1301 | properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); |
| 1311 | 1302 | ||
| 1303 | properties["atp_in_kbps"] = bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AssetServer); | ||
| 1304 | |||
| 1312 | auto nodeList = DependencyManager::get<NodeList>(); | 1305 | auto nodeList = DependencyManager::get<NodeList>(); |
| 1313 | SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer); | 1306 | SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer); |
| 1314 | SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); | 1307 | SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); |
| @@ -1322,8 +1315,61 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 7 unmodified lines | |||
| 1322 | properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1; | 1315 | properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1; |
| 1323 | 1316 | ||
| 1324 | auto loadingRequests = ResourceCache::getLoadingRequests(); | 1317 | auto loadingRequests = ResourceCache::getLoadingRequests(); |
| 1318 | |||
| 1319 | QJsonArray loadingRequestsStats; | ||
| 1320 | for (const auto& request : loadingRequests) { | ||
| 1321 | QJsonObject requestStats; | ||
| 1322 | requestStats["filename"] = request->getURL().fileName(); | ||
| 1323 | requestStats["received"] = request->getBytesReceived(); | ||
| 1324 | requestStats["total"] = request->getBytesTotal(); | ||
| 1325 | requestStats["attempts"] = (int)request->getDownloadAttempts(); | ||
| 1326 | loadingRequestsStats.append(requestStats); | ||
| 1327 | } | ||
| 1328 | |||
| 1325 | properties["active_downloads"] = loadingRequests.size(); | 1329 | properties["active_downloads"] = loadingRequests.size(); |
| 1326 | properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); | 1330 | properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); |
| 1331 | properties["active_downloads_details"] = loadingRequestsStats; | ||
| 1332 | |||
| 1333 | auto statTracker = DependencyManager::get<StatTracker>(); | ||
| 1334 | |||
| 1335 | properties["processing_resources"] = statTracker->getStat("Processing").toInt(); | ||
| 1336 | properties["pending_processing_resources"] = statTracker->getStat("PendingProcessing").toInt(); | ||
| 1337 | |||
| 1338 | QJsonObject startedRequests; | ||
| 1339 | startedRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_STARTED).toInt(); | ||
| 1340 | startedRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_STARTED).toInt(); | ||
| 1341 | startedRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_STARTED).toInt(); | ||
| 1342 | startedRequests["total"] = startedRequests["atp"].toInt() + startedRequests["http"].toInt() | ||
| 1343 | + startedRequests["file"].toInt(); | ||
| 1344 | properties["started_requests"] = startedRequests; | ||
| 1345 | |||
| 1346 | QJsonObject successfulRequests; | ||
| 1347 | successfulRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_SUCCESS).toInt(); | ||
| 1348 | successfulRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_SUCCESS).toInt(); | ||
| 1349 | successfulRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_SUCCESS).toInt(); | ||
| 1350 | successfulRequests["total"] = successfulRequests["atp"].toInt() + successfulRequests["http"].toInt() | ||
| 1351 | + successfulRequests["file"].toInt(); | ||
| 1352 | properties["successful_requests"] = successfulRequests; | ||
| 1353 | |||
| 1354 | QJsonObject failedRequests; | ||
| 1355 | failedRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_FAILED).toInt(); | ||
| 1356 | failedRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_FAILED).toInt(); | ||
| 1357 | failedRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_FAILED).toInt(); | ||
| 1358 | failedRequests["total"] = failedRequests["atp"].toInt() + failedRequests["http"].toInt() | ||
| 1359 | + failedRequests["file"].toInt(); | ||
| 1360 | properties["failed_requests"] = failedRequests; | ||
| 1361 | |||
| 1362 | QJsonObject cacheRequests; | ||
| 1363 | cacheRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_CACHE).toInt(); | ||
| 1364 | cacheRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_CACHE).toInt(); | ||
| 1365 | cacheRequests["total"] = cacheRequests["atp"].toInt() + cacheRequests["http"].toInt(); | ||
| 1366 | properties["cache_requests"] = cacheRequests; | ||
| 1367 | |||
| 1368 | QJsonObject atpMappingRequests; | ||
| 1369 | atpMappingRequests["started"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_STARTED).toInt(); | ||
| 1370 | atpMappingRequests["failed"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_FAILED).toInt(); | ||
| 1371 | atpMappingRequests["successful"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_SUCCESS).toInt(); | ||
| 1372 | properties["atp_mapping_requests"] = atpMappingRequests; | ||
| 1327 | 1373 | ||
| 1328 | properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; | 1374 | properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; |
| 1329 | 1375 | ||
| @@ -1339,9 +1385,43 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 9 unmodified lines | |||
| 1339 | properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount; | 1385 | properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount; |
| 1340 | properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount; | 1386 | properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount; |
| 1341 | 1387 | ||
| 1388 | NodeToOctreeSceneStats* octreeServerSceneStats = getOcteeSceneStats(); | ||
| 1389 | unsigned long totalServerOctreeElements = 0; | ||
| 1390 | for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) { | ||
| 1391 | totalServerOctreeElements += i->second.getTotalElements(); | ||
| 1392 | } | ||
| 1393 | |||
| 1394 | properties["local_octree_elements"] = (qint64) OctreeElement::getInternalNodeCount(); | ||
| 1395 | properties["server_octree_elements"] = (qint64) totalServerOctreeElements; | ||
| 1396 | |||
| 1342 | properties["active_display_plugin"] = getActiveDisplayPlugin()->getName(); | 1397 | properties["active_display_plugin"] = getActiveDisplayPlugin()->getName(); |
| 1343 | properties["using_hmd"] = isHMDMode(); | 1398 | properties["using_hmd"] = isHMDMode(); |
| 1344 | 1399 | ||
| 1400 | _autoSwitchDisplayModeSupportedHMDPlugin = nullptr; | ||
| 1401 | foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { | ||
| 1402 | if (displayPlugin->isHmd() && | ||
| 1403 | displayPlugin->getSupportsAutoSwitch()) { | ||
| 1404 | _autoSwitchDisplayModeSupportedHMDPlugin = displayPlugin; | ||
| 1405 | _autoSwitchDisplayModeSupportedHMDPluginName = | ||
| 1406 | _autoSwitchDisplayModeSupportedHMDPlugin->getName(); | ||
| 1407 | _previousHMDWornStatus = | ||
| 1408 | _autoSwitchDisplayModeSupportedHMDPlugin->isDisplayVisible(); | ||
| 1409 | break; | ||
| 1410 | } | ||
| 1411 | } | ||
| 1412 | |||
| 1413 | if (_autoSwitchDisplayModeSupportedHMDPlugin) { | ||
| 1414 | if (getActiveDisplayPlugin() != _autoSwitchDisplayModeSupportedHMDPlugin && | ||
| 1415 | !_autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) { | ||
| 1416 | startHMDStandBySession(); | ||
| 1417 | } | ||
| 1418 | // Poll periodically to check whether the user has worn HMD or not. Switch Display mode accordingly. | ||
| 1419 | // If the user wears HMD then switch to VR mode. If the user removes HMD then switch to Desktop mode. | ||
| 1420 | QTimer* autoSwitchDisplayModeTimer = new QTimer(this); | ||
| 1421 | connect(autoSwitchDisplayModeTimer, SIGNAL(timeout()), this, SLOT(switchDisplayMode())); | ||
| 1422 | autoSwitchDisplayModeTimer->start(INTERVAL_TO_CHECK_HMD_WORN_STATUS); | ||
| 1423 | } | ||
| 1424 | |||
| 1345 | auto glInfo = getGLContextData(); | 1425 | auto glInfo = getGLContextData(); |
| 1346 | properties["gl_info"] = glInfo; | 1426 | properties["gl_info"] = glInfo; |
| 1347 | properties["gpu_used_memory"] = (int)BYTES_TO_MB(gpu::Context::getUsedGPUMemSize()); | 1427 | properties["gpu_used_memory"] = (int)BYTES_TO_MB(gpu::Context::getUsedGPUMemSize()); |
| @@ -1364,6 +1444,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 16 unmodified lines | |||
| 1364 | lastLeftHandPose = leftHandPose; | 1444 | lastLeftHandPose = leftHandPose; |
| 1365 | lastRightHandPose = rightHandPose; | 1445 | lastRightHandPose = rightHandPose; |
| 1366 | 1446 | ||
| 1447 | properties["local_socket_changes"] = DependencyManager::get<StatTracker>()->getStat(LOCAL_SOCKET_CHANGE_STAT).toInt(); | ||
| 1448 | |||
| 1367 | UserActivityLogger::getInstance().logAction("stats", properties); | 1449 | UserActivityLogger::getInstance().logAction("stats", properties); |
| 1368 | }); | 1450 | }); |
| 1369 | sendStatsTimer->start(); | 1451 | sendStatsTimer->start(); |
| @@ -1463,6 +1545,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo · 93 unmodified lines | |||
| 1463 | updateSystemTabletMode(); | 1545 | updateSystemTabletMode(); |
| 1464 | 1546 | ||
| 1465 | connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged); | 1547 | connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged); |
| 1548 | |||
| 1549 | qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); | ||
| 1466 | } | 1550 | } |
| 1467 | 1551 | ||
| 1468 | void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { | 1552 | void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { |
| @@ -1571,6 +1655,12 @@ void Application::aboutToQuit() { · 102 unmodified lines | |||
| 1571 | } | 1655 | } |
| 1572 | 1656 | ||
| 1573 | getActiveDisplayPlugin()->deactivate(); | 1657 | getActiveDisplayPlugin()->deactivate(); |
| 1658 | if (_autoSwitchDisplayModeSupportedHMDPlugin | ||
| 1659 | && _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) { | ||
| 1660 | _autoSwitchDisplayModeSupportedHMDPlugin->endSession(); | ||
| 1661 | } | ||
| 1662 | // use the CloseEventSender via a QThread to send an event that says the user asked for the app to close | ||
| 1663 | DependencyManager::get<CloseEventSender>()->startThread(); | ||
| 1574 | 1664 | ||
| 1575 | // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. | 1665 | // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. |
| 1576 | DependencyManager::get<OffscreenUi>()->hide("RunningScripts"); | 1666 | DependencyManager::get<OffscreenUi>()->hide("RunningScripts"); |
| @@ -1658,6 +1748,8 @@ void Application::cleanupBeforeQuit() { · 81 unmodified lines | |||
| 1658 | // stop QML | 1748 | // stop QML |
| 1659 | DependencyManager::destroy<OffscreenUi>(); | 1749 | DependencyManager::destroy<OffscreenUi>(); |
| 1660 | 1750 | ||
| 1751 | DependencyManager::destroy<OffscreenQmlSurfaceCache>(); | ||
| 1752 | |||
| 1661 | if (_snapshotSoundInjector != nullptr) { | 1753 | if (_snapshotSoundInjector != nullptr) { |
| 1662 | _snapshotSoundInjector->stop(); | 1754 | _snapshotSoundInjector->stop(); |
| 1663 | } | 1755 | } |
| @@ -1685,6 +1777,10 @@ Application::~Application() { · 21 unmodified lines | |||
| 1685 | 1777 | ||
| 1686 | _physicsEngine->setCharacterController(nullptr); | 1778 | _physicsEngine->setCharacterController(nullptr); |
| 1687 | 1779 | ||
| 1780 | // the _shapeManager should have zero references | ||
| 1781 | _shapeManager.collectGarbage(); | ||
| 1782 | assert(_shapeManager.getNumShapes() == 0); | ||
| 1783 | |||
| 1688 | // shutdown render engine | 1784 | // shutdown render engine |
| 1689 | _main3DScene = nullptr; | 1785 | _main3DScene = nullptr; |
| 1690 | _renderEngine = nullptr; | 1786 | _renderEngine = nullptr; |
| @@ -1713,15 +1809,9 @@ Application::~Application() { · 22 unmodified lines | |||
| 1713 | 1809 | ||
| 1714 | ResourceManager::cleanup(); | 1810 | ResourceManager::cleanup(); |
| 1715 | 1811 | ||
| 1716 | QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); | ||
| 1717 | |||
| 1718 | // remove the NodeList from the DependencyManager | 1812 | // remove the NodeList from the DependencyManager |
| 1719 | DependencyManager::destroy<NodeList>(); | 1813 | DependencyManager::destroy<NodeList>(); |
| 1720 | 1814 | ||
| 1721 | // ask the node thread to quit and wait until it is done | ||
| 1722 | nodeThread->quit(); | ||
| 1723 | nodeThread->wait(); | ||
| 1724 | |||
| 1725 | Leapmotion::destroy(); | 1815 | Leapmotion::destroy(); |
| 1726 | 1816 | ||
| 1727 | if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { | 1817 | if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { |
| @@ -1736,6 +1826,15 @@ Application::~Application() { · 8 unmodified lines | |||
| 1736 | 1826 | ||
| 1737 | _window->deleteLater(); | 1827 | _window->deleteLater(); |
| 1738 | 1828 | ||
| 1829 | // make sure that the quit event has finished sending before we take the application down | ||
| 1830 | auto closeEventSender = DependencyManager::get<CloseEventSender>(); | ||
| 1831 | while (!closeEventSender->hasFinishedQuitEvent() && !closeEventSender->hasTimedOutQuitEvent()) { | ||
| 1832 | // sleep a little so we're not spinning at 100% | ||
| 1833 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); | ||
| 1834 | } | ||
| 1835 | // quit the thread used by the closure event sender | ||
| 1836 | closeEventSender->thread()->quit(); | ||
| 1837 | |||
| 1739 | // Can't log to file passed this point, FileLogger about to be deleted | 1838 | // Can't log to file passed this point, FileLogger about to be deleted |
| 1740 | qInstallMessageHandler(LogHandler::verboseMessageHandler); | 1839 | qInstallMessageHandler(LogHandler::verboseMessageHandler); |
| 1741 | } | 1840 | } |
| @@ -1771,15 +1870,9 @@ void Application::initializeGL() { · 29 unmodified lines | |||
| 1771 | 1870 | ||
| 1772 | // Set up the render engine | 1871 | // Set up the render engine |
| 1773 | render::CullFunctor cullFunctor = LODManager::shouldRender; | 1872 | render::CullFunctor cullFunctor = LODManager::shouldRender; |
| 1774 | _renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor); | ||
| 1775 | const auto items = _renderEngine->addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor); | ||
| 1776 | assert(items.canCast<RenderFetchCullSortTask::Output>()); | ||
| 1777 | static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; | 1873 | static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; |
| 1778 | if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) { | 1874 | bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); |
| 1779 | _renderEngine->addJob<RenderForwardTask>("Forward", items); | 1875 | _renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred); |
| 1780 | } else { | ||
| 1781 | _renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", items); | ||
| 1782 | } | ||
| 1783 | _renderEngine->load(); | 1876 | _renderEngine->load(); |
| 1784 | _renderEngine->registerScene(_main3DScene); | 1877 | _renderEngine->registerScene(_main3DScene); |
| 1785 | 1878 | ||
| @@ -1828,14 +1921,10 @@ void Application::initializeUi() { · 42 unmodified lines | |||
| 1828 | UpdateDialog::registerType(); | 1921 | UpdateDialog::registerType(); |
| 1829 | qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); | 1922 | qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); |
| 1830 | 1923 | ||
| 1831 | qmlRegisterType<HFWebEngineProfile>("HFWebEngineProfile", 1, 0, "HFWebEngineProfile"); | ||
| 1832 | qmlRegisterType<HFTabletWebEngineProfile>("HFTabletWebEngineProfile", 1, 0, "HFTabletWebEngineProfile"); | ||
| 1833 | qmlRegisterType<FileTypeProfile>("FileTypeProfile", 1, 0, "FileTypeProfile"); | ||
| 1834 | |||
| 1835 | auto offscreenUi = DependencyManager::get<OffscreenUi>(); | 1924 | auto offscreenUi = DependencyManager::get<OffscreenUi>(); |
| 1836 | offscreenUi->create(_glWidget->qglContext()); | 1925 | offscreenUi->create(_glWidget->qglContext()); |
| 1837 | 1926 | ||
| 1838 | auto rootContext = offscreenUi->getRootContext(); | 1927 | auto surfaceContext = offscreenUi->getSurfaceContext(); |
| 1839 | 1928 | ||
| 1840 | offscreenUi->setProxyWindow(_window->windowHandle()); | 1929 | offscreenUi->setProxyWindow(_window->windowHandle()); |
| 1841 | offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); | 1930 | offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); |
| @@ -1847,7 +1936,7 @@ void Application::initializeUi() { · 5 unmodified lines | |||
| 1847 | // do better detection in the offscreen UI of what has focus | 1936 | // do better detection in the offscreen UI of what has focus |
| 1848 | offscreenUi->setNavigationFocused(false); | 1937 | offscreenUi->setNavigationFocused(false); |
| 1849 | 1938 | ||
| 1850 | auto engine = rootContext->engine(); | 1939 | auto engine = surfaceContext->engine(); |
| 1851 | connect(engine, &QQmlEngine::quit, [] { | 1940 | connect(engine, &QQmlEngine::quit, [] { |
| 1852 | qApp->quit(); | 1941 | qApp->quit(); |
| 1853 | }); | 1942 | }); |
| @@ -1856,78 +1945,77 @@ void Application::initializeUi() { · 2 unmodified lines | |||
| 1856 | 1945 | ||
| 1857 | // For some reason there is already an "Application" object in the QML context, | 1946 | // For some reason there is already an "Application" object in the QML context, |
| 1858 | // though I can't find it. Hence, "ApplicationInterface" | 1947 | // though I can't find it. Hence, "ApplicationInterface" |
| 1859 | rootContext->setContextProperty("ApplicationInterface", this); | 1948 | surfaceContext->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data()); |
| 1860 | rootContext->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data()); | 1949 | surfaceContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data()); |
| 1861 | rootContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data()); | 1950 | surfaceContext->setContextProperty("AudioScope", DependencyManager::get<AudioScope>().data()); |
| 1862 | rootContext->setContextProperty("AudioScope", DependencyManager::get<AudioScope>().data()); | ||
| 1863 | 1951 | ||
| 1864 | rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data()); | 1952 | surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data()); |
| 1865 | rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data()); | 1953 | surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data()); |
| 1866 | _fileDownload = new FileScriptingInterface(engine); | 1954 | _fileDownload = new FileScriptingInterface(engine); |
| 1867 | rootContext->setContextProperty("File", _fileDownload); | 1955 | surfaceContext->setContextProperty("File", _fileDownload); |
| 1868 | connect(_fileDownload, &FileScriptingInterface::unzipResult, this, &Application::handleUnzip); | 1956 | connect(_fileDownload, &FileScriptingInterface::unzipResult, this, &Application::handleUnzip); |
| 1869 | rootContext->setContextProperty("MyAvatar", getMyAvatar().get()); | 1957 | surfaceContext->setContextProperty("MyAvatar", getMyAvatar().get()); |
| 1870 | rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data()); | 1958 | surfaceContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data()); |
| 1871 | rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data()); | 1959 | surfaceContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data()); |
| 1872 | rootContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data()); | 1960 | surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data()); |
| 1873 | rootContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data()); | 1961 | surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data()); |
| 1874 | rootContext->setContextProperty("FrameTimings", &_frameTimingsScriptingInterface); | 1962 | surfaceContext->setContextProperty("FrameTimings", &_frameTimingsScriptingInterface); |
| 1875 | rootContext->setContextProperty("Rates", new RatesScriptingInterface(this)); | 1963 | surfaceContext->setContextProperty("Rates", new RatesScriptingInterface(this)); |
| 1876 | rootContext->setContextProperty("pathToFonts", "../../"); | ||
| 1877 | 1964 | ||
| 1878 | rootContext->setContextProperty("TREE_SCALE", TREE_SCALE); | 1965 | surfaceContext->setContextProperty("TREE_SCALE", TREE_SCALE); |
| 1879 | rootContext->setContextProperty("Quat", new Quat()); | 1966 | // FIXME Quat and Vec3 won't work with QJSEngine used by QML |
| 1880 | rootContext->setContextProperty("Vec3", new Vec3()); | 1967 | surfaceContext->setContextProperty("Quat", new Quat()); |
| 1881 | rootContext->setContextProperty("Uuid", new ScriptUUID()); | 1968 | surfaceContext->setContextProperty("Vec3", new Vec3()); |
| 1882 | rootContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data()); | 1969 | surfaceContext->setContextProperty("Uuid", new ScriptUUID()); |
| 1970 | surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data()); | ||
| 1883 | 1971 | ||
| 1884 | rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data()); | 1972 | surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data()); |
| 1885 | rootContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data()); | 1973 | surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data()); |
| 1886 | 1974 | ||
| 1887 | rootContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data()); | 1975 | surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data()); |
| 1888 | 1976 | ||
| 1889 | rootContext->setContextProperty("Camera", &_myCamera); | 1977 | surfaceContext->setContextProperty("Camera", &_myCamera); |
| 1890 | 1978 | ||
| 1891 | #if defined(Q_OS_MAC) || defined(Q_OS_WIN) | 1979 | #if defined(Q_OS_MAC) || defined(Q_OS_WIN) |
| 1892 | rootContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data()); | 1980 | surfaceContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data()); |
| 1893 | #endif | 1981 | #endif |
| 1894 | 1982 | ||
| 1895 | rootContext->setContextProperty("Overlays", &_overlays); | 1983 | surfaceContext->setContextProperty("Overlays", &_overlays); |
| 1896 | rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data()); | 1984 | surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data()); |
| 1897 | rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); | 1985 | surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); |
| 1898 | rootContext->setContextProperty("Stats", Stats::getInstance()); | 1986 | surfaceContext->setContextProperty("Stats", Stats::getInstance()); |
| 1899 | rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); | 1987 | surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); |
| 1900 | rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data()); | 1988 | surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data()); |
| 1901 | rootContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data()); | 1989 | surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data()); |
| 1902 | rootContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data()); | 1990 | surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data()); |
| 1903 | 1991 | ||
| 1904 | // Caches | 1992 | // Caches |
| 1905 | rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data()); | 1993 | surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data()); |
| 1906 | rootContext->setContextProperty("TextureCache", DependencyManager::get<TextureCache>().data()); | 1994 | surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCache>().data()); |
| 1907 | rootContext->setContextProperty("ModelCache", DependencyManager::get<ModelCache>().data()); | 1995 | surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCache>().data()); |
| 1908 | rootContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data()); | 1996 | surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data()); |
| 1909 | 1997 | ||
| 1910 | rootContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); | 1998 | surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); |
| 1911 | rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data()); | 1999 | surfaceContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data()); |
| 1912 | rootContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); | 2000 | surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); |
| 1913 | rootContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); | 2001 | surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); |
| 1914 | rootContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data()); | 2002 | surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data()); |
| 1915 | rootContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data()); | 2003 | surfaceContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data()); |
| 1916 | rootContext->setContextProperty("UndoStack", &_undoStackScriptingInterface); | 2004 | surfaceContext->setContextProperty("UndoStack", &_undoStackScriptingInterface); |
| 1917 | rootContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data()); | 2005 | surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data()); |
| 1918 | rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data()); | 2006 | surfaceContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data()); |
| 1919 | rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data()); | 2007 | surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data()); |
| 1920 | rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data()); | 2008 | surfaceContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data()); |
| 1921 | rootContext->setContextProperty("Render", _renderEngine->getConfiguration().get()); | 2009 | surfaceContext->setContextProperty("Render", _renderEngine->getConfiguration().get()); |
| 1922 | rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface()); | 2010 | surfaceContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface()); |
| 1923 | rootContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data()); | 2011 | surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data()); |
| 1924 | 2012 | ||
| 1925 | rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor()); | 2013 | surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor()); |
| 1926 | 2014 | ||
| 1927 | rootContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); | 2015 | surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); |
| 1928 | 2016 | ||
| 1929 | if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { | 2017 | if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { |
| 1930 | rootContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get())); | 2018 | surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get())); |
| 1931 | } | 2019 | } |
| 1932 | 2020 | ||
| 1933 | 2021 | ||
| @@ -2066,7 +2154,7 @@ void Application::paintGL() { · 132 unmodified lines | |||
| 2066 | _myCamera.setOrientation(glm::quat_cast(camMat)); | 2154 | _myCamera.setOrientation(glm::quat_cast(camMat)); |
| 2067 | } else { | 2155 | } else { |
| 2068 | _myCamera.setPosition(myAvatar->getDefaultEyePosition()); | 2156 | _myCamera.setPosition(myAvatar->getDefaultEyePosition()); |
| 2069 | _myCamera.setOrientation(myAvatar->getMyHead()->getCameraOrientation()); | 2157 | _myCamera.setOrientation(myAvatar->getMyHead()->getHeadOrientation()); |
| 2070 | } | 2158 | } |
| 2071 | } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { | 2159 | } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { |
| 2072 | if (isHMDMode()) { | 2160 | if (isHMDMode()) { |
| @@ -2186,6 +2274,9 @@ void Application::paintGL() { · 113 unmodified lines | |||
| 2186 | }); | 2274 | }); |
| 2187 | renderArgs._context->setStereoProjections(eyeProjections); | 2275 | renderArgs._context->setStereoProjections(eyeProjections); |
| 2188 | renderArgs._context->setStereoViews(eyeOffsets); | 2276 | renderArgs._context->setStereoViews(eyeOffsets); |
| 2277 | |||
| 2278 | // Configure the type of display / stereo | ||
| 2279 | renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR); | ||
| 2189 | } | 2280 | } |
| 2190 | renderArgs._blitFramebuffer = finalFramebuffer; | 2281 | renderArgs._blitFramebuffer = finalFramebuffer; |
| 2191 | displaySide(&renderArgs, _myCamera); | 2282 | displaySide(&renderArgs, _myCamera); |
| @@ -2379,15 +2470,16 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 187 unmodified lines | |||
| 2379 | 2470 | ||
| 2380 | // Check HMD use (may be technically available without being in use) | 2471 | // Check HMD use (may be technically available without being in use) |
| 2381 | bool hasHMD = PluginUtils::isHMDAvailable(); | 2472 | bool hasHMD = PluginUtils::isHMDAvailable(); |
| 2382 | bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd(); | 2473 | bool isUsingHMD = _displayPlugin->isHmd(); |
| 2474 | bool isUsingHMDAndHandControllers = hasHMD && hasHandControllers && isUsingHMD; | ||
| 2383 | 2475 | ||
| 2384 | Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false }; | 2476 | Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false }; |
| 2385 | Setting::Handle<bool> firstRun{ Settings::firstRun, true }; | 2477 | Setting::Handle<bool> firstRun{ Settings::firstRun, true }; |
| 2386 | 2478 | ||
| 2387 | bool isTutorialComplete = tutorialComplete.get(); | 2479 | bool isTutorialComplete = tutorialComplete.get(); |
| 2388 | bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete; | 2480 | bool shouldGoToTutorial = isUsingHMDAndHandControllers && hasTutorialContent && !isTutorialComplete; |
| 2389 | 2481 | ||
| 2390 | qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD; | 2482 | qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMDAndHandControllers; |
| 2391 | qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent << | 2483 | qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent << |
| 2392 | ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial; | 2484 | ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial; |
| 2393 | 2485 | ||
| @@ -2401,10 +2493,18 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 7 unmodified lines | |||
| 2401 | 2493 | ||
| 2402 | const QString TUTORIAL_PATH = "/tutorial_begin"; | 2494 | const QString TUTORIAL_PATH = "/tutorial_begin"; |
| 2403 | 2495 | ||
| 2496 | static const QString SENT_TO_TUTORIAL = "tutorial"; | ||
| 2497 | static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location"; | ||
| 2498 | static const QString SENT_TO_ENTRY = "entry"; | ||
| 2499 | static const QString SENT_TO_SANDBOX = "sandbox"; | ||
| 2500 | |||
| 2501 | QString sentTo; | ||
| 2502 | |||
| 2404 | if (shouldGoToTutorial) { | 2503 | if (shouldGoToTutorial) { |
| 2405 | if (sandboxIsRunning) { | 2504 | if (sandboxIsRunning) { |
| 2406 | qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; | 2505 | qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; |
| 2407 | DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH); | 2506 | DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH); |
| 2507 | sentTo = SENT_TO_TUTORIAL; | ||
| 2408 | } else { | 2508 | } else { |
| 2409 | qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; | 2509 | qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; |
| 2410 | if (firstRun.get()) { | 2510 | if (firstRun.get()) { |
| @@ -2412,8 +2512,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 1 unmodified line | |||
| 2412 | } | 2512 | } |
| 2413 | if (addressLookupString.isEmpty()) { | 2513 | if (addressLookupString.isEmpty()) { |
| 2414 | DependencyManager::get<AddressManager>()->goToEntry(); | 2514 | DependencyManager::get<AddressManager>()->goToEntry(); |
| 2515 | sentTo = SENT_TO_ENTRY; | ||
| 2415 | } else { | 2516 | } else { |
| 2416 | DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); | 2517 | DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); |
| 2518 | sentTo = SENT_TO_PREVIOUS_LOCATION; | ||
| 2417 | } | 2519 | } |
| 2418 | } | 2520 | } |
| 2419 | } else { | 2521 | } else { |
| @@ -2426,23 +2528,40 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { · 6 unmodified lines | |||
| 2426 | 2528 | ||
| 2427 | // If this is a first run we short-circuit the address passed in | 2529 | // If this is a first run we short-circuit the address passed in |
| 2428 | if (isFirstRun) { | 2530 | if (isFirstRun) { |
| 2429 | if (isUsingHMD) { | 2531 | if (isUsingHMDAndHandControllers) { |
| 2430 | if (sandboxIsRunning) { | 2532 | if (sandboxIsRunning) { |
| 2431 | qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; | 2533 | qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; |
| 2432 | DependencyManager::get<AddressManager>()->goToLocalSandbox(); | 2534 | DependencyManager::get<AddressManager>()->goToLocalSandbox(); |
| 2535 | sentTo = SENT_TO_SANDBOX; | ||
| 2433 | } else { | 2536 | } else { |
| 2434 | qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; | 2537 | qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; |
| 2435 | DependencyManager::get<AddressManager>()->goToEntry(); | 2538 | DependencyManager::get<AddressManager>()->goToEntry(); |
| 2539 | sentTo = SENT_TO_ENTRY; | ||
| 2436 | } | 2540 | } |
| 2437 | } else { | 2541 | } else { |
| 2438 | DependencyManager::get<AddressManager>()->goToEntry(); | 2542 | DependencyManager::get<AddressManager>()->goToEntry(); |
| 2543 | sentTo = SENT_TO_ENTRY; | ||
| 2439 | } | 2544 | } |
| 2440 | } else { | 2545 | } else { |
| 2441 | qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); | 2546 | qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); |
| 2442 | DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); | 2547 | DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); |
| 2548 | sentTo = SENT_TO_PREVIOUS_LOCATION; | ||
| 2443 | } | 2549 | } |
| 2444 | } | 2550 | } |
| 2445 | 2551 | ||
| 2552 | UserActivityLogger::getInstance().logAction("startup_sent_to", { | ||
| 2553 | { "sent_to", sentTo }, | ||
| 2554 | { "sandbox_is_running", sandboxIsRunning }, | ||
| 2555 | { "has_hmd", hasHMD }, | ||
| 2556 | { "has_hand_controllers", hasHandControllers }, | ||
| 2557 | { "is_using_hmd", isUsingHMD }, | ||
| 2558 | { "is_using_hmd_and_hand_controllers", isUsingHMDAndHandControllers }, | ||
| 2559 | { "content_version", contentVersion }, | ||
| 2560 | { "is_tutorial_complete", isTutorialComplete }, | ||
| 2561 | { "has_tutorial_content", hasTutorialContent }, | ||
| 2562 | { "should_go_to_tutorial", shouldGoToTutorial } | ||
| 2563 | }); | ||
| 2564 | |||
| 2446 | _connectionMonitor.init(); | 2565 | _connectionMonitor.init(); |
| 2447 | 2566 | ||
| 2448 | // After all of the constructor is completed, then set firstRun to false. | 2567 | // After all of the constructor is completed, then set firstRun to false. |
| @@ -2777,6 +2896,17 @@ void Application::keyPressEvent(QKeyEvent* event) { · 328 unmodified lines | |||
| 2777 | if (isShifted && isMeta && !isOption) { | 2896 | if (isShifted && isMeta && !isOption) { |
| 2778 | Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); | 2897 | Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); |
| 2779 | } else if (!isOption && !isShifted && isMeta) { | 2898 | } else if (!isOption && !isShifted && isMeta) { |
| 2899 | AudioInjectorOptions options; | ||
| 2900 | options.localOnly = true; | ||
| 2901 | options.stereo = true; | ||
| 2902 | |||
| 2903 | if (_snapshotSoundInjector) { | ||
| 2904 | _snapshotSoundInjector->setOptions(options); | ||
| 2905 | _snapshotSoundInjector->restart(); | ||
| 2906 | } else { | ||
| 2907 | QByteArray samples = _snapshotSound->getByteArray(); | ||
| 2908 | _snapshotSoundInjector = AudioInjector::playSound(samples, options); | ||
| 2909 | } | ||
| 2780 | takeSnapshot(true); | 2910 | takeSnapshot(true); |
| 2781 | } | 2911 | } |
| 2782 | break; | 2912 | break; |
| @@ -3951,6 +4081,7 @@ void Application::updateMyAvatarLookAtPosition() { · 1168 unmodified lines | |||
| 3951 | lookAtPosition.x = -lookAtPosition.x; | 4081 | lookAtPosition.x = -lookAtPosition.x; |
| 3952 | } | 4082 | } |
| 3953 | if (isHMD) { | 4083 | if (isHMD) { |
| 4084 | // TODO -- this code is probably wrong, getHeadPose() returns something in sensor frame, not avatar | ||
| 3954 | glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); | 4085 | glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); |
| 3955 | glm::quat hmdRotation = glm::quat_cast(headPose); | 4086 | glm::quat hmdRotation = glm::quat_cast(headPose); |
| 3956 | lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition); | 4087 | lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition); |
| @@ -3992,9 +4123,10 @@ void Application::updateMyAvatarLookAtPosition() { · 35 unmodified lines | |||
| 3992 | } | 4123 | } |
| 3993 | } else { | 4124 | } else { |
| 3994 | // I am not looking at anyone else, so just look forward | 4125 | // I am not looking at anyone else, so just look forward |
| 3995 | if (isHMD) { | 4126 | auto headPose = myAvatar->getHeadControllerPoseInSensorFrame(); |
| 3996 | glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); | 4127 | if (headPose.isValid()) { |
| 3997 | lookAtSpot = transformPoint(worldHMDMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); | 4128 | glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * headPose.getMatrix(); |
| 4129 | lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE)); | ||
| 3998 | } else { | 4130 | } else { |
| 3999 | lookAtSpot = myAvatar->getHead()->getEyePosition() + | 4131 | lookAtSpot = myAvatar->getHead()->getEyePosition() + |
| 4000 | (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); | 4132 | (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); |
| @@ -4492,12 +4624,13 @@ void Application::update(float deltaTime) { · 491 unmodified lines | |||
| 4492 | 4624 | ||
| 4493 | getEntities()->getTree()->withWriteLock([&] { | 4625 | getEntities()->getTree()->withWriteLock([&] { |
| 4494 | PerformanceTimer perfTimer("handleOutgoingChanges"); | 4626 | PerformanceTimer perfTimer("handleOutgoingChanges"); |
| 4495 | const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates(); | ||
| 4496 | _entitySimulation->handleDeactivatedMotionStates(deactivations); | ||
| 4497 | 4627 | ||
| 4498 | const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); | 4628 | const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); |
| 4499 | _entitySimulation->handleChangedMotionStates(outgoingChanges); | 4629 | _entitySimulation->handleChangedMotionStates(outgoingChanges); |
| 4500 | avatarManager->handleChangedMotionStates(outgoingChanges); | 4630 | avatarManager->handleChangedMotionStates(outgoingChanges); |
| 4631 | |||
| 4632 | const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates(); | ||
| 4633 | _entitySimulation->handleDeactivatedMotionStates(deactivations); | ||
| 4501 | }); | 4634 | }); |
| 4502 | 4635 | ||
| 4503 | if (!_aboutToQuit) { | 4636 | if (!_aboutToQuit) { |
| @@ -4885,12 +5018,6 @@ QRect Application::getDesirableApplicationGeometry() const { · 381 unmodified lines | |||
| 4885 | return applicationGeometry; | 5018 | return applicationGeometry; |
| 4886 | } | 5019 | } |
| 4887 | 5020 | ||
| 4888 | glm::vec3 Application::getSunDirection() const { | ||
| 4889 | // Sun direction is in fact just the location of the sun relative to the origin | ||
| 4890 | auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); | ||
| 4891 | return skyStage->getSunLight()->getDirection(); | ||
| 4892 | } | ||
| 4893 | |||
| 4894 | // FIXME, preprocessor guard this check to occur only in DEBUG builds | 5021 | // FIXME, preprocessor guard this check to occur only in DEBUG builds |
| 4895 | static QThread * activeRenderingThread = nullptr; | 5022 | static QThread * activeRenderingThread = nullptr; |
| 4896 | 5023 | ||
| @@ -4947,7 +5074,7 @@ namespace render { · 50 unmodified lines | |||
| 4947 | template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } | 5074 | template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } |
| 4948 | template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } | 5075 | template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } |
| 4949 | template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { | 5076 | template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { |
| 4950 | if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { | 5077 | if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { |
| 4951 | PerformanceTimer perfTimer("worldBox"); | 5078 | PerformanceTimer perfTimer("worldBox"); |
| 4952 | 5079 | ||
| 4953 | auto& batch = *args->_batch; | 5080 | auto& batch = *args->_batch; |
| @@ -4957,90 +5084,6 @@ namespace render { · 3 unmodified lines | |||
| 4957 | } | 5084 | } |
| 4958 | } | 5085 | } |
| 4959 | 5086 | ||
| 4960 | // Background Render Data & rendering functions | ||
| 4961 | class BackgroundRenderData { | ||
| 4962 | public: | ||
| 4963 | typedef render::Payload<BackgroundRenderData> Payload; | ||
| 4964 | typedef Payload::DataPointer Pointer; | ||
| 4965 | |||
| 4966 | static render::ItemID _item; // unique WorldBoxRenderData | ||
| 4967 | }; | ||
| 4968 | |||
| 4969 | render::ItemID BackgroundRenderData::_item = 0; | ||
| 4970 | |||
| 4971 | namespace render { | ||
| 4972 | template <> const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) { | ||
| 4973 | return ItemKey::Builder::background(); | ||
| 4974 | } | ||
| 4975 | |||
| 4976 | template <> const Item::Bound payloadGetBound(const BackgroundRenderData::Pointer& stuff) { | ||
| 4977 | return Item::Bound(); | ||
| 4978 | } | ||
| 4979 | |||
| 4980 | template <> void payloadRender(const BackgroundRenderData::Pointer& background, RenderArgs* args) { | ||
| 4981 | Q_ASSERT(args->_batch); | ||
| 4982 | gpu::Batch& batch = *args->_batch; | ||
| 4983 | |||
| 4984 | // Background rendering decision | ||
| 4985 | auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); | ||
| 4986 | auto backgroundMode = skyStage->getBackgroundMode(); | ||
| 4987 | |||
| 4988 | switch (backgroundMode) { | ||
| 4989 | case model::SunSkyStage::SKY_DEFAULT: { | ||
| 4990 | auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage(); | ||
| 4991 | auto sceneKeyLight = scene->getKeyLight(); | ||
| 4992 | |||
| 4993 | scene->setSunModelEnable(false); | ||
| 4994 | sceneKeyLight->setColor(ColorUtils::toVec3(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR)); | ||
| 4995 | sceneKeyLight->setIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY); | ||
| 4996 | sceneKeyLight->setAmbientIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY); | ||
| 4997 | sceneKeyLight->setDirection(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION); | ||
| 4998 | // fall through: render a skybox (if available), or the defaults (if requested) | ||
| 4999 | } | ||
| 5000 | |||
| 5001 | case model::SunSkyStage::SKY_BOX: { | ||
| 5002 | auto skybox = skyStage->getSkybox(); | ||
| 5003 | if (!skybox->empty()) { | ||
| 5004 | PerformanceTimer perfTimer("skybox"); | ||
| 5005 | skybox->render(batch, args->getViewFrustum()); | ||
| 5006 | break; | ||
| 5007 | } | ||
| 5008 | // fall through: render defaults (if requested) | ||
| 5009 | } | ||
| 5010 | |||
| 5011 | case model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE: { | ||
| 5012 | if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) { | ||
| 5013 | auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage(); | ||
| 5014 | auto sceneKeyLight = scene->getKeyLight(); | ||
| 5015 | auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture(); | ||
| 5016 | if (defaultSkyboxAmbientTexture) { | ||
| 5017 | sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance()); | ||
| 5018 | sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture); | ||
| 5019 | } else { | ||
| 5020 | static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( | ||
| 5021 | "Failed to get a valid Default Skybox Ambient Texture ? probably because it couldn't be find during initialization step"); | ||
| 5022 | } | ||
| 5023 | // fall through: render defaults skybox | ||
| 5024 | } else { | ||
| 5025 | break; | ||
| 5026 | } | ||
| 5027 | } | ||
| 5028 | |||
| 5029 | case model::SunSkyStage::SKY_DEFAULT_TEXTURE: | ||
| 5030 | if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) { | ||
| 5031 | qApp->getDefaultSkybox()->render(batch, args->getViewFrustum()); | ||
| 5032 | } | ||
| 5033 | break; | ||
| 5034 | |||
| 5035 | // Any other cases require no extra rendering | ||
| 5036 | case model::SunSkyStage::NO_BACKGROUND: | ||
| 5037 | default: | ||
| 5038 | break; | ||
| 5039 | } | ||
| 5040 | } | ||
| 5041 | } | ||
| 5042 | |||
| 5043 | |||
| 5044 | void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) { | 5087 | void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) { |
| 5045 | 5088 | ||
| 5046 | // FIXME: This preDisplayRender call is temporary until we create a separate render::scene for the mirror rendering. | 5089 | // FIXME: This preDisplayRender call is temporary until we create a separate render::scene for the mirror rendering. |
| @@ -5064,15 +5107,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se · 17 unmodified lines | |||
| 5064 | // The pending changes collecting the changes here | 5107 | // The pending changes collecting the changes here |
| 5065 | render::Transaction transaction; | 5108 | render::Transaction transaction; |
| 5066 | 5109 | ||
| 5067 | // FIXME: Move this out of here!, Background / skybox should be driven by the enityt content just like the other entities | ||
| 5068 | // Background rendering decision | ||
| 5069 | if (!render::Item::isValidID(BackgroundRenderData::_item)) { | ||
| 5070 | auto backgroundRenderData = make_shared<BackgroundRenderData>(); | ||
| 5071 | auto backgroundRenderPayload = make_shared<BackgroundRenderData::Payload>(backgroundRenderData); | ||
| 5072 | BackgroundRenderData::_item = _main3DScene->allocateID(); | ||
| 5073 | transaction.resetItem(BackgroundRenderData::_item, backgroundRenderPayload); | ||
| 5074 | } | ||
| 5075 | |||
| 5076 | // Assuming nothing gets rendered through that | 5110 | // Assuming nothing gets rendered through that |
| 5077 | if (!selfAvatarOnly) { | 5111 | if (!selfAvatarOnly) { |
| 5078 | if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { | 5112 | if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { |
| @@ -5108,12 +5142,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se · 29 unmodified lines | |||
| 5108 | }); | 5142 | }); |
| 5109 | } | 5143 | } |
| 5110 | 5144 | ||
| 5111 | // Setup the current Zone Entity lighting | ||
| 5112 | { | ||
| 5113 | auto stage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); | ||
| 5114 | DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight()); | ||
| 5115 | } | ||
| 5116 | |||
| 5117 | { | 5145 | { |
| 5118 | PerformanceTimer perfTimer("SceneProcessTransaction"); | 5146 | PerformanceTimer perfTimer("SceneProcessTransaction"); |
| 5119 | _main3DScene->enqueueTransaction(transaction); | 5147 | _main3DScene->enqueueTransaction(transaction); |
| @@ -6384,21 +6412,6 @@ void Application::loadAddAvatarBookmarkDialog() const { · 1264 unmodified lines | |||
| 6384 | } | 6412 | } |
| 6385 | 6413 | ||
| 6386 | void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { | 6414 | void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { |
| 6387 | |||
| 6388 | //keep sound thread out of event loop scope | ||
| 6389 | |||
| 6390 | AudioInjectorOptions options; | ||
| 6391 | options.localOnly = true; | ||
| 6392 | options.stereo = true; | ||
| 6393 | |||
| 6394 | if (_snapshotSoundInjector) { | ||
| 6395 | _snapshotSoundInjector->setOptions(options); | ||
| 6396 | _snapshotSoundInjector->restart(); | ||
| 6397 | } else { | ||
| 6398 | QByteArray samples = _snapshotSound->getByteArray(); | ||
| 6399 | _snapshotSoundInjector = AudioInjector::playSound(samples, options); | ||
| 6400 | } | ||
| 6401 | |||
| 6402 | postLambdaEvent([notify, includeAnimated, aspectRatio, this] { | 6415 | postLambdaEvent([notify, includeAnimated, aspectRatio, this] { |
| 6403 | // Get a screenshot and save it | 6416 | // Get a screenshot and save it |
| 6404 | QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); | 6417 | QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); |
| @@ -6811,6 +6824,35 @@ void Application::updateDisplayMode() { · 406 unmodified lines | |||
| 6811 | Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); | 6824 | Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); |
| 6812 | } | 6825 | } |
| 6813 | 6826 | ||
| 6827 | void Application::switchDisplayMode() { | ||
| 6828 | if (!_autoSwitchDisplayModeSupportedHMDPlugin) { | ||
| 6829 | return; | ||
| 6830 | } | ||
| 6831 | bool currentHMDWornStatus = _autoSwitchDisplayModeSupportedHMDPlugin->isDisplayVisible(); | ||
| 6832 | if (currentHMDWornStatus != _previousHMDWornStatus) { | ||
| 6833 | // Switch to respective mode as soon as currentHMDWornStatus changes | ||
| 6834 | if (currentHMDWornStatus) { | ||
| 6835 | qCDebug(interfaceapp) << "Switching from Desktop to HMD mode"; | ||
| 6836 | endHMDSession(); | ||
| 6837 | setActiveDisplayPlugin(_autoSwitchDisplayModeSupportedHMDPluginName); | ||
| 6838 | } else { | ||
| 6839 | qCDebug(interfaceapp) << "Switching from HMD to desktop mode"; | ||
| 6840 | setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME); | ||
| 6841 | startHMDStandBySession(); | ||
| 6842 | } | ||
| 6843 | emit activeDisplayPluginChanged(); | ||
| 6844 | } | ||
| 6845 | _previousHMDWornStatus = currentHMDWornStatus; | ||
| 6846 | } | ||
| 6847 | |||
| 6848 | void Application::startHMDStandBySession() { | ||
| 6849 | _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession(); | ||
| 6850 | } | ||
| 6851 | |||
| 6852 | void Application::endHMDSession() { | ||
| 6853 | _autoSwitchDisplayModeSupportedHMDPlugin->endSession(); | ||
| 6854 | } | ||
| 6855 | |||
| 6814 | mat4 Application::getEyeProjection(int eye) const { | 6856 | mat4 Application::getEyeProjection(int eye) const { |
| 6815 | QMutexLocker viewLocker(&_viewMutex); | 6857 | QMutexLocker viewLocker(&_viewMutex); |
| 6816 | if (isHMDMode()) { | 6858 | if (isHMDMode()) { |
| @@ -439,7 +439,7 @@ private slots: · 438 unmodified lines | |||
| 439 | void addAssetToWorldErrorTimeout(); | 439 | void addAssetToWorldErrorTimeout(); |
| 440 | 440 | ||
| 441 | void handleSandboxStatus(QNetworkReply* reply); | 441 | void handleSandboxStatus(QNetworkReply* reply); |
| 442 | 442 | void switchDisplayMode(); | |
| 443 | private: | 443 | private: |
| 444 | static void initDisplay(); | 444 | static void initDisplay(); |
| 445 | void init(); | 445 | void init(); |
| @@ -457,8 +457,6 @@ private: · 11 unmodified lines | |||
| 457 | 457 | ||
| 458 | void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false); | 458 | void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false); |
| 459 | 459 | ||
| 460 | glm::vec3 getSunDirection() const; | ||
| 461 | |||
| 462 | void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed); | 460 | void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed); |
| 463 | 461 | ||
| 464 | int sendNackPackets(); | 462 | int sendNackPackets(); |
| @@ -679,7 +677,11 @@ private: · 214 unmodified lines | |||
| 679 | FileScriptingInterface* _fileDownload; | 677 | FileScriptingInterface* _fileDownload; |
| 680 | AudioInjector* _snapshotSoundInjector { nullptr }; | 678 | AudioInjector* _snapshotSoundInjector { nullptr }; |
| 681 | SharedSoundPointer _snapshotSound; | 679 | SharedSoundPointer _snapshotSound; |
| 680 | |||
| 681 | DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin; | ||
| 682 | QString _autoSwitchDisplayModeSupportedHMDPluginName; | ||
| 683 | bool _previousHMDWornStatus; | ||
| 684 | void startHMDStandBySession(); | ||
| 685 | void endHMDSession(); | ||
| 682 | }; | 686 | }; |
| 683 | |||
| 684 | |||
| 685 | #endif // hifi_Application_h | 687 | #endif // hifi_Application_h |
| @@ -21,15 +21,35 @@ · 20 unmodified lines | |||
| 21 | 21 | ||
| 22 | #include "MainWindow.h" | 22 | #include "MainWindow.h" |
| 23 | #include "Menu.h" | 23 | #include "Menu.h" |
| 24 | |||
| 25 | #include "AvatarBookmarks.h" | 24 | #include "AvatarBookmarks.h" |
| 25 | #include "InterfaceLogging.h" | ||
| 26 | |||
| 26 | #include <QtQuick/QQuickWindow> | 27 | #include <QtQuick/QQuickWindow> |
| 27 | 28 | ||
| 28 | AvatarBookmarks::AvatarBookmarks() { | 29 | AvatarBookmarks::AvatarBookmarks() { |
| 29 | _bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; | 30 | _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME; |
| 30 | readFromFile(); | 31 | readFromFile(); |
| 31 | } | 32 | } |
| 32 | 33 | ||
| 34 | void AvatarBookmarks::readFromFile() { | ||
| 35 | // migrate old avatarbookmarks.json, used to be in 'local' folder on windows | ||
| 36 | QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; | ||
| 37 | QFile oldConfig(oldConfigPath); | ||
| 38 | |||
| 39 | // I imagine that in a year from now, this code for migrating (as well as the two lines above) | ||
| 40 | // may be removed since all bookmarks should have been migrated by then | ||
| 41 | // - Robbie Uvanni (6.8.2017) | ||
| 42 | if (oldConfig.exists()) { | ||
| 43 | if (QDir().rename(oldConfigPath, _bookmarksFilename)) { | ||
| 44 | qCDebug(interfaceapp) << "Successfully migrated" << AVATARBOOKMARKS_FILENAME; | ||
| 45 | } else { | ||
| 46 | qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | Bookmarks::readFromFile(); | ||
| 51 | } | ||
| 52 | |||
| 33 | void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { | 53 | void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { |
| 34 | // Add menus/actions | 54 | // Add menus/actions |
| 35 | auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); | 55 | auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); |
| @@ -29,6 +29,7 @@ public slots: · 28 unmodified lines | |||
| 29 | 29 | ||
| 30 | protected: | 30 | protected: |
| 31 | void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override; | 31 | void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override; |
| 32 | void readFromFile(); | ||
| 32 | 33 | ||
| 33 | private: | 34 | private: |
| 34 | const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; | 35 | const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; |
| @@ -24,12 +24,15 @@ · 23 unmodified lines | |||
| 24 | 24 | ||
| 25 | #include "Application.h" | 25 | #include "Application.h" |
| 26 | #include "Menu.h" | 26 | #include "Menu.h" |
| 27 | #include <SettingHandle.h> | ||
| 28 | 27 | ||
| 29 | #include <RunningMarker.h> | 28 | #include <RunningMarker.h> |
| 29 | #include <SettingHandle.h> | ||
| 30 | #include <SettingHelpers.h> | ||
| 31 | |||
| 30 | 32 | ||
| 31 | bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) { | 33 | bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) { |
| 32 | Settings settings; | 34 | QSettings::setDefaultFormat(JSON_FORMAT); |
| 35 | QSettings settings; | ||
| 33 | settings.beginGroup("Developer"); | 36 | settings.beginGroup("Developer"); |
| 34 | QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions); | 37 | QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions); |
| 35 | QVariant askToResetSettingsOption = settings.value(MenuOption::AskToResetSettings); | 38 | QVariant askToResetSettingsOption = settings.value(MenuOption::AskToResetSettings); |
| @@ -70,7 +73,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { · 34 unmodified lines | |||
| 70 | layout->addWidget(label); | 73 | layout->addWidget(label); |
| 71 | 74 | ||
| 72 | QRadioButton* option1 = new QRadioButton("Reset all my settings"); | 75 | QRadioButton* option1 = new QRadioButton("Reset all my settings"); |
| 73 | QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info."); | 76 | QRadioButton* option2 = new QRadioButton("Reset my settings but keep essential info"); |
| 74 | QRadioButton* option3 = new QRadioButton("Continue with my current settings"); | 77 | QRadioButton* option3 = new QRadioButton("Continue with my current settings"); |
| 75 | option3->setChecked(true); | 78 | option3->setChecked(true); |
| 76 | layout->addWidget(option1); | 79 | layout->addWidget(option1); |
| @@ -92,7 +95,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { · 15 unmodified lines | |||
| 92 | return CrashHandler::DELETE_INTERFACE_INI; | 95 | return CrashHandler::DELETE_INTERFACE_INI; |
| 93 | } | 96 | } |
| 94 | if (option2->isChecked()) { | 97 | if (option2->isChecked()) { |
| 95 | return CrashHandler::RETAIN_AVATAR_INFO; | 98 | return CrashHandler::RETAIN_IMPORTANT_INFO; |
| 96 | } | 99 | } |
| 97 | } | 100 | } |
| 98 | 101 | ||
| @@ -101,24 +104,27 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { · 2 unmodified lines | |||
| 101 | } | 104 | } |
| 102 | 105 | ||
| 103 | void CrashHandler::handleCrash(CrashHandler::Action action) { | 106 | void CrashHandler::handleCrash(CrashHandler::Action action) { |
| 104 | if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) { | 107 | if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_IMPORTANT_INFO) { |
| 105 | // CrashHandler::DO_NOTHING or unexpected value | 108 | // CrashHandler::DO_NOTHING or unexpected value |
| 106 | return; | 109 | return; |
| 107 | } | 110 | } |
| 108 | 111 | ||
| 109 | Settings settings; | 112 | QSettings settings; |
| 110 | const QString ADDRESS_MANAGER_GROUP = "AddressManager"; | 113 | const QString ADDRESS_MANAGER_GROUP = "AddressManager"; |
| 111 | const QString ADDRESS_KEY = "address"; | 114 | const QString ADDRESS_KEY = "address"; |
| 112 | const QString AVATAR_GROUP = "Avatar"; | 115 | const QString AVATAR_GROUP = "Avatar"; |
| 113 | const QString DISPLAY_NAME_KEY = "displayName"; | 116 | const QString DISPLAY_NAME_KEY = "displayName"; |
| 114 | const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; | 117 | const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; |
| 115 | const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; | 118 | const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; |
| 119 | const QString TUTORIAL_COMPLETE_FLAG_KEY = "tutorialComplete"; | ||
| 120 | |||
| 116 | QString displayName; | 121 | QString displayName; |
| 117 | QUrl fullAvatarURL; | 122 | QUrl fullAvatarURL; |
| 118 | QString fullAvatarModelName; | 123 | QString fullAvatarModelName; |
| 119 | QUrl address; | 124 | QUrl address; |
| 125 | bool tutorialComplete = false; | ||
| 120 | 126 | ||
| 121 | if (action == CrashHandler::RETAIN_AVATAR_INFO) { | 127 | if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { |
| 122 | // Read avatar info | 128 | // Read avatar info |
| 123 | 129 | ||
| 124 | // Location and orientation | 130 | // Location and orientation |
| @@ -132,6 +138,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { · 7 unmodified lines | |||
| 132 | fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); | 138 | fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); |
| 133 | fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); | 139 | fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); |
| 134 | settings.endGroup(); | 140 | settings.endGroup(); |
| 141 | |||
| 142 | // Tutorial complete | ||
| 143 | tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool(); | ||
| 135 | } | 144 | } |
| 136 | 145 | ||
| 137 | // Delete Interface.ini | 146 | // Delete Interface.ini |
| @@ -140,7 +149,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { · 2 unmodified lines | |||
| 140 | settingsFile.remove(); | 149 | settingsFile.remove(); |
| 141 | } | 150 | } |
| 142 | 151 | ||
| 143 | if (action == CrashHandler::RETAIN_AVATAR_INFO) { | 152 | if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { |
| 144 | // Write avatar info | 153 | // Write avatar info |
| 145 | 154 | ||
| 146 | // Location and orientation | 155 | // Location and orientation |
| @@ -154,6 +163,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { · 7 unmodified lines | |||
| 154 | settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); | 163 | settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); |
| 155 | settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); | 164 | settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); |
| 156 | settings.endGroup(); | 165 | settings.endGroup(); |
| 166 | |||
| 167 | // Tutorial complete | ||
| 168 | settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete); | ||
| 157 | } | 169 | } |
| 158 | } | 170 | } |
| 159 | 171 | ||
| @@ -22,7 +22,7 @@ public: · 21 unmodified lines | |||
| 22 | private: | 22 | private: |
| 23 | enum Action { | 23 | enum Action { |
| 24 | DELETE_INTERFACE_INI, | 24 | DELETE_INTERFACE_INI, |
| 25 | RETAIN_AVATAR_INFO, | 25 | RETAIN_IMPORTANT_INFO, |
| 26 | DO_NOTHING | 26 | DO_NOTHING |
| 27 | }; | 27 | }; |
| 28 | 28 | ||
| @@ -25,7 +25,7 @@ · 24 unmodified lines | |||
| 25 | 25 | ||
| 26 | #include <QThread> | 26 | #include <QThread> |
| 27 | 27 | ||
| 28 | const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Friends; | 28 | const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Connections; |
| 29 | 29 | ||
| 30 | DiscoverabilityManager::DiscoverabilityManager() : | 30 | DiscoverabilityManager::DiscoverabilityManager() : |
| 31 | _mode("discoverabilityMode", DEFAULT_DISCOVERABILITY_MODE) | 31 | _mode("discoverabilityMode", DEFAULT_DISCOVERABILITY_MODE) |
| @@ -405,6 +405,12 @@ Menu::Menu() { · 404 unmodified lines | |||
| 405 | #endif | 405 | #endif |
| 406 | 406 | ||
| 407 | 407 | ||
| 408 | { | ||
| 409 | auto action = addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderClearKtxCache); | ||
| 410 | connect(action, &QAction::triggered, []{ | ||
| 411 | Setting::Handle<int>(KTXCache::SETTING_VERSION_NAME, KTXCache::INVALID_VERSION).set(KTXCache::INVALID_VERSION); | ||
| 412 | }); | ||
| 413 | } | ||
| 408 | 414 | ||
| 409 | // Developer > Render > LOD Tools | 415 | // Developer > Render > LOD Tools |
| 410 | addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, | 416 | addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, |
| @@ -142,6 +142,7 @@ namespace MenuOption { · 141 unmodified lines | |||
| 142 | const QString Quit = "Quit"; | 142 | const QString Quit = "Quit"; |
| 143 | const QString ReloadAllScripts = "Reload All Scripts"; | 143 | const QString ReloadAllScripts = "Reload All Scripts"; |
| 144 | const QString ReloadContent = "Reload Content (Clears all caches)"; | 144 | const QString ReloadContent = "Reload Content (Clears all caches)"; |
| 145 | const QString RenderClearKtxCache = "Clear KTX Cache (requires restart)"; | ||
| 145 | const QString RenderMaxTextureMemory = "Maximum Texture Memory"; | 146 | const QString RenderMaxTextureMemory = "Maximum Texture Memory"; |
| 146 | const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; | 147 | const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; |
| 147 | const QString RenderMaxTexture4MB = "4 MB"; | 148 | const QString RenderMaxTexture4MB = "4 MB"; |
| @@ -294,6 +294,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { · 293 unmodified lines | |||
| 294 | QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { | 294 | QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { |
| 295 | CameraMode mode = qApp->getCamera().getMode(); | 295 | CameraMode mode = qApp->getCamera().getMode(); |
| 296 | _globalPosition = getPosition(); | 296 | _globalPosition = getPosition(); |
| 297 | // This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17 | ||
| 297 | _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); | 298 | _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); |
| 298 | _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight(); | 299 | _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight(); |
| 299 | _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius(); | 300 | _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius(); |
| @@ -409,7 +410,7 @@ void MyAvatar::update(float deltaTime) { · 109 unmodified lines | |||
| 409 | // update moving average of HMD facing in xz plane. | 410 | // update moving average of HMD facing in xz plane. |
| 410 | const float HMD_FACING_TIMESCALE = 4.0f; // very slow average | 411 | const float HMD_FACING_TIMESCALE = 4.0f; // very slow average |
| 411 | float tau = deltaTime / HMD_FACING_TIMESCALE; | 412 | float tau = deltaTime / HMD_FACING_TIMESCALE; |
| 412 | _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); | 413 | _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); |
| 413 | 414 | ||
| 414 | if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { | 415 | if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { |
| 415 | _rotationChanged = usecTimestampNow(); | 416 | _rotationChanged = usecTimestampNow(); |
| @@ -417,16 +418,18 @@ void MyAvatar::update(float deltaTime) { · 1 unmodified line | |||
| 417 | } | 418 | } |
| 418 | 419 | ||
| 419 | #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE | 420 | #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE |
| 420 | glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y)); | 421 | glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getHeadControllerPoseInAvatarFrame() * |
| 422 | glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); | ||
| 421 | DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f)); | 423 | DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f)); |
| 422 | p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacing.x, 0.0f, _hmdSensorFacing.y)); | 424 | p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() + |
| 425 | glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); | ||
| 423 | DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f)); | 426 | DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f)); |
| 424 | #endif | 427 | #endif |
| 425 | 428 | ||
| 426 | if (_goToPending) { | 429 | if (_goToPending) { |
| 427 | setPosition(_goToPosition); | 430 | setPosition(_goToPosition); |
| 428 | setOrientation(_goToOrientation); | 431 | setOrientation(_goToOrientation); |
| 429 | _hmdSensorFacingMovingAverage = _hmdSensorFacing; // reset moving average | 432 | _headControllerFacingMovingAverage = _headControllerFacing; // reset moving average |
| 430 | _goToPending = false; | 433 | _goToPending = false; |
| 431 | // updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes | 434 | // updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes |
| 432 | // that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so). | 435 | // that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so). |
| @@ -434,6 +437,13 @@ void MyAvatar::update(float deltaTime) { · 1 unmodified line | |||
| 434 | // so we update now. It's ok if it updates again in the normal way. | 437 | // so we update now. It's ok if it updates again in the normal way. |
| 435 | updateSensorToWorldMatrix(); | 438 | updateSensorToWorldMatrix(); |
| 436 | emit positionGoneTo(); | 439 | emit positionGoneTo(); |
| 440 | // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. | ||
| 441 | _physicsSafetyPending = getCollisionsEnabled(); | ||
| 442 | } | ||
| 443 | if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { | ||
| 444 | // When needed and ready, arrange to check and fix. | ||
| 445 | _physicsSafetyPending = false; | ||
| 446 | safeLanding(_goToPosition); // no-op if already safe | ||
| 437 | } | 447 | } |
| 438 | 448 | ||
| 439 | Head* head = getHead(); | 449 | Head* head = getHead(); |
| @@ -448,6 +458,7 @@ void MyAvatar::update(float deltaTime) { · 8 unmodified lines | |||
| 448 | setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); | 458 | setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); |
| 449 | 459 | ||
| 450 | glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); | 460 | glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); |
| 461 | // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17 | ||
| 451 | halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); | 462 | halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); |
| 452 | QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", | 463 | QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", |
| 453 | Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), | 464 | Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), |
| @@ -633,15 +644,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { · 179 unmodified lines | |||
| 633 | _hmdSensorMatrix = hmdSensorMatrix; | 644 | _hmdSensorMatrix = hmdSensorMatrix; |
| 634 | auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix); | 645 | auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix); |
| 635 | 646 | ||
| 636 | if (newHmdSensorPosition != _hmdSensorPosition && | 647 | if (newHmdSensorPosition != getHMDSensorPosition() && |
| 637 | glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) { | 648 | glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) { |
| 638 | qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition; | 649 | qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition; |
| 639 | // Ignore unreasonable HMD sensor data | 650 | // Ignore unreasonable HMD sensor data |
| 640 | return; | 651 | return; |
| 641 | } | 652 | } |
| 653 | |||
| 642 | _hmdSensorPosition = newHmdSensorPosition; | 654 | _hmdSensorPosition = newHmdSensorPosition; |
| 643 | _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); | 655 | _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); |
| 644 | _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); | 656 | auto headPose = _headControllerPoseInSensorFrameCache.get(); |
| 657 | if (headPose.isValid()) { | ||
| 658 | _headControllerFacing = getFacingDir2D(headPose.rotation); | ||
| 659 | } else { | ||
| 660 | _headControllerFacing = glm::vec2(1.0f, 0.0f); | ||
| 661 | } | ||
| 645 | } | 662 | } |
| 646 | 663 | ||
| 647 | void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache<glm::mat4>& matrixCache) { | 664 | void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache<glm::mat4>& matrixCache) { |
| @@ -679,7 +696,7 @@ void MyAvatar::updateSensorToWorldMatrix() { · 31 unmodified lines | |||
| 679 | 696 | ||
| 680 | // Update avatar head rotation with sensor data | 697 | // Update avatar head rotation with sensor data |
| 681 | void MyAvatar::updateFromTrackers(float deltaTime) { | 698 | void MyAvatar::updateFromTrackers(float deltaTime) { |
| 682 | glm::vec3 estimatedPosition, estimatedRotation; | 699 | glm::vec3 estimatedRotation; |
| 683 | 700 | ||
| 684 | bool inHmd = qApp->isHMDMode(); | 701 | bool inHmd = qApp->isHMDMode(); |
| 685 | bool playing = DependencyManager::get<recording::Deck>()->isPlaying(); | 702 | bool playing = DependencyManager::get<recording::Deck>()->isPlaying(); |
| @@ -690,11 +707,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { · 4 unmodified lines | |||
| 690 | FaceTracker* tracker = qApp->getActiveFaceTracker(); | 707 | FaceTracker* tracker = qApp->getActiveFaceTracker(); |
| 691 | bool inFacetracker = tracker && !FaceTracker::isMuted(); | 708 | bool inFacetracker = tracker && !FaceTracker::isMuted(); |
| 692 | 709 | ||
| 693 | if (inHmd) { | 710 | if (inFacetracker) { |
| 694 | estimatedPosition = extractTranslation(getHMDSensorMatrix()); | ||
| 695 | estimatedPosition.x *= -1.0f; | ||
| 696 | } else if (inFacetracker) { | ||
| 697 | estimatedPosition = tracker->getHeadTranslation(); | ||
| 698 | estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); | 711 | estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); |
| 699 | } | 712 | } |
| 700 | 713 | ||
| @@ -781,6 +794,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const { · 80 unmodified lines | |||
| 781 | return pose; | 794 | return pose; |
| 782 | } | 795 | } |
| 783 | 796 | ||
| 797 | glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const { | ||
| 798 | glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified | ||
| 799 | glm::quat jointRot = getRotation();//default value if no or invalid joint specified | ||
| 800 | if (jointIndex != -1) { | ||
| 801 | if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { | ||
| 802 | _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); | ||
| 803 | } else { | ||
| 804 | qWarning() << "Invalid joint index specified: " << jointIndex; | ||
| 805 | } | ||
| 806 | } | ||
| 807 | glm::vec3 modelOffset = position - jointPos; | ||
| 808 | glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset; | ||
| 809 | |||
| 810 | return jointSpacePosition; | ||
| 811 | } | ||
| 812 | |||
| 813 | glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const { | ||
| 814 | glm::quat jointRot = getRotation();//default value if no or invalid joint specified | ||
| 815 | if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { | ||
| 816 | qWarning() << "Invalid joint index specified: " << jointIndex; | ||
| 817 | } | ||
| 818 | |||
| 819 | glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir; | ||
| 820 | return jointSpaceDir; | ||
| 821 | } | ||
| 822 | |||
| 823 | glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const { | ||
| 824 | glm::quat jointRot = getRotation();//default value if no or invalid joint specified | ||
| 825 | if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { | ||
| 826 | qWarning() << "Invalid joint index specified: " << jointIndex; | ||
| 827 | } | ||
| 828 | glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot; | ||
| 829 | return jointSpaceRot; | ||
| 830 | } | ||
| 831 | |||
| 832 | glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const { | ||
| 833 | glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified | ||
| 834 | glm::quat jointRot = getRotation();//default value if no or invalid joint specified | ||
| 835 | |||
| 836 | if (jointIndex != -1) { | ||
| 837 | if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { | ||
| 838 | _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); | ||
| 839 | } else { | ||
| 840 | qWarning() << "Invalid joint index specified: " << jointIndex; | ||
| 841 | } | ||
| 842 | } | ||
| 843 | |||
| 844 | glm::vec3 worldOffset = jointRot * jointSpacePos; | ||
| 845 | glm::vec3 worldPos = jointPos + worldOffset; | ||
| 846 | |||
| 847 | return worldPos; | ||
| 848 | } | ||
| 849 | |||
| 850 | glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const { | ||
| 851 | glm::quat jointRot = getRotation();//default value if no or invalid joint specified | ||
| 852 | if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { | ||
| 853 | qWarning() << "Invalid joint index specified: " << jointIndex; | ||
| 854 | } | ||
| 855 | glm::vec3 worldDir = jointRot * jointSpaceDir; | ||
| 856 | return worldDir; | ||
| 857 | } | ||
| 858 | |||
| 859 | glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const { | ||
| 860 | glm::quat jointRot = getRotation();//default value if no or invalid joint specified | ||
| 861 | if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { | ||
| 862 | qWarning() << "Invalid joint index specified: " << jointIndex; | ||
| 863 | } | ||
| 864 | glm::quat worldRot = jointRot * jointSpaceRot; | ||
| 865 | return worldRot; | ||
| 866 | } | ||
| 867 | |||
| 784 | // virtual | 868 | // virtual |
| 785 | void MyAvatar::render(RenderArgs* renderArgs) { | 869 | void MyAvatar::render(RenderArgs* renderArgs) { |
| 786 | // don't render if we've been asked to disable local rendering | 870 | // don't render if we've been asked to disable local rendering |
| @@ -1479,12 +1563,12 @@ void MyAvatar::updateMotors() { · 692 unmodified lines | |||
| 1479 | if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { | 1563 | if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { |
| 1480 | if (_characterController.getState() == CharacterController::State::Hover || | 1564 | if (_characterController.getState() == CharacterController::State::Hover || |
| 1481 | _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { | 1565 | _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { |
| 1482 | motorRotation = getMyHead()->getCameraOrientation(); | 1566 | motorRotation = getMyHead()->getHeadOrientation(); |
| 1483 | } else { | 1567 | } else { |
| 1484 | // non-hovering = walking: follow camera twist about vertical but not lift | 1568 | // non-hovering = walking: follow camera twist about vertical but not lift |
| 1485 | // so we decompose camera's rotation and store the twist part in motorRotation | 1569 | // so we decompose camera's rotation and store the twist part in motorRotation |
| 1486 | glm::quat liftRotation; | 1570 | glm::quat liftRotation; |
| 1487 | swingTwistDecomposition(getMyHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation); | 1571 | swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation); |
| 1488 | } | 1572 | } |
| 1489 | const float DEFAULT_MOTOR_TIMESCALE = 0.2f; | 1573 | const float DEFAULT_MOTOR_TIMESCALE = 0.2f; |
| 1490 | const float INVALID_MOTOR_TIMESCALE = 1.0e6f; | 1574 | const float INVALID_MOTOR_TIMESCALE = 1.0e6f; |
| @@ -1498,7 +1582,7 @@ void MyAvatar::updateMotors() { · 7 unmodified lines | |||
| 1498 | } | 1582 | } |
| 1499 | if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { | 1583 | if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { |
| 1500 | if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { | 1584 | if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { |
| 1501 | motorRotation = getMyHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); | 1585 | motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); |
| 1502 | } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { | 1586 | } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { |
| 1503 | motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); | 1587 | motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); |
| 1504 | } else { | 1588 | } else { |
| @@ -1548,6 +1632,10 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { · 43 unmodified lines | |||
| 1548 | 1632 | ||
| 1549 | if (_characterController.isEnabledAndReady()) { | 1633 | if (_characterController.isEnabledAndReady()) { |
| 1550 | setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); | 1634 | setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); |
| 1635 | if (_characterController.isStuck()) { | ||
| 1636 | _physicsSafetyPending = true; | ||
| 1637 | _goToPosition = getPosition(); | ||
| 1638 | } | ||
| 1551 | } else { | 1639 | } else { |
| 1552 | setVelocity(getVelocity() + _characterController.getFollowVelocity()); | 1640 | setVelocity(getVelocity() + _characterController.getFollowVelocity()); |
| 1553 | } | 1641 | } |
| @@ -1847,7 +1935,7 @@ void MyAvatar::updateOrientation(float deltaTime) { · 293 unmodified lines | |||
| 1847 | if (getCharacterController()->getState() == CharacterController::State::Hover) { | 1935 | if (getCharacterController()->getState() == CharacterController::State::Hover) { |
| 1848 | 1936 | ||
| 1849 | // This is the direction the user desires to fly in. | 1937 | // This is the direction the user desires to fly in. |
| 1850 | glm::vec3 desiredFacing = getMyHead()->getCameraOrientation() * Vectors::UNIT_Z; | 1938 | glm::vec3 desiredFacing = getMyHead()->getHeadOrientation() * Vectors::UNIT_Z; |
| 1851 | desiredFacing.y = 0.0f; | 1939 | desiredFacing.y = 0.0f; |
| 1852 | 1940 | ||
| 1853 | // This is our reference frame, it is captured when the user begins to move. | 1941 | // This is our reference frame, it is captured when the user begins to move. |
| @@ -1886,11 +1974,9 @@ void MyAvatar::updateOrientation(float deltaTime) { · 32 unmodified lines | |||
| 1886 | 1974 | ||
| 1887 | getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); | 1975 | getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); |
| 1888 | 1976 | ||
| 1889 | if (qApp->isHMDMode()) { | 1977 | auto headPose = getHeadControllerPoseInAvatarFrame(); |
| 1890 | glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation(); | 1978 | if (headPose.isValid()) { |
| 1891 | glm::quat bodyOrientation = getWorldBodyOrientation(); | 1979 | glm::quat localOrientation = headPose.rotation * Quaternions::Y_180; |
| 1892 | glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation; | ||
| 1893 | |||
| 1894 | // these angles will be in radians | 1980 | // these angles will be in radians |
| 1895 | // ... so they need to be converted to degrees before we do math... | 1981 | // ... so they need to be converted to degrees before we do math... |
| 1896 | glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN; | 1982 | glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN; |
| @@ -2004,11 +2090,14 @@ void MyAvatar::updatePosition(float deltaTime) { · 107 unmodified lines | |||
| 2004 | } | 2090 | } |
| 2005 | 2091 | ||
| 2006 | // capture the head rotation, in sensor space, when the user first indicates they would like to move/fly. | 2092 | // capture the head rotation, in sensor space, when the user first indicates they would like to move/fly. |
| 2007 | if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) { | 2093 | if (!_hoverReferenceCameraFacingIsCaptured && |
| 2094 | (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) { | ||
| 2008 | _hoverReferenceCameraFacingIsCaptured = true; | 2095 | _hoverReferenceCameraFacingIsCaptured = true; |
| 2009 | // transform the camera facing vector into sensor space. | 2096 | // transform the camera facing vector into sensor space. |
| 2010 | _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getMyHead()->getCameraOrientation() * Vectors::UNIT_Z); | 2097 | _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), |
| 2011 | } else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) { | 2098 | getMyHead()->getHeadOrientation() * Vectors::UNIT_Z); |
| 2099 | } else if (_hoverReferenceCameraFacingIsCaptured && | ||
| 2100 | (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) { | ||
| 2012 | _hoverReferenceCameraFacingIsCaptured = false; | 2101 | _hoverReferenceCameraFacingIsCaptured = false; |
| 2013 | } | 2102 | } |
| 2014 | } | 2103 | } |
| @@ -2219,6 +2308,144 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, · 204 unmodified lines | |||
| 2219 | emit transformChanged(); | 2308 | emit transformChanged(); |
| 2220 | } | 2309 | } |
| 2221 | 2310 | ||
| 2311 | void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. | ||
| 2312 | goToLocation(position); | ||
| 2313 | QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); | ||
| 2314 | } | ||
| 2315 | bool MyAvatar::safeLanding(const glm::vec3& position) { | ||
| 2316 | // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. | ||
| 2317 | // There needs to be a "landing" if: | ||
| 2318 | // a) the closest above and the closest below are less than the avatar capsule height apart, or | ||
| 2319 | // b) the above point is the top surface of an entity, indicating that we are inside it. | ||
| 2320 | // If no landing is required, we go to that point directly and return false; | ||
| 2321 | // When a landing is required by a, we find the highest intersection on that closest-agbove entity | ||
| 2322 | // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. | ||
| 2323 | // For b, use that top surface point. | ||
| 2324 | // We then place our feet there, recurse with new capsule center point, and return true. | ||
| 2325 | |||
| 2326 | if (QThread::currentThread() != thread()) { | ||
| 2327 | bool result; | ||
| 2328 | QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); | ||
| 2329 | return result; | ||
| 2330 | } | ||
| 2331 | glm::vec3 better; | ||
| 2332 | if (!requiresSafeLanding(position, better)) { | ||
| 2333 | return false; | ||
| 2334 | } | ||
| 2335 | if (!getCollisionsEnabled()) { | ||
| 2336 | goToLocation(better); // recurses on next update | ||
| 2337 | } else { // If you try to go while stuck, physics will keep you stuck. | ||
| 2338 | setCollisionsEnabled(false); | ||
| 2339 | // Don't goToLocation just yet. Yield so that physics can act on the above. | ||
| 2340 | QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick | ||
| 2341 | Q_ARG(glm::vec3, better)); | ||
| 2342 | } | ||
| 2343 | return true; | ||
| 2344 | } | ||
| 2345 | |||
| 2346 | // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. | ||
| 2347 | bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { | ||
| 2348 | // We begin with utilities and tests. The Algorithm in four parts is below. | ||
| 2349 | auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius(); | ||
| 2350 | if (halfHeight == 0) { | ||
| 2351 | return false; // zero height avatar | ||
| 2352 | } | ||
| 2353 | auto entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree(); | ||
| 2354 | if (!entityTree) { | ||
| 2355 | return false; // no entity tree | ||
| 2356 | } | ||
| 2357 | // More utilities. | ||
| 2358 | const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); | ||
| 2359 | const auto capsuleCenter = positionIn + offset; | ||
| 2360 | const auto up = _worldUpDirection, down = -up; | ||
| 2361 | glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; | ||
| 2362 | EntityItemID upperId, lowerId; | ||
| 2363 | QVector<EntityItemID> include{}, ignore{}; | ||
| 2364 | auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. | ||
| 2365 | betterPositionOut = upperIntersection + (up * halfHeight) - offset; | ||
| 2366 | return true; | ||
| 2367 | }; | ||
| 2368 | auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { | ||
| 2369 | OctreeElementPointer element; | ||
| 2370 | EntityItemPointer intersectedEntity = NULL; | ||
| 2371 | float distance; | ||
| 2372 | BoxFace face; | ||
| 2373 | const bool visibleOnly = false; | ||
| 2374 | // This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable. | ||
| 2375 | // What we really want is to use the collision hull! | ||
| 2376 | // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders | ||
| 2377 | const bool collidableOnly = true; | ||
| 2378 | const bool precisionPicking = true; | ||
| 2379 | const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? | ||
| 2380 | bool* accurateResult = NULL; | ||
| 2381 | |||
| 2382 | bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, | ||
| 2383 | element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult); | ||
| 2384 | if (!intersects || !intersectedEntity) { | ||
| 2385 | return false; | ||
| 2386 | } | ||
| 2387 | intersectionOut = startPointIn + (directionIn * distance); | ||
| 2388 | entityIdOut = intersectedEntity->getEntityItemID(); | ||
| 2389 | return true; | ||
| 2390 | }; | ||
| 2391 | |||
| 2392 | // The Algorithm, in four parts: | ||
| 2393 | |||
| 2394 | if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { | ||
| 2395 | // We currently believe that physics will reliably push us out if our feet are embedded, | ||
| 2396 | // as long as our capsule center is out and there's room above us. Here we have those | ||
| 2397 | // conditions, so no need to check our feet below. | ||
| 2398 | return false; // nothing above | ||
| 2399 | } | ||
| 2400 | |||
| 2401 | if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { | ||
| 2402 | // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. | ||
| 2403 | return false; // nothing below | ||
| 2404 | } | ||
| 2405 | |||
| 2406 | // See if we have room between entities above and below, but that we are not contained. | ||
| 2407 | // First check if the surface above us is the bottom of something, and the surface below us it the top of something. | ||
| 2408 | // I.e., we are in a clearing between two objects. | ||
| 2409 | if (isDown(upperNormal) && isUp(lowerNormal)) { | ||
| 2410 | auto spaceBetween = glm::distance(upperIntersection, lowerIntersection); | ||
| 2411 | const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. | ||
| 2412 | if (spaceBetween > (halfHeightFactor * halfHeight)) { | ||
| 2413 | // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. | ||
| 2414 | // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. | ||
| 2415 | // There will be one of two outcomes: | ||
| 2416 | // a) We're not contained, so we have enough room and our position is good. | ||
| 2417 | // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. | ||
| 2418 | const int iterationLimit = 1000; | ||
| 2419 | for (int counter = 0; counter < iterationLimit; counter++) { | ||
| 2420 | ignore.push_back(upperId); | ||
| 2421 | if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { | ||
| 2422 | // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! | ||
| 2423 | return false; // enough room | ||
| 2424 | } | ||
| 2425 | if (isUp(upperNormal)) { | ||
| 2426 | // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. | ||
| 2427 | // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top | ||
| 2428 | // of the enclosing entity, let's put our feet at upperIntersection and start over. | ||
| 2429 | return mustMove(); | ||
| 2430 | } | ||
| 2431 | // We found a new bottom surface, which we're not interested in. | ||
| 2432 | // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. | ||
| 2433 | } | ||
| 2434 | qCDebug(interfaceapp) << "Loop in requiresSafeLanding. Floor/ceiling do not make sense."; | ||
| 2435 | } | ||
| 2436 | } | ||
| 2437 | |||
| 2438 | include.push_back(upperId); // We're now looking for the intersection from above onto this entity. | ||
| 2439 | const float big = (float)TREE_SCALE; | ||
| 2440 | const auto skyHigh = up * big; | ||
| 2441 | auto fromAbove = capsuleCenter + skyHigh; | ||
| 2442 | if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { | ||
| 2443 | return false; // Unable to find a landing | ||
| 2444 | } | ||
| 2445 | // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. | ||
| 2446 | return mustMove(); | ||
| 2447 | } | ||
| 2448 | |||
| 2222 | void MyAvatar::updateMotionBehaviorFromMenu() { | 2449 | void MyAvatar::updateMotionBehaviorFromMenu() { |
| 2223 | 2450 | ||
| 2224 | if (QThread::currentThread() != thread()) { | 2451 | if (QThread::currentThread() != thread()) { |
| @@ -2322,36 +2549,27 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const { · 97 unmodified lines | |||
| 2322 | } | 2549 | } |
| 2323 | } | 2550 | } |
| 2324 | 2551 | ||
| 2325 | glm::vec3 MyAvatar::getWorldBodyPosition() const { | ||
| 2326 | return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix)); | ||
| 2327 | } | ||
| 2328 | |||
| 2329 | glm::quat MyAvatar::getWorldBodyOrientation() const { | ||
| 2330 | return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix); | ||
| 2331 | } | ||
| 2332 | |||
| 2333 | // old school meat hook style | 2552 | // old school meat hook style |
| 2334 | glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { | 2553 | glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { |
| 2335 | 2554 | glm::vec3 headPosition; | |
| 2336 | // HMD is in sensor space. | 2555 | glm::quat headOrientation; |
| 2337 | const glm::vec3 hmdPosition = getHMDSensorPosition(); | 2556 | auto headPose = getHeadControllerPoseInSensorFrame(); |
| 2338 | const glm::quat hmdOrientation = getHMDSensorOrientation(); | 2557 | if (headPose.isValid()) { |
| 2339 | const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); | 2558 | headPosition = getHeadControllerPoseInSensorFrame().translation; |
| 2559 | headOrientation = getHeadControllerPoseInSensorFrame().rotation * Quaternions::Y_180; | ||
| 2560 | } | ||
| 2561 | const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation); | ||
| 2340 | 2562 | ||
| 2341 | const Rig& rig = _skeletonModel->getRig(); | 2563 | const Rig& rig = _skeletonModel->getRig(); |
| 2342 | int rightEyeIndex = rig.indexOfJoint("RightEye"); | 2564 | int headIndex = rig.indexOfJoint("Head"); |
| 2343 | int leftEyeIndex = rig.indexOfJoint("LeftEye"); | ||
| 2344 | int neckIndex = rig.indexOfJoint("Neck"); | 2565 | int neckIndex = rig.indexOfJoint("Neck"); |
| 2345 | int hipsIndex = rig.indexOfJoint("Hips"); | 2566 | int hipsIndex = rig.indexOfJoint("Hips"); |
| 2346 | 2567 | ||
| 2347 | glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS; | 2568 | glm::vec3 rigHeadPos = headIndex != -1 ? rig.getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_AVATAR_HEAD_POS; |
| 2348 | if (leftEyeIndex >= 0 && rightEyeIndex >= 0) { | ||
| 2349 | rigMiddleEyePos = (rig.getAbsoluteDefaultPose(leftEyeIndex).trans() + rig.getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f; | ||
| 2350 | } | ||
| 2351 | glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; | 2569 | glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; |
| 2352 | glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; | 2570 | glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; |
| 2353 | 2571 | ||
| 2354 | glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos); | 2572 | glm::vec3 localHead = (rigHeadPos - rigHipsPos); |
| 2355 | glm::vec3 localNeck = (rigNeckPos - rigHipsPos); | 2573 | glm::vec3 localNeck = (rigNeckPos - rigHipsPos); |
| 2356 | 2574 | ||
| 2357 | // apply simplistic head/neck model | 2575 | // apply simplistic head/neck model |
| @@ -2360,11 +2578,11 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { · 2 unmodified lines | |||
| 2360 | // eyeToNeck offset is relative full HMD orientation. | 2578 | // eyeToNeck offset is relative full HMD orientation. |
| 2361 | // while neckToRoot offset is only relative to HMDs yaw. | 2579 | // while neckToRoot offset is only relative to HMDs yaw. |
| 2362 | // Y_180 is necessary because rig is z forward and hmdOrientation is -z forward | 2580 | // Y_180 is necessary because rig is z forward and hmdOrientation is -z forward |
| 2363 | glm::vec3 eyeToNeck = hmdOrientation * Quaternions::Y_180 * (localNeck - localEyes); | 2581 | glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); |
| 2364 | glm::vec3 neckToRoot = hmdOrientationYawOnly * Quaternions::Y_180 * -localNeck; | 2582 | glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; |
| 2365 | glm::vec3 bodyPos = hmdPosition + eyeToNeck + neckToRoot; | 2583 | glm::vec3 bodyPos = headPosition + headToNeck + neckToRoot; |
| 2366 | 2584 | ||
| 2367 | return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos); | 2585 | return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); |
| 2368 | } | 2586 | } |
| 2369 | 2587 | ||
| 2370 | glm::vec3 MyAvatar::getPositionForAudio() { | 2588 | glm::vec3 MyAvatar::getPositionForAudio() { |
| @@ -2480,7 +2698,7 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, co · 109 unmodified lines | |||
| 2480 | } else { | 2698 | } else { |
| 2481 | const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees | 2699 | const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees |
| 2482 | glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); | 2700 | glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); |
| 2483 | return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; | 2701 | return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; |
| 2484 | } | 2702 | } |
| 2485 | } | 2703 | } |
| 2486 | 2704 | ||
| @@ -2517,8 +2735,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co · 30 unmodified lines | |||
| 2517 | return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); | 2735 | return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); |
| 2518 | } | 2736 | } |
| 2519 | 2737 | ||
| 2520 | void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { | 2738 | void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, |
| 2521 | _desiredBodyMatrix = desiredBodyMatrix; | 2739 | const glm::mat4& currentBodyMatrix, bool hasDriveInput) { |
| 2522 | 2740 | ||
| 2523 | if (myAvatar.getHMDLeanRecenterEnabled()) { | 2741 | if (myAvatar.getHMDLeanRecenterEnabled()) { |
| 2524 | if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { | 2742 | if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { |
| @@ -2532,7 +2750,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat · 7 unmodified lines | |||
| 2532 | } | 2750 | } |
| 2533 | } | 2751 | } |
| 2534 | 2752 | ||
| 2535 | glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; | 2753 | glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix; |
| 2536 | glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; | 2754 | glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; |
| 2537 | 2755 | ||
| 2538 | AnimPose followWorldPose(currentWorldMatrix); | 2756 | AnimPose followWorldPose(currentWorldMatrix); |
| @@ -2627,9 +2845,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c · 88 unmodified lines | |||
| 2627 | cameraWorldMatrix *= createMatFromScaleQuatAndPos(vec3(-1.0f, 1.0f, 1.0f), glm::quat(), glm::vec3()); | 2845 | cameraWorldMatrix *= createMatFromScaleQuatAndPos(vec3(-1.0f, 1.0f, 1.0f), glm::quat(), glm::vec3()); |
| 2628 | } | 2846 | } |
| 2629 | 2847 | ||
| 2630 | // compute a NEW sensorToWorldMatrix for the camera. The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix. | 2848 | // compute a NEW sensorToWorldMatrix for the camera. |
| 2849 | // The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix. | ||
| 2631 | // here we solve for the unknown cameraSensorToWorldMatrix. | 2850 | // here we solve for the unknown cameraSensorToWorldMatrix. |
| 2632 | glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(_hmdSensorMatrix); | 2851 | glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(getHMDSensorMatrix()); |
| 2633 | 2852 | ||
| 2634 | // Using the new cameraSensorToWorldMatrix, compute where the controller is in world space. | 2853 | // Using the new cameraSensorToWorldMatrix, compute where the controller is in world space. |
| 2635 | glm::mat4 controllerWorldMatrix = cameraSensorToWorldMatrix * controllerSensorMatrix; | 2854 | glm::mat4 controllerWorldMatrix = cameraSensorToWorldMatrix * controllerSensorMatrix; |
| @@ -2770,7 +2989,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { · 134 unmodified lines | |||
| 2770 | auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); | 2989 | auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); |
| 2771 | return createMatFromQuatAndPos(leftFootRot, leftFootPos); | 2990 | return createMatFromQuatAndPos(leftFootRot, leftFootPos); |
| 2772 | } else { | 2991 | } else { |
| 2773 | return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS); | 2992 | return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS); |
| 2774 | } | 2993 | } |
| 2775 | } | 2994 | } |
| 2776 | 2995 | ||
| @@ -2782,7 +3001,7 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const { · 5 unmodified lines | |||
| 2782 | auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); | 3001 | auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); |
| 2783 | return createMatFromQuatAndPos(rightFootRot, rightFootPos); | 3002 | return createMatFromQuatAndPos(rightFootRot, rightFootPos); |
| 2784 | } else { | 3003 | } else { |
| 2785 | return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS); | 3004 | return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS); |
| 2786 | } | 3005 | } |
| 2787 | } | 3006 | } |
| 2788 | 3007 | ||
| @@ -2805,7 +3024,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { · 16 unmodified lines | |||
| 2805 | auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); | 3024 | auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); |
| 2806 | return createMatFromQuatAndPos(leftArmRot, leftArmPos); | 3025 | return createMatFromQuatAndPos(leftArmRot, leftArmPos); |
| 2807 | } else { | 3026 | } else { |
| 2808 | return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS); | 3027 | return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS); |
| 2809 | } | 3028 | } |
| 2810 | } | 3029 | } |
| 2811 | 3030 | ||
| @@ -185,7 +185,6 @@ public: · 184 unmodified lines | |||
| 185 | const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } | 185 | const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } |
| 186 | const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } | 186 | const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } |
| 187 | const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } | 187 | const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } |
| 188 | const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; } | ||
| 189 | 188 | ||
| 190 | Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); | 189 | Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); |
| 191 | Q_INVOKABLE QVariant getOrientationVar() const; | 190 | Q_INVOKABLE QVariant getOrientationVar() const; |
| @@ -379,6 +378,15 @@ public: · 187 unmodified lines | |||
| 379 | Q_INVOKABLE controller::Pose getLeftHandTipPose() const; | 378 | Q_INVOKABLE controller::Pose getLeftHandTipPose() const; |
| 380 | Q_INVOKABLE controller::Pose getRightHandTipPose() const; | 379 | Q_INVOKABLE controller::Pose getRightHandTipPose() const; |
| 381 | 380 | ||
| 381 | // world-space to avatar-space rigconversion functions | ||
| 382 | Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; | ||
| 383 | Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; | ||
| 384 | Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; | ||
| 385 | |||
| 386 | Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; | ||
| 387 | Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; | ||
| 388 | Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; | ||
| 389 | |||
| 382 | AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } | 390 | AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } |
| 383 | void updateLookAtTargetAvatar(); | 391 | void updateLookAtTargetAvatar(); |
| 384 | void clearLookAtTargetAvatar(); | 392 | void clearLookAtTargetAvatar(); |
| @@ -470,6 +478,8 @@ public: · 85 unmodified lines | |||
| 470 | controller::Pose getHeadControllerPoseInSensorFrame() const; | 478 | controller::Pose getHeadControllerPoseInSensorFrame() const; |
| 471 | controller::Pose getHeadControllerPoseInWorldFrame() const; | 479 | controller::Pose getHeadControllerPoseInWorldFrame() const; |
| 472 | controller::Pose getHeadControllerPoseInAvatarFrame() const; | 480 | controller::Pose getHeadControllerPoseInAvatarFrame() const; |
| 481 | const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } | ||
| 482 | |||
| 473 | 483 | ||
| 474 | void setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); | 484 | void setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); |
| 475 | controller::Pose getLeftArmControllerPoseInSensorFrame() const; | 485 | controller::Pose getLeftArmControllerPoseInSensorFrame() const; |
| @@ -509,6 +519,10 @@ public: · 33 unmodified lines | |||
| 509 | // results are in HMD frame | 519 | // results are in HMD frame |
| 510 | glm::mat4 deriveBodyFromHMDSensor() const; | 520 | glm::mat4 deriveBodyFromHMDSensor() const; |
| 511 | 521 | ||
| 522 | Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up. | ||
| 523 | Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; }; | ||
| 524 | |||
| 525 | |||
| 512 | public slots: | 526 | public slots: |
| 513 | void increaseSize(); | 527 | void increaseSize(); |
| 514 | void decreaseSize(); | 528 | void decreaseSize(); |
| @@ -518,6 +532,8 @@ public slots: · 3 unmodified lines | |||
| 518 | bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), | 532 | bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), |
| 519 | bool shouldFaceLocation = false); | 533 | bool shouldFaceLocation = false); |
| 520 | void goToLocation(const QVariant& properties); | 534 | void goToLocation(const QVariant& properties); |
| 535 | void goToLocationAndEnableCollisions(const glm::vec3& newPosition); | ||
| 536 | bool safeLanding(const glm::vec3& position); | ||
| 521 | 537 | ||
| 522 | void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); | 538 | void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); |
| 523 | void clearScaleRestriction(); | 539 | void clearScaleRestriction(); |
| @@ -563,9 +579,7 @@ signals: · 39 unmodified lines | |||
| 563 | 579 | ||
| 564 | private: | 580 | private: |
| 565 | 581 | ||
| 566 | glm::vec3 getWorldBodyPosition() const; | 582 | bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); |
| 567 | glm::quat getWorldBodyOrientation() const; | ||
| 568 | |||
| 569 | 583 | ||
| 570 | virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; | 584 | virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; |
| 571 | 585 | ||
| @@ -676,13 +690,13 @@ private: · 104 unmodified lines | |||
| 676 | // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access | 690 | // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access |
| 677 | glm::mat4 _sensorToWorldMatrix { glm::mat4() }; | 691 | glm::mat4 _sensorToWorldMatrix { glm::mat4() }; |
| 678 | 692 | ||
| 679 | // cache of the current HMD sensor position and orientation | 693 | // cache of the current HMD sensor position and orientation in sensor space. |
| 680 | // in sensor space. | ||
| 681 | glm::mat4 _hmdSensorMatrix; | 694 | glm::mat4 _hmdSensorMatrix; |
| 682 | glm::quat _hmdSensorOrientation; | 695 | glm::quat _hmdSensorOrientation; |
| 683 | glm::vec3 _hmdSensorPosition; | 696 | glm::vec3 _hmdSensorPosition; |
| 684 | glm::vec2 _hmdSensorFacing; // facing vector in xz plane | 697 | // cache head controller pose in sensor space |
| 685 | glm::vec2 _hmdSensorFacingMovingAverage { 0, 0 }; // facing vector in xz plane | 698 | glm::vec2 _headControllerFacing; // facing vector in xz plane |
| 699 | glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane | ||
| 686 | 700 | ||
| 687 | // cache of the current body position and orientation of the avatar's body, | 701 | // cache of the current body position and orientation of the avatar's body, |
| 688 | // in sensor space. | 702 | // in sensor space. |
| @@ -697,7 +711,6 @@ private: · 8 unmodified lines | |||
| 697 | Vertical, | 711 | Vertical, |
| 698 | NumFollowTypes | 712 | NumFollowTypes |
| 699 | }; | 713 | }; |
| 700 | glm::mat4 _desiredBodyMatrix; | ||
| 701 | float _timeRemaining[NumFollowTypes]; | 714 | float _timeRemaining[NumFollowTypes]; |
| 702 | 715 | ||
| 703 | void deactivate(); | 716 | void deactivate(); |
| @@ -716,7 +729,8 @@ private: · 12 unmodified lines | |||
| 716 | }; | 729 | }; |
| 717 | FollowHelper _follow; | 730 | FollowHelper _follow; |
| 718 | 731 | ||
| 719 | bool _goToPending; | 732 | bool _goToPending { false }; |
| 733 | bool _physicsSafetyPending { false }; | ||
| 720 | glm::vec3 _goToPosition; | 734 | glm::vec3 _goToPosition; |
| 721 | glm::quat _goToOrientation; | 735 | glm::quat _goToOrientation; |
| 722 | 736 | ||
| @@ -26,19 +26,20 @@ using namespace std; · 25 unmodified lines | |||
| 26 | MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) { | 26 | MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) { |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | glm::quat MyHead::getCameraOrientation() const { | 29 | glm::quat MyHead::getHeadOrientation() const { |
| 30 | // NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so | 30 | // NOTE: Head::getHeadOrientation() is not used for orienting the camera "view" while in Oculus mode, so |
| 31 | // you may wonder why this code is here. This method will be called while in Oculus mode to determine how | 31 | // you may wonder why this code is here. This method will be called while in Oculus mode to determine how |
| 32 | // to change the driving direction while in Oculus mode. It is used to support driving toward where you're | 32 | // to change the driving direction while in Oculus mode. It is used to support driving toward where you're |
| 33 | // head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not | 33 | // head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not |
| 34 | // always the same. | 34 | // always the same. |
| 35 | if (qApp->isHMDMode()) { | 35 | |
| 36 | MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar); | 36 | MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar); |
| 37 | return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation(); | 37 | auto headPose = myAvatar->getHeadControllerPoseInWorldFrame(); |
| 38 | } else { | 38 | if (headPose.isValid()) { |
| 39 | Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar); | 39 | return headPose.rotation * Quaternions::Y_180; |
| 40 | return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f))); | ||
| 41 | } | 40 | } |
| 41 | |||
| 42 | return myAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f))); | ||
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | void MyHead::simulate(float deltaTime) { | 45 | void MyHead::simulate(float deltaTime) { |
| @@ -18,7 +18,7 @@ public: · 17 unmodified lines | |||
| 18 | explicit MyHead(MyAvatar* owningAvatar); | 18 | explicit MyHead(MyAvatar* owningAvatar); |
| 19 | 19 | ||
| 20 | /// \return orientationBody * orientationBasePitch | 20 | /// \return orientationBody * orientationBasePitch |
| 21 | glm::quat getCameraOrientation() const; | 21 | glm::quat getHeadOrientation() const; |
| 22 | void simulate(float deltaTime) override; | 22 | void simulate(float deltaTime) override; |
| 23 | 23 | ||
| 24 | private: | 24 | private: |
| @@ -52,26 +52,18 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { · 51 unmodified lines | |||
| 52 | // input action is the highest priority source for head orientation. | 52 | // input action is the highest priority source for head orientation. |
| 53 | auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame(); | 53 | auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame(); |
| 54 | if (avatarHeadPose.isValid()) { | 54 | if (avatarHeadPose.isValid()) { |
| 55 | glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); | 55 | glm::mat4 rigHeadMat = Matrices::Y_180 * |
| 56 | createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); | ||
| 56 | headParams.rigHeadPosition = extractTranslation(rigHeadMat); | 57 | headParams.rigHeadPosition = extractTranslation(rigHeadMat); |
| 57 | headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat); | 58 | headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat); |
| 58 | headParams.headEnabled = true; | 59 | headParams.headEnabled = true; |
| 59 | } else { | 60 | } else { |
| 60 | if (qApp->isHMDMode()) { | 61 | // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and |
| 61 | // get HMD position from sensor space into world space, and back into rig space | 62 | // down in desktop mode. |
| 62 | glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); | 63 | // preMult 180 is necessary to convert from avatar to rig coordinates. |
| 63 | glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation()); | 64 | // postMult 180 is necessary to convert head from -z forward to z forward. |
| 64 | glm::mat4 worldToRig = glm::inverse(rigToWorld); | 65 | headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; |
| 65 | glm::mat4 rigHMDMat = worldToRig * worldHMDMat; | 66 | headParams.headEnabled = false; |
| 66 | _rig.computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation); | ||
| 67 | headParams.headEnabled = true; | ||
| 68 | } else { | ||
| 69 | // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode. | ||
| 70 | // preMult 180 is necessary to convert from avatar to rig coordinates. | ||
| 71 | // postMult 180 is necessary to convert head from -z forward to z forward. | ||
| 72 | headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; | ||
| 73 | headParams.headEnabled = false; | ||
| 74 | } | ||
| 75 | } | 67 | } |
| 76 | 68 | ||
| 77 | auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame(); | 69 | auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame(); |
| @@ -24,7 +24,6 @@ · 23 unmodified lines | |||
| 24 | #include <SandboxUtils.h> | 24 | #include <SandboxUtils.h> |
| 25 | #include <SharedUtil.h> | 25 | #include <SharedUtil.h> |
| 26 | 26 | ||
| 27 | |||
| 28 | #include "AddressManager.h" | 27 | #include "AddressManager.h" |
| 29 | #include "Application.h" | 28 | #include "Application.h" |
| 30 | #include "InterfaceLogging.h" | 29 | #include "InterfaceLogging.h" |
| @@ -191,7 +190,7 @@ int main(int argc, const char* argv[]) { · 160 unmodified lines | |||
| 191 | 190 | ||
| 192 | int exitCode; | 191 | int exitCode; |
| 193 | { | 192 | { |
| 194 | RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME); | 193 | RunningMarker runningMarker(RUNNING_MARKER_FILENAME); |
| 195 | bool runningMarkerExisted = runningMarker.fileExists(); | 194 | bool runningMarkerExisted = runningMarker.fileExists(); |
| 196 | runningMarker.writeRunningMarkerFile(); | 195 | runningMarker.writeRunningMarkerFile(); |
| 197 | 196 | ||
| @@ -200,14 +199,11 @@ int main(int argc, const char* argv[]) { · 2 unmodified lines | |||
| 200 | bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); | 199 | bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); |
| 201 | QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); | 200 | QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); |
| 202 | if (runServer) { | 201 | if (runServer) { |
| 203 | SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater); | 202 | SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater); |
| 204 | } | 203 | } |
| 205 | 204 | ||
| 206 | Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted); | 205 | Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted); |
| 207 | 206 | ||
| 208 | // Now that the main event loop is setup, launch running marker thread | ||
| 209 | runningMarker.startRunningMarker(); | ||
| 210 | |||
| 211 | // If we failed the OpenGLVersion check, log it. | 207 | // If we failed the OpenGLVersion check, log it. |
| 212 | if (override) { | 208 | if (override) { |
| 213 | auto accountManager = DependencyManager::get<AccountManager>(); | 209 | auto accountManager = DependencyManager::get<AccountManager>(); |
| @@ -0,0 +1,95 @@ | |||
| 1 | // | ||
| 2 | // CloseEventSender.cpp | ||
| 3 | // interface/src/networking | ||
| 4 | // | ||
| 5 | // Created by Stephen Birarda on 5/31/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #include <QtCore/QDateTime> | ||
| 13 | #include <QtCore/QEventLoop> | ||
| 14 | #include <QtCore/QJsonDocument> | ||
| 15 | #include <QtNetwork/QNetworkReply> | ||
| 16 | |||
| 17 | #include <ThreadHelpers.h> | ||
| 18 | #include <AccountManager.h> | ||
| 19 | #include <NetworkAccessManager.h> | ||
| 20 | #include <NetworkingConstants.h> | ||
| 21 | #include <NetworkLogging.h> | ||
| 22 | #include <UserActivityLogger.h> | ||
| 23 | #include <UUID.h> | ||
| 24 | |||
| 25 | #include "CloseEventSender.h" | ||
| 26 | |||
| 27 | QNetworkRequest createNetworkRequest() { | ||
| 28 | |||
| 29 | QNetworkRequest request; | ||
| 30 | |||
| 31 | QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; | ||
| 32 | requestURL.setPath(USER_ACTIVITY_URL); | ||
| 33 | |||
| 34 | request.setUrl(requestURL); | ||
| 35 | |||
| 36 | auto accountManager = DependencyManager::get<AccountManager>(); | ||
| 37 | |||
| 38 | if (accountManager->hasValidAccessToken()) { | ||
| 39 | request.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, | ||
| 40 | accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); | ||
| 41 | } | ||
| 42 | |||
| 43 | request.setRawHeader(METAVERSE_SESSION_ID_HEADER, | ||
| 44 | uuidStringWithoutCurlyBraces(accountManager->getSessionID()).toLocal8Bit()); | ||
| 45 | |||
| 46 | request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); | ||
| 47 | |||
| 48 | request.setPriority(QNetworkRequest::HighPriority); | ||
| 49 | |||
| 50 | return request; | ||
| 51 | } | ||
| 52 | |||
| 53 | QByteArray postDataForAction(QString action) { | ||
| 54 | return QString("{\"action_name\": \"" + action + "\"}").toUtf8(); | ||
| 55 | } | ||
| 56 | |||
| 57 | QNetworkReply* replyForAction(QString action) { | ||
| 58 | QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); | ||
| 59 | return networkAccessManager.post(createNetworkRequest(), postDataForAction(action)); | ||
| 60 | } | ||
| 61 | |||
| 62 | void CloseEventSender::sendQuitEventAsync() { | ||
| 63 | if (UserActivityLogger::getInstance().isEnabled()) { | ||
| 64 | QNetworkReply* reply = replyForAction("quit"); | ||
| 65 | connect(reply, &QNetworkReply::finished, this, &CloseEventSender::handleQuitEventFinished); | ||
| 66 | _quitEventStartTimestamp = QDateTime::currentMSecsSinceEpoch(); | ||
| 67 | } else { | ||
| 68 | _hasFinishedQuitEvent = true; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | void CloseEventSender::handleQuitEventFinished() { | ||
| 73 | _hasFinishedQuitEvent = true; | ||
| 74 | |||
| 75 | auto reply = qobject_cast<QNetworkReply*>(sender()); | ||
| 76 | if (reply->error() == QNetworkReply::NoError) { | ||
| 77 | qCDebug(networking) << "Quit event sent successfully"; | ||
| 78 | } else { | ||
| 79 | qCDebug(networking) << "Failed to send quit event -" << reply->errorString(); | ||
| 80 | } | ||
| 81 | |||
| 82 | reply->deleteLater(); | ||
| 83 | } | ||
| 84 | |||
| 85 | bool CloseEventSender::hasTimedOutQuitEvent() { | ||
| 86 | const int CLOSURE_EVENT_TIMEOUT_MS = 5000; | ||
| 87 | return _quitEventStartTimestamp != 0 | ||
| 88 | && QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS; | ||
| 89 | } | ||
| 90 | |||
| 91 | void CloseEventSender::startThread() { | ||
| 92 | moveToNewNamedThread(this, "CloseEvent Logger Thread", [this] { | ||
| 93 | sendQuitEventAsync(); | ||
| 94 | }); | ||
| 95 | } | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | // | ||
| 2 | // CloseEventSender.h | ||
| 3 | // interface/src/networking | ||
| 4 | // | ||
| 5 | // Created by Stephen Birarda on 5/31/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #ifndef hifi_CloseEventSender_h | ||
| 13 | #define hifi_CloseEventSender_h | ||
| 14 | |||
| 15 | #include <atomic> | ||
| 16 | |||
| 17 | #include <QtCore/QString> | ||
| 18 | #include <QtCore/QUuid> | ||
| 19 | |||
| 20 | #include <DependencyManager.h> | ||
| 21 | |||
| 22 | class CloseEventSender : public QObject, public Dependency { | ||
| 23 | Q_OBJECT | ||
| 24 | SINGLETON_DEPENDENCY | ||
| 25 | |||
| 26 | public: | ||
| 27 | void startThread(); | ||
| 28 | bool hasTimedOutQuitEvent(); | ||
| 29 | bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; } | ||
| 30 | |||
| 31 | public slots: | ||
| 32 | void sendQuitEventAsync(); | ||
| 33 | |||
| 34 | private slots: | ||
| 35 | void handleQuitEventFinished(); | ||
| 36 | |||
| 37 | private: | ||
| 38 | std::atomic<bool> _hasFinishedQuitEvent { false }; | ||
| 39 | std::atomic<int64_t> _quitEventStartTimestamp; | ||
| 40 | }; | ||
| 41 | |||
| 42 | #endif // hifi_CloseEventSender_h | ||
| @@ -54,6 +54,10 @@ bool HMDScriptingInterface::isHMDAvailable(const QString& name) { · 53 unmodified lines | |||
| 54 | return PluginUtils::isHMDAvailable(name); | 54 | return PluginUtils::isHMDAvailable(name); |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | bool HMDScriptingInterface::isHeadControllerAvailable(const QString& name) { | ||
| 58 | return PluginUtils::isHeadControllerAvailable(name); | ||
| 59 | } | ||
| 60 | |||
| 57 | bool HMDScriptingInterface::isHandControllerAvailable(const QString& name) { | 61 | bool HMDScriptingInterface::isHandControllerAvailable(const QString& name) { |
| 58 | return PluginUtils::isHandControllerAvailable(name); | 62 | return PluginUtils::isHandControllerAvailable(name); |
| 59 | } | 63 | } |
| @@ -43,6 +43,7 @@ public: · 42 unmodified lines | |||
| 43 | Q_INVOKABLE QString preferredAudioOutput() const; | 43 | Q_INVOKABLE QString preferredAudioOutput() const; |
| 44 | 44 | ||
| 45 | Q_INVOKABLE bool isHMDAvailable(const QString& name = ""); | 45 | Q_INVOKABLE bool isHMDAvailable(const QString& name = ""); |
| 46 | Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = ""); | ||
| 46 | Q_INVOKABLE bool isHandControllerAvailable(const QString& name = ""); | 47 | Q_INVOKABLE bool isHandControllerAvailable(const QString& name = ""); |
| 47 | Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name); | 48 | Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name); |
| 48 | 49 | ||
| @@ -9,9 +9,12 @@ · 8 unmodified lines | |||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 10 | // | 10 | // |
| 11 | 11 | ||
| 12 | #include <QtConcurrent/QtConcurrentRun> | ||
| 13 | |||
| 14 | #include <ThreadHelpers.h> | ||
| 12 | #include <src/InterfaceLogging.h> | 15 | #include <src/InterfaceLogging.h> |
| 13 | #include <src/ui/AvatarInputs.h> | 16 | #include <src/ui/AvatarInputs.h> |
| 14 | #include <QtConcurrent/QtConcurrentRun> | 17 | |
| 15 | #include "LimitlessVoiceRecognitionScriptingInterface.h" | 18 | #include "LimitlessVoiceRecognitionScriptingInterface.h" |
| 16 | 19 | ||
| 17 | const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f; | 20 | const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f; |
| @@ -24,9 +27,7 @@ LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingI · 6 unmodified lines | |||
| 24 | connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout); | 27 | connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout); |
| 25 | connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);}); | 28 | connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);}); |
| 26 | connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);}); | 29 | connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);}); |
| 27 | _connection.moveToThread(&_connectionThread); | 30 | moveToNewNamedThread(&_connection, "Limitless Connection"); |
| 28 | _connectionThread.setObjectName("Limitless Connection"); | ||
| 29 | _connectionThread.start(); | ||
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | void LimitlessVoiceRecognitionScriptingInterface::update() { | 33 | void LimitlessVoiceRecognitionScriptingInterface::update() { |
| @@ -41,7 +41,6 @@ private: · 40 unmodified lines | |||
| 41 | static const int _voiceTimeoutDuration; | 41 | static const int _voiceTimeoutDuration; |
| 42 | 42 | ||
| 43 | QTimer _voiceTimer; | 43 | QTimer _voiceTimer; |
| 44 | QThread _connectionThread; | ||
| 45 | LimitlessConnection _connection; | 44 | LimitlessConnection _connection; |
| 46 | 45 | ||
| 47 | void voiceTimeout(); | 46 | void voiceTimeout(); |
| @@ -23,6 +23,7 @@ · 22 unmodified lines | |||
| 23 | #include <QUrlQuery> | 23 | #include <QUrlQuery> |
| 24 | #include <QXmlStreamReader> | 24 | #include <QXmlStreamReader> |
| 25 | 25 | ||
| 26 | #include <ThreadHelpers.h> | ||
| 26 | #include <NetworkAccessManager.h> | 27 | #include <NetworkAccessManager.h> |
| 27 | #include <SharedUtil.h> | 28 | #include <SharedUtil.h> |
| 28 | 29 | ||
| @@ -84,12 +85,7 @@ ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) : · 55 unmodified lines | |||
| 84 | _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); | 85 | _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); |
| 85 | 86 | ||
| 86 | // Setup and launch update thread | 87 | // Setup and launch update thread |
| 87 | QThread* thread = new QThread(); | 88 | moveToNewNamedThread(_handler, "Models Browser"); |
| 88 | thread->setObjectName("Models Browser"); | ||
| 89 | thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit())); | ||
| 90 | thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); | ||
| 91 | _handler->moveToThread(thread); | ||
| 92 | thread->start(); | ||
| 93 | emit startDownloading(); | 89 | emit startDownloading(); |
| 94 | 90 | ||
| 95 | // Initialize the view | 91 | // Initialize the view |
| @@ -51,6 +51,16 @@ Text3DOverlay::~Text3DOverlay() { · 50 unmodified lines | |||
| 51 | } | 51 | } |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | const QString Text3DOverlay::getText() const { | ||
| 55 | QMutexLocker lock(&_mutex); | ||
| 56 | return _text; | ||
| 57 | } | ||
| 58 | |||
| 59 | void Text3DOverlay::setText(const QString& text) { | ||
| 60 | QMutexLocker lock(&_mutex); | ||
| 61 | _text = text; | ||
| 62 | } | ||
| 63 | |||
| 54 | xColor Text3DOverlay::getBackgroundColor() { | 64 | xColor Text3DOverlay::getBackgroundColor() { |
| 55 | if (_colorPulse == 0.0f) { | 65 | if (_colorPulse == 0.0f) { |
| 56 | return _backgroundColor; | 66 | return _backgroundColor; |
| @@ -125,7 +135,7 @@ void Text3DOverlay::render(RenderArgs* args) { · 68 unmodified lines | |||
| 125 | // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline | 135 | // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline |
| 126 | // for a gpu performance increase. Currently, | 136 | // for a gpu performance increase. Currently, |
| 127 | // Text renderer sets its own pipeline, | 137 | // Text renderer sets its own pipeline, |
| 128 | _textRenderer->draw(batch, 0, 0, _text, textColor, glm::vec2(-1.0f), getDrawInFront()); | 138 | _textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f), getDrawInFront()); |
| 129 | // so before we continue, we must reset the pipeline | 139 | // so before we continue, we must reset the pipeline |
| 130 | batch.setPipeline(args->_pipeline->pipeline); | 140 | batch.setPipeline(args->_pipeline->pipeline); |
| 131 | args->_pipeline->prepare(batch); | 141 | args->_pipeline->prepare(batch); |
| @@ -188,7 +198,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { · 56 unmodified lines | |||
| 188 | 198 | ||
| 189 | QVariant Text3DOverlay::getProperty(const QString& property) { | 199 | QVariant Text3DOverlay::getProperty(const QString& property) { |
| 190 | if (property == "text") { | 200 | if (property == "text") { |
| 191 | return _text; | 201 | return getText(); |
| 192 | } | 202 | } |
| 193 | if (property == "textAlpha") { | 203 | if (property == "textAlpha") { |
| 194 | return _textAlpha; | 204 | return _textAlpha; |
| @@ -231,7 +241,7 @@ QSizeF Text3DOverlay::textSize(const QString& text) const { · 36 unmodified lines | |||
| 231 | return QSizeF(extents.x, extents.y) * pointToWorldScale; | 241 | return QSizeF(extents.x, extents.y) * pointToWorldScale; |
| 232 | } | 242 | } |
| 233 | 243 | ||
| 234 | bool Text3DOverlay::findRayIntersection(const glm::vec3 &origin, const glm::vec3 &direction, float &distance, | 244 | bool Text3DOverlay::findRayIntersection(const glm::vec3 &origin, const glm::vec3 &direction, float &distance, |
| 235 | BoxFace &face, glm::vec3& surfaceNormal) { | 245 | BoxFace &face, glm::vec3& surfaceNormal) { |
| 236 | Transform transform = getTransform(); | 246 | Transform transform = getTransform(); |
| 237 | applyTransformTo(transform, true); | 247 | applyTransformTo(transform, true); |
| @@ -12,14 +12,14 @@ · 11 unmodified lines | |||
| 12 | #define hifi_Text3DOverlay_h | 12 | #define hifi_Text3DOverlay_h |
| 13 | 13 | ||
| 14 | #include <QString> | 14 | #include <QString> |
| 15 | 15 | #include <QtCore/QMutex> | |
| 16 | #include "Billboard3DOverlay.h" | 16 | #include "Billboard3DOverlay.h" |
| 17 | 17 | ||
| 18 | class TextRenderer3D; | 18 | class TextRenderer3D; |
| 19 | 19 | ||
| 20 | class Text3DOverlay : public Billboard3DOverlay { | 20 | class Text3DOverlay : public Billboard3DOverlay { |
| 21 | Q_OBJECT | 21 | Q_OBJECT |
| 22 | 22 | ||
| 23 | public: | 23 | public: |
| 24 | static QString const TYPE; | 24 | static QString const TYPE; |
| 25 | virtual QString getType() const override { return TYPE; } | 25 | virtual QString getType() const override { return TYPE; } |
| @@ -34,7 +34,7 @@ public: · 8 unmodified lines | |||
| 34 | virtual const render::ShapeKey getShapeKey() override; | 34 | virtual const render::ShapeKey getShapeKey() override; |
| 35 | 35 | ||
| 36 | // getters | 36 | // getters |
| 37 | const QString& getText() const { return _text; } | 37 | const QString getText() const; |
| 38 | float getLineHeight() const { return _lineHeight; } | 38 | float getLineHeight() const { return _lineHeight; } |
| 39 | float getLeftMargin() const { return _leftMargin; } | 39 | float getLeftMargin() const { return _leftMargin; } |
| 40 | float getTopMargin() const { return _topMargin; } | 40 | float getTopMargin() const { return _topMargin; } |
| @@ -45,7 +45,7 @@ public: · 4 unmodified lines | |||
| 45 | float getBackgroundAlpha() { return getAlpha(); } | 45 | float getBackgroundAlpha() { return getAlpha(); } |
| 46 | 46 | ||
| 47 | // setters | 47 | // setters |
| 48 | void setText(const QString& text) { _text = text; } | 48 | void setText(const QString& text); |
| 49 | void setTextAlpha(float alpha) { _textAlpha = alpha; } | 49 | void setTextAlpha(float alpha) { _textAlpha = alpha; } |
| 50 | void setLineHeight(float value) { _lineHeight = value; } | 50 | void setLineHeight(float value) { _lineHeight = value; } |
| 51 | void setLeftMargin(float margin) { _leftMargin = margin; } | 51 | void setLeftMargin(float margin) { _leftMargin = margin; } |
| @@ -58,15 +58,16 @@ public: · 6 unmodified lines | |||
| 58 | 58 | ||
| 59 | QSizeF textSize(const QString& test) const; // Meters | 59 | QSizeF textSize(const QString& test) const; // Meters |
| 60 | 60 | ||
| 61 | virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, | 61 | virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, |
| 62 | BoxFace& face, glm::vec3& surfaceNormal) override; | 62 | BoxFace& face, glm::vec3& surfaceNormal) override; |
| 63 | 63 | ||
| 64 | virtual Text3DOverlay* createClone() const override; | 64 | virtual Text3DOverlay* createClone() const override; |
| 65 | 65 | ||
| 66 | private: | 66 | private: |
| 67 | TextRenderer3D* _textRenderer = nullptr; | 67 | TextRenderer3D* _textRenderer = nullptr; |
| 68 | 68 | ||
| 69 | QString _text; | 69 | QString _text; |
| 70 | mutable QMutex _mutex; // used to make get/setText threadsafe, mutable so can be used in const functions | ||
| 70 | xColor _backgroundColor = xColor { 0, 0, 0 }; | 71 | xColor _backgroundColor = xColor { 0, 0, 0 }; |
| 71 | float _textAlpha { 1.0f }; | 72 | float _textAlpha { 1.0f }; |
| 72 | float _lineHeight { 1.0f }; | 73 | float _lineHeight { 1.0f }; |
| @@ -134,7 +134,7 @@ Web3DOverlay::~Web3DOverlay() { · 133 unmodified lines | |||
| 134 | void Web3DOverlay::update(float deltatime) { | 134 | void Web3DOverlay::update(float deltatime) { |
| 135 | if (_webSurface) { | 135 | if (_webSurface) { |
| 136 | // update globalPosition | 136 | // update globalPosition |
| 137 | _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); | 137 | _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); |
| 138 | } | 138 | } |
| 139 | } | 139 | } |
| 140 | 140 | ||
| @@ -162,57 +162,56 @@ void Web3DOverlay::loadSourceURL() { · 21 unmodified lines | |||
| 162 | _webSurface->resume(); | 162 | _webSurface->resume(); |
| 163 | _webSurface->getRootItem()->setProperty("url", _url); | 163 | _webSurface->getRootItem()->setProperty("url", _url); |
| 164 | _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); | 164 | _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); |
| 165 | _webSurface->getRootContext()->setContextProperty("ApplicationInterface", qApp); | ||
| 166 | 165 | ||
| 167 | } else { | 166 | } else { |
| 168 | _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); | 167 | _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); |
| 169 | _webSurface->load(_url, [&](QQmlContext* context, QObject* obj) {}); | 168 | _webSurface->load(_url, [&](QQmlContext* context, QObject* obj) {}); |
| 170 | _webSurface->resume(); | 169 | _webSurface->resume(); |
| 171 | 170 | ||
| 172 | _webSurface->getRootContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data()); | 171 | _webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data()); |
| 173 | _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data()); | 172 | _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data()); |
| 174 | _webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data()); | 173 | _webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data()); |
| 175 | _webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data()); | 174 | _webSurface->getSurfaceContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data()); |
| 176 | _webSurface->getRootContext()->setContextProperty("Vec3", new Vec3()); | 175 | _webSurface->getSurfaceContext()->setContextProperty("Vec3", new Vec3()); |
| 177 | _webSurface->getRootContext()->setContextProperty("Quat", new Quat()); | 176 | _webSurface->getSurfaceContext()->setContextProperty("Quat", new Quat()); |
| 178 | _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get()); | 177 | _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get()); |
| 179 | _webSurface->getRootContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data()); | 178 | _webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data()); |
| 180 | _webSurface->getRootContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data()); | 179 | _webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data()); |
| 181 | 180 | ||
| 182 | if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { | 181 | if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { |
| 183 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); | 182 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); |
| 184 | auto flags = tabletScriptingInterface->getFlags(); | 183 | auto flags = tabletScriptingInterface->getFlags(); |
| 185 | _webSurface->getRootContext()->setContextProperty("offscreenFlags", flags); | 184 | _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); |
| 186 | _webSurface->getRootContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data()); | 185 | _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data()); |
| 187 | _webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance()); | 186 | _webSurface->getSurfaceContext()->setContextProperty("Account", AccountScriptingInterface::getInstance()); |
| 188 | _webSurface->getRootContext()->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data()); | 187 | _webSurface->getSurfaceContext()->setContextProperty("Audio", DependencyManager::get<AudioScriptingInterface>().data()); |
| 189 | _webSurface->getRootContext()->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data()); | 188 | _webSurface->getSurfaceContext()->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data()); |
| 190 | _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data()); | 189 | _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data()); |
| 191 | _webSurface->getRootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); | 190 | _webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); |
| 192 | _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get()); | 191 | _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get()); |
| 193 | _webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data()); | 192 | _webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data()); |
| 194 | _webSurface->getRootContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data()); | 193 | _webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data()); |
| 195 | _webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data()); | 194 | _webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data()); |
| 196 | _webSurface->getRootContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data()); | 195 | _webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data()); |
| 197 | _webSurface->getRootContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data()); | 196 | _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data()); |
| 198 | _webSurface->getRootContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data()); | 197 | _webSurface->getSurfaceContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data()); |
| 199 | _webSurface->getRootContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); | 198 | _webSurface->getSurfaceContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); |
| 200 | _webSurface->getRootContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); | 199 | _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); |
| 201 | _webSurface->getRootContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data()); | 200 | _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data()); |
| 202 | _webSurface->getRootContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); | 201 | _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); |
| 203 | 202 | ||
| 204 | _webSurface->getRootContext()->setContextProperty("pathToFonts", "../../"); | 203 | _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); |
| 205 | tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); | 204 | tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); |
| 206 | 205 | ||
| 207 | // mark the TabletProxy object as cpp ownership. | 206 | // mark the TabletProxy object as cpp ownership. |
| 208 | QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); | 207 | QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); |
| 209 | _webSurface->getRootContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); | 208 | _webSurface->getSurfaceContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); |
| 210 | 209 | ||
| 211 | // Override min fps for tablet UI, for silky smooth scrolling | 210 | // Override min fps for tablet UI, for silky smooth scrolling |
| 212 | setMaxFPS(90); | 211 | setMaxFPS(90); |
| 213 | } | 212 | } |
| 214 | } | 213 | } |
| 215 | _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); | 214 | _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); |
| 216 | } | 215 | } |
| 217 | 216 | ||
| 218 | void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { | 217 | void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { |
| @@ -443,6 +442,9 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { · 224 unmodified lines | |||
| 443 | 442 | ||
| 444 | QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); | 443 | QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); |
| 445 | 444 | ||
| 445 | if (this->_pressed && event.getType() == PointerEvent::Move) { | ||
| 446 | return; | ||
| 447 | } | ||
| 446 | // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. | 448 | // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. |
| 447 | // FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times). | 449 | // FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times). |
| 448 | // This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery". | 450 | // This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery". |
| @@ -20,6 +20,8 @@ · 19 unmodified lines | |||
| 20 | #include "ElbowConstraint.h" | 20 | #include "ElbowConstraint.h" |
| 21 | #include "SwingTwistConstraint.h" | 21 | #include "SwingTwistConstraint.h" |
| 22 | #include "AnimationLogging.h" | 22 | #include "AnimationLogging.h" |
| 23 | #include "CubicHermiteSpline.h" | ||
| 24 | #include "AnimUtil.h" | ||
| 23 | 25 | ||
| 24 | AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, | 26 | AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, |
| 25 | const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) : | 27 | const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) : |
| @@ -59,7 +61,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN · 33 unmodified lines | |||
| 59 | 61 | ||
| 60 | AnimInverseKinematics::~AnimInverseKinematics() { | 62 | AnimInverseKinematics::~AnimInverseKinematics() { |
| 61 | clearConstraints(); | 63 | clearConstraints(); |
| 62 | _accumulators.clear(); | 64 | _rotationAccumulators.clear(); |
| 65 | _translationAccumulators.clear(); | ||
| 63 | _targetVarVec.clear(); | 66 | _targetVarVec.clear(); |
| 64 | } | 67 | } |
| 65 | 68 | ||
| @@ -72,10 +75,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { · 6 unmodified lines | |||
| 72 | assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); | 75 | assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); |
| 73 | if (_skeleton->getNumJoints() == (int)poses.size()) { | 76 | if (_skeleton->getNumJoints() == (int)poses.size()) { |
| 74 | _relativePoses = poses; | 77 | _relativePoses = poses; |
| 75 | _accumulators.resize(_relativePoses.size()); | 78 | _rotationAccumulators.resize(_relativePoses.size()); |
| 79 | _translationAccumulators.resize(_relativePoses.size()); | ||
| 76 | } else { | 80 | } else { |
| 77 | _relativePoses.clear(); | 81 | _relativePoses.clear(); |
| 78 | _accumulators.clear(); | 82 | _rotationAccumulators.clear(); |
| 83 | _translationAccumulators.clear(); | ||
| 79 | } | 84 | } |
| 80 | } | 85 | } |
| 81 | 86 | ||
| @@ -175,14 +180,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: · 93 unmodified lines | |||
| 175 | } | 180 | } |
| 176 | } | 181 | } |
| 177 | 182 | ||
| 178 | void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) { | 183 | void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<IKTarget>& targets) { |
| 179 | // compute absolute poses that correspond to relative target poses | 184 | // compute absolute poses that correspond to relative target poses |
| 180 | AnimPoseVec absolutePoses; | 185 | AnimPoseVec absolutePoses; |
| 181 | absolutePoses.resize(_relativePoses.size()); | 186 | absolutePoses.resize(_relativePoses.size()); |
| 182 | computeAbsolutePoses(absolutePoses); | 187 | computeAbsolutePoses(absolutePoses); |
| 183 | 188 | ||
| 184 | // clear the accumulators before we start the IK solver | 189 | // clear the accumulators before we start the IK solver |
| 185 | for (auto& accumulator: _accumulators) { | 190 | for (auto& accumulator : _rotationAccumulators) { |
| 191 | accumulator.clearAndClean(); | ||
| 192 | } | ||
| 193 | for (auto& accumulator : _translationAccumulators) { | ||
| 186 | accumulator.clearAndClean(); | 194 | accumulator.clearAndClean(); |
| 187 | } | 195 | } |
| 188 | 196 | ||
| @@ -197,14 +205,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& · 8 unmodified lines | |||
| 197 | 205 | ||
| 198 | // solve all targets | 206 | // solve all targets |
| 199 | for (auto& target: targets) { | 207 | for (auto& target: targets) { |
| 200 | solveTargetWithCCD(context, target, absolutePoses, debug); | 208 | if (target.getType() == IKTarget::Type::Spline) { |
| 209 | solveTargetWithSpline(context, target, absolutePoses, debug); | ||
| 210 | } else { | ||
| 211 | solveTargetWithCCD(context, target, absolutePoses, debug); | ||
| 212 | } | ||
| 201 | } | 213 | } |
| 202 | 214 | ||
| 203 | // harvest accumulated rotations and apply the average | 215 | // harvest accumulated rotations and apply the average |
| 204 | for (int i = 0; i < (int)_relativePoses.size(); ++i) { | 216 | for (int i = 0; i < (int)_relativePoses.size(); ++i) { |
| 205 | if (_accumulators[i].size() > 0) { | 217 | if (_rotationAccumulators[i].size() > 0) { |
| 206 | _relativePoses[i].rot() = _accumulators[i].getAverage(); | 218 | _relativePoses[i].rot() = _rotationAccumulators[i].getAverage(); |
| 207 | _accumulators[i].clear(); | 219 | _rotationAccumulators[i].clear(); |
| 220 | } | ||
| 221 | if (_translationAccumulators[i].size() > 0) { | ||
| 222 | _relativePoses[i].trans() = _translationAccumulators[i].getAverage(); | ||
| 223 | _translationAccumulators[i].clear(); | ||
| 208 | } | 224 | } |
| 209 | } | 225 | } |
| 210 | 226 | ||
| @@ -236,7 +252,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& · 25 unmodified lines | |||
| 236 | int parentIndex = _skeleton->getParentIndex(tipIndex); | 252 | int parentIndex = _skeleton->getParentIndex(tipIndex); |
| 237 | 253 | ||
| 238 | // update rotationOnly targets that don't lie on the ik chain of other ik targets. | 254 | // update rotationOnly targets that don't lie on the ik chain of other ik targets. |
| 239 | if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { | 255 | if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { |
| 240 | const glm::quat& targetRotation = target.getRotation(); | 256 | const glm::quat& targetRotation = target.getRotation(); |
| 241 | // compute tip's new parent-relative rotation | 257 | // compute tip's new parent-relative rotation |
| 242 | // Q = Qp * q --> q' = Qp^ * Q | 258 | // Q = Qp * q --> q' = Qp^ * Q |
| @@ -311,10 +327,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const · 68 unmodified lines | |||
| 311 | } | 327 | } |
| 312 | 328 | ||
| 313 | // store the relative rotation change in the accumulator | 329 | // store the relative rotation change in the accumulator |
| 314 | _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); | 330 | _rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); |
| 331 | |||
| 332 | glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans(); | ||
| 333 | _translationAccumulators[tipIndex].add(tipRelativeTranslation); | ||
| 315 | 334 | ||
| 316 | if (debug) { | 335 | if (debug) { |
| 317 | debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained); | 336 | debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained); |
| 318 | } | 337 | } |
| 319 | } | 338 | } |
| 320 | 339 | ||
| @@ -422,10 +441,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const · 101 unmodified lines | |||
| 422 | } | 441 | } |
| 423 | 442 | ||
| 424 | // store the relative rotation change in the accumulator | 443 | // store the relative rotation change in the accumulator |
| 425 | _accumulators[pivotIndex].add(newRot, target.getWeight()); | 444 | _rotationAccumulators[pivotIndex].add(newRot, target.getWeight()); |
| 445 | |||
| 446 | glm::vec3 newTrans = _relativePoses[pivotIndex].trans(); | ||
| 447 | _translationAccumulators[pivotIndex].add(newTrans); | ||
| 426 | 448 | ||
| 427 | if (debug) { | 449 | if (debug) { |
| 428 | debugJointMap[pivotIndex] = DebugJoint(newRot, constrained); | 450 | debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained); |
| 429 | } | 451 | } |
| 430 | 452 | ||
| 431 | // keep track of tip's new transform as we descend towards root | 453 | // keep track of tip's new transform as we descend towards root |
| @@ -444,6 +466,187 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const · 12 unmodified lines | |||
| 444 | } | 466 | } |
| 445 | } | 467 | } |
| 446 | 468 | ||
| 469 | static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { | ||
| 470 | float linearDistance = glm::length(basePose.trans() - tipPose.trans()); | ||
| 471 | glm::vec3 p0 = basePose.trans(); | ||
| 472 | glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); | ||
| 473 | glm::vec3 p1 = tipPose.trans(); | ||
| 474 | glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); | ||
| 475 | |||
| 476 | return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); | ||
| 477 | } | ||
| 478 | |||
| 479 | // pre-compute information about each joint influeced by this spline IK target. | ||
| 480 | void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) { | ||
| 481 | std::vector<SplineJointInfo> splineJointInfoVec; | ||
| 482 | |||
| 483 | // build spline between the default poses. | ||
| 484 | AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); | ||
| 485 | AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); | ||
| 486 | |||
| 487 | CubicHermiteSplineFunctorWithArcLength spline; | ||
| 488 | if (target.getIndex() == _headIndex) { | ||
| 489 | // set gain factors so that more curvature occurs near the tip of the spline. | ||
| 490 | const float HIPS_GAIN = 0.5f; | ||
| 491 | const float HEAD_GAIN = 1.0f; | ||
| 492 | spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); | ||
| 493 | } else { | ||
| 494 | spline = computeSplineFromTipAndBase(tipPose, basePose); | ||
| 495 | } | ||
| 496 | |||
| 497 | // measure the total arc length along the spline | ||
| 498 | float totalArcLength = spline.arcLength(1.0f); | ||
| 499 | |||
| 500 | glm::vec3 baseToTip = tipPose.trans() - basePose.trans(); | ||
| 501 | float baseToTipLength = glm::length(baseToTip); | ||
| 502 | glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; | ||
| 503 | |||
| 504 | int index = target.getIndex(); | ||
| 505 | int endIndex = _skeleton->getParentIndex(_hipsIndex); | ||
| 506 | while (index != endIndex) { | ||
| 507 | AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); | ||
| 508 | |||
| 509 | float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength; | ||
| 510 | |||
| 511 | // compute offset from spline to the default pose. | ||
| 512 | float t = spline.arcLengthInverse(ratio * totalArcLength); | ||
| 513 | |||
| 514 | // compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis | ||
| 515 | glm::vec3 y = glm::normalize(spline.d(t)); | ||
| 516 | glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X; | ||
| 517 | glm::vec3 u, v, w; | ||
| 518 | generateBasisVectors(y, x, v, u, w); | ||
| 519 | glm::mat3 m(u, v, glm::cross(u, v)); | ||
| 520 | glm::quat rot = glm::normalize(glm::quat_cast(m)); | ||
| 521 | |||
| 522 | AnimPose pose(glm::vec3(1.0f), rot, spline(t)); | ||
| 523 | AnimPose offsetPose = pose.inverse() * defaultPose; | ||
| 524 | |||
| 525 | SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; | ||
| 526 | splineJointInfoVec.push_back(splineJointInfo); | ||
| 527 | index = _skeleton->getParentIndex(index); | ||
| 528 | } | ||
| 529 | |||
| 530 | _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; | ||
| 531 | } | ||
| 532 | |||
| 533 | const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) { | ||
| 534 | // find or create splineJointInfo for this target | ||
| 535 | auto iter = _splineJointInfoMap.find(target.getIndex()); | ||
| 536 | if (iter != _splineJointInfoMap.end()) { | ||
| 537 | return &(iter->second); | ||
| 538 | } else { | ||
| 539 | computeSplineJointInfosForIKTarget(context, target); | ||
| 540 | auto iter = _splineJointInfoMap.find(target.getIndex()); | ||
| 541 | if (iter != _splineJointInfoMap.end()) { | ||
| 542 | return &(iter->second); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | return nullptr; | ||
| 547 | } | ||
| 548 | |||
| 549 | void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { | ||
| 550 | |||
| 551 | std::map<int, DebugJoint> debugJointMap; | ||
| 552 | |||
| 553 | const int baseIndex = _hipsIndex; | ||
| 554 | |||
| 555 | // build spline from tip to base | ||
| 556 | AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); | ||
| 557 | AnimPose basePose = absolutePoses[baseIndex]; | ||
| 558 | CubicHermiteSplineFunctorWithArcLength spline; | ||
| 559 | if (target.getIndex() == _headIndex) { | ||
| 560 | // set gain factors so that more curvature occurs near the tip of the spline. | ||
| 561 | const float HIPS_GAIN = 0.5f; | ||
| 562 | const float HEAD_GAIN = 1.0f; | ||
| 563 | spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); | ||
| 564 | } else { | ||
| 565 | spline = computeSplineFromTipAndBase(tipPose, basePose); | ||
| 566 | } | ||
| 567 | float totalArcLength = spline.arcLength(1.0f); | ||
| 568 | |||
| 569 | // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way) | ||
| 570 | // when the head is arched backwards very far. | ||
| 571 | glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f)); | ||
| 572 | if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) { | ||
| 573 | tipPose.rot() = -tipPose.rot(); | ||
| 574 | } | ||
| 575 | |||
| 576 | // find or create splineJointInfo for this target | ||
| 577 | const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, target); | ||
| 578 | |||
| 579 | if (splineJointInfoVec && splineJointInfoVec->size() > 0) { | ||
| 580 | const int baseParentIndex = _skeleton->getParentIndex(baseIndex); | ||
| 581 | AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); | ||
| 582 | |||
| 583 | // go thru splineJointInfoVec backwards (base to tip) | ||
| 584 | for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) { | ||
| 585 | const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i]; | ||
| 586 | float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); | ||
| 587 | glm::vec3 trans = spline(t); | ||
| 588 | |||
| 589 | // for head splines, preform most twist toward the tip by using ease in function. t^2 | ||
| 590 | float rotT = t; | ||
| 591 | if (target.getIndex() == _headIndex) { | ||
| 592 | rotT = t * t; | ||
| 593 | } | ||
| 594 | glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT)); | ||
| 595 | |||
| 596 | // compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis | ||
| 597 | glm::vec3 y = glm::normalize(spline.d(t)); | ||
| 598 | glm::vec3 x = twistRot * Vectors::UNIT_X; | ||
| 599 | glm::vec3 u, v, w; | ||
| 600 | generateBasisVectors(y, x, v, u, w); | ||
| 601 | glm::mat3 m(u, v, glm::cross(u, v)); | ||
| 602 | glm::quat rot = glm::normalize(glm::quat_cast(m)); | ||
| 603 | |||
| 604 | AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; | ||
| 605 | |||
| 606 | // apply flex coefficent | ||
| 607 | AnimPose flexedAbsPose; | ||
| 608 | ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose); | ||
| 609 | |||
| 610 | AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; | ||
| 611 | _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight()); | ||
| 612 | |||
| 613 | bool constrained = false; | ||
| 614 | if (splineJointInfo.jointIndex != _hipsIndex) { | ||
| 615 | // constrain the amount the spine can stretch or compress | ||
| 616 | float length = glm::length(relPose.trans()); | ||
| 617 | const float EPSILON = 0.0001f; | ||
| 618 | if (length > EPSILON) { | ||
| 619 | float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); | ||
| 620 | const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; | ||
| 621 | const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); | ||
| 622 | const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); | ||
| 623 | if (length > MAX_LENGTH) { | ||
| 624 | relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; | ||
| 625 | constrained = true; | ||
| 626 | } else if (length < MIN_LENGTH) { | ||
| 627 | relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; | ||
| 628 | constrained = true; | ||
| 629 | } | ||
| 630 | } else { | ||
| 631 | relPose.trans() = glm::vec3(0.0f); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 635 | _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight()); | ||
| 636 | |||
| 637 | if (debug) { | ||
| 638 | debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained); | ||
| 639 | } | ||
| 640 | |||
| 641 | parentAbsPose = flexedAbsPose; | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | if (debug) { | ||
| 646 | debugDrawIKChain(debugJointMap, context); | ||
| 647 | } | ||
| 648 | } | ||
| 649 | |||
| 447 | //virtual | 650 | //virtual |
| 448 | const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) { | 651 | const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) { |
| 449 | // don't call this function, call overlay() instead | 652 | // don't call this function, call overlay() instead |
| @@ -453,14 +656,9 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar · 3 unmodified lines | |||
| 453 | 656 | ||
| 454 | //virtual | 657 | //virtual |
| 455 | const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { | 658 | const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { |
| 456 | |||
| 457 | // allows solutionSource to be overridden by an animVar | 659 | // allows solutionSource to be overridden by an animVar |
| 458 | auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); | 660 | auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); |
| 459 | 661 | ||
| 460 | if (context.getEnableDebugDrawIKConstraints()) { | ||
| 461 | debugDrawConstraints(context); | ||
| 462 | } | ||
| 463 | |||
| 464 | const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay | 662 | const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay |
| 465 | if (dt > MAX_OVERLAY_DT) { | 663 | if (dt > MAX_OVERLAY_DT) { |
| 466 | dt = MAX_OVERLAY_DT; | 664 | dt = MAX_OVERLAY_DT; |
| @@ -569,7 +767,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars · 102 unmodified lines | |||
| 569 | 767 | ||
| 570 | { | 768 | { |
| 571 | PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); | 769 | PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); |
| 572 | solveWithCyclicCoordinateDescent(context, targets); | 770 | solve(context, targets); |
| 573 | } | 771 | } |
| 574 | 772 | ||
| 575 | if (_hipsTargetIndex < 0) { | 773 | if (_hipsTargetIndex < 0) { |
| @@ -579,6 +777,20 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars · 3 unmodified lines | |||
| 579 | _hipsOffset = Vectors::ZERO; | 777 | _hipsOffset = Vectors::ZERO; |
| 580 | } | 778 | } |
| 581 | } | 779 | } |
| 780 | |||
| 781 | if (context.getEnableDebugDrawIKConstraints()) { | ||
| 782 | debugDrawConstraints(context); | ||
| 783 | } | ||
| 784 | } | ||
| 785 | |||
| 786 | if (_leftHandIndex > -1) { | ||
| 787 | _uncontrolledLeftHandPose = _skeleton->getAbsolutePose(_leftHandIndex, underPoses); | ||
| 788 | } | ||
| 789 | if (_rightHandIndex > -1) { | ||
| 790 | _uncontrolledRightHandPose = _skeleton->getAbsolutePose(_rightHandIndex, underPoses); | ||
| 791 | } | ||
| 792 | if (_hipsIndex > -1) { | ||
| 793 | _uncontrolledHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); | ||
| 582 | } | 794 | } |
| 583 | 795 | ||
| 584 | return _relativePoses; | 796 | return _relativePoses; |
| @@ -722,8 +934,10 @@ void AnimInverseKinematics::initConstraints() { · 137 unmodified lines | |||
| 722 | 934 | ||
| 723 | loadDefaultPoses(_skeleton->getRelativeBindPoses()); | 935 | loadDefaultPoses(_skeleton->getRelativeBindPoses()); |
| 724 | 936 | ||
| 725 | // compute corresponding absolute poses | ||
| 726 | int numJoints = (int)_defaultRelativePoses.size(); | 937 | int numJoints = (int)_defaultRelativePoses.size(); |
| 938 | |||
| 939 | /* KEEP THIS CODE for future experimentation | ||
| 940 | // compute corresponding absolute poses | ||
| 727 | AnimPoseVec absolutePoses; | 941 | AnimPoseVec absolutePoses; |
| 728 | absolutePoses.resize(numJoints); | 942 | absolutePoses.resize(numJoints); |
| 729 | for (int i = 0; i < numJoints; ++i) { | 943 | for (int i = 0; i < numJoints; ++i) { |
| @@ -734,6 +948,7 @@ void AnimInverseKinematics::initConstraints() { · 4 unmodified lines | |||
| 734 | absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i]; | 948 | absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i]; |
| 735 | } | 949 | } |
| 736 | } | 950 | } |
| 951 | */ | ||
| 737 | 952 | ||
| 738 | clearConstraints(); | 953 | clearConstraints(); |
| 739 | for (int i = 0; i < numJoints; ++i) { | 954 | for (int i = 0; i < numJoints; ++i) { |
| @@ -1045,7 +1260,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele · 305 unmodified lines | |||
| 1045 | 1260 | ||
| 1046 | _maxTargetIndex = -1; | 1261 | _maxTargetIndex = -1; |
| 1047 | 1262 | ||
| 1048 | for (auto& accumulator: _accumulators) { | 1263 | for (auto& accumulator: _rotationAccumulators) { |
| 1264 | accumulator.clearAndClean(); | ||
| 1265 | } | ||
| 1266 | for (auto& accumulator: _translationAccumulators) { | ||
| 1049 | accumulator.clearAndClean(); | 1267 | accumulator.clearAndClean(); |
| 1050 | } | 1268 | } |
| 1051 | 1269 | ||
| @@ -1061,12 +1279,21 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele · 9 unmodified lines | |||
| 1061 | } else { | 1279 | } else { |
| 1062 | _hipsParentIndex = -1; | 1280 | _hipsParentIndex = -1; |
| 1063 | } | 1281 | } |
| 1282 | |||
| 1283 | _leftHandIndex = _skeleton->nameToJointIndex("LeftHand"); | ||
| 1284 | _rightHandIndex = _skeleton->nameToJointIndex("RightHand"); | ||
| 1064 | } else { | 1285 | } else { |
| 1065 | clearConstraints(); | 1286 | clearConstraints(); |
| 1066 | _headIndex = -1; | 1287 | _headIndex = -1; |
| 1067 | _hipsIndex = -1; | 1288 | _hipsIndex = -1; |
| 1068 | _hipsParentIndex = -1; | 1289 | _hipsParentIndex = -1; |
| 1290 | _leftHandIndex = -1; | ||
| 1291 | _rightHandIndex = -1; | ||
| 1069 | } | 1292 | } |
| 1293 | |||
| 1294 | _uncontrolledLeftHandPose = AnimPose(); | ||
| 1295 | _uncontrolledRightHandPose = AnimPose(); | ||
| 1296 | _uncontrolledHipsPose = AnimPose(); | ||
| 1070 | } | 1297 | } |
| 1071 | 1298 | ||
| 1072 | static glm::vec3 sphericalToCartesian(float phi, float theta) { | 1299 | static glm::vec3 sphericalToCartesian(float phi, float theta) { |
| @@ -1117,6 +1344,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi · 44 unmodified lines | |||
| 1117 | // copy debug joint rotations into the relative poses | 1344 | // copy debug joint rotations into the relative poses |
| 1118 | for (auto& debugJoint : debugJointMap) { | 1345 | for (auto& debugJoint : debugJointMap) { |
| 1119 | poses[debugJoint.first].rot() = debugJoint.second.relRot; | 1346 | poses[debugJoint.first].rot() = debugJoint.second.relRot; |
| 1347 | poses[debugJoint.first].trans() = debugJoint.second.relTrans; | ||
| 1120 | } | 1348 | } |
| 1121 | 1349 | ||
| 1122 | // convert relative poses to absolute | 1350 | // convert relative poses to absolute |
| @@ -1282,7 +1510,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A · 159 unmodified lines | |||
| 1282 | int numJoints = (int)_relativePoses.size(); | 1510 | int numJoints = (int)_relativePoses.size(); |
| 1283 | for (int i = 0; i < numJoints; ++i) { | 1511 | for (int i = 0; i < numJoints; ++i) { |
| 1284 | float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot())); | 1512 | float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot())); |
| 1285 | if (_accumulators[i].isDirty()) { | 1513 | if (_rotationAccumulators[i].isDirty()) { |
| 1286 | // this joint is affected by IK --> blend toward the targetPoses rotation | 1514 | // this joint is affected by IK --> blend toward the targetPoses rotation |
| 1287 | _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor)); | 1515 | _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor)); |
| 1288 | } else { | 1516 | } else { |
| @@ -1316,3 +1544,46 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s · 27 unmodified lines | |||
| 1316 | break; | 1544 | break; |
| 1317 | } | 1545 | } |
| 1318 | } | 1546 | } |
| 1547 | |||
| 1548 | void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const { | ||
| 1549 | |||
| 1550 | for (auto& target : targets) { | ||
| 1551 | |||
| 1552 | if (target.getType() != IKTarget::Type::Spline) { | ||
| 1553 | continue; | ||
| 1554 | } | ||
| 1555 | |||
| 1556 | const int baseIndex = _hipsIndex; | ||
| 1557 | |||
| 1558 | // build spline | ||
| 1559 | AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); | ||
| 1560 | AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses); | ||
| 1561 | |||
| 1562 | CubicHermiteSplineFunctorWithArcLength spline; | ||
| 1563 | if (target.getIndex() == _headIndex) { | ||
| 1564 | // set gain factors so that more curvature occurs near the tip of the spline. | ||
| 1565 | const float HIPS_GAIN = 0.5f; | ||
| 1566 | const float HEAD_GAIN = 1.0f; | ||
| 1567 | spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); | ||
| 1568 | } else { | ||
| 1569 | spline = computeSplineFromTipAndBase(tipPose, basePose); | ||
| 1570 | } | ||
| 1571 | float totalArcLength = spline.arcLength(1.0f); | ||
| 1572 | |||
| 1573 | const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); | ||
| 1574 | const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); | ||
| 1575 | |||
| 1576 | // draw red and white stripped spline, parameterized by arc length. | ||
| 1577 | // i.e. each stripe should be the same length. | ||
| 1578 | AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); | ||
| 1579 | const int NUM_SEGMENTS = 20; | ||
| 1580 | const float dArcLength = totalArcLength / NUM_SEGMENTS; | ||
| 1581 | float arcLength = 0.0f; | ||
| 1582 | for (int i = 0; i < NUM_SEGMENTS; i++) { | ||
| 1583 | float prevT = spline.arcLengthInverse(arcLength); | ||
| 1584 | float nextT = spline.arcLengthInverse(arcLength + dArcLength); | ||
| 1585 | DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE); | ||
| 1586 | arcLength += dArcLength; | ||
| 1587 | } | ||
| 1588 | } | ||
| 1589 | } | ||
| @@ -19,6 +19,7 @@ · 18 unmodified lines | |||
| 19 | #include "IKTarget.h" | 19 | #include "IKTarget.h" |
| 20 | 20 | ||
| 21 | #include "RotationAccumulator.h" | 21 | #include "RotationAccumulator.h" |
| 22 | #include "TranslationAccumulator.h" | ||
| 22 | 23 | ||
| 23 | class RotationConstraint; | 24 | class RotationConstraint; |
| 24 | 25 | ||
| @@ -56,23 +57,39 @@ public: · 31 unmodified lines | |||
| 56 | void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } | 57 | void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } |
| 57 | void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; } | 58 | void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; } |
| 58 | 59 | ||
| 60 | const AnimPose& getUncontrolledLeftHandPose() { return _uncontrolledLeftHandPose; } | ||
| 61 | const AnimPose& getUncontrolledRightHandPose() { return _uncontrolledRightHandPose; } | ||
| 62 | const AnimPose& getUncontrolledHipPose() { return _uncontrolledHipsPose; } | ||
| 63 | |||
| 59 | protected: | 64 | protected: |
| 60 | void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses); | 65 | void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses); |
| 61 | void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets); | 66 | void solve(const AnimContext& context, const std::vector<IKTarget>& targets); |
| 62 | void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); | 67 | void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); |
| 68 | void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); | ||
| 63 | virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; | 69 | virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; |
| 64 | struct DebugJoint { | 70 | struct DebugJoint { |
| 65 | DebugJoint() : relRot(), constrained(false) {} | 71 | DebugJoint() : relRot(), constrained(false) {} |
| 66 | DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {} | 72 | DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {} |
| 67 | glm::quat relRot; | 73 | glm::quat relRot; |
| 74 | glm::vec3 relTrans; | ||
| 68 | bool constrained; | 75 | bool constrained; |
| 69 | }; | 76 | }; |
| 70 | void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const; | 77 | void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const; |
| 71 | void debugDrawRelativePoses(const AnimContext& context) const; | 78 | void debugDrawRelativePoses(const AnimContext& context) const; |
| 72 | void debugDrawConstraints(const AnimContext& context) const; | 79 | void debugDrawConstraints(const AnimContext& context) const; |
| 80 | void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const; | ||
| 73 | void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); | 81 | void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); |
| 74 | void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); | 82 | void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); |
| 75 | 83 | ||
| 84 | // used to pre-compute information about each joint influeced by a spline IK target. | ||
| 85 | struct SplineJointInfo { | ||
| 86 | int jointIndex; // joint in the skeleton that this information pertains to. | ||
| 87 | float ratio; // percentage (0..1) along the spline for this joint. | ||
| 88 | AnimPose offsetPose; // local offset from the spline to the joint. | ||
| 89 | }; | ||
| 90 | |||
| 91 | void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target); | ||
| 92 | const std::vector<SplineJointInfo>* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target); | ||
| 76 | 93 | ||
| 77 | // for AnimDebugDraw rendering | 94 | // for AnimDebugDraw rendering |
| 78 | virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } | 95 | virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } |
| @@ -105,12 +122,15 @@ protected: · 26 unmodified lines | |||
| 105 | }; | 122 | }; |
| 106 | 123 | ||
| 107 | std::map<int, RotationConstraint*> _constraints; | 124 | std::map<int, RotationConstraint*> _constraints; |
| 108 | std::vector<RotationAccumulator> _accumulators; | 125 | std::vector<RotationAccumulator> _rotationAccumulators; |
| 126 | std::vector<TranslationAccumulator> _translationAccumulators; | ||
| 109 | std::vector<IKTargetVar> _targetVarVec; | 127 | std::vector<IKTargetVar> _targetVarVec; |
| 110 | AnimPoseVec _defaultRelativePoses; // poses of the relaxed state | 128 | AnimPoseVec _defaultRelativePoses; // poses of the relaxed state |
| 111 | AnimPoseVec _relativePoses; // current relative poses | 129 | AnimPoseVec _relativePoses; // current relative poses |
| 112 | AnimPoseVec _limitCenterPoses; // relative | 130 | AnimPoseVec _limitCenterPoses; // relative |
| 113 | 131 | ||
| 132 | std::map<int, std::vector<SplineJointInfo>> _splineJointInfoMap; | ||
| 133 | |||
| 114 | // experimental data for moving hips during IK | 134 | // experimental data for moving hips during IK |
| 115 | glm::vec3 _hipsOffset { Vectors::ZERO }; | 135 | glm::vec3 _hipsOffset { Vectors::ZERO }; |
| 116 | float _maxHipsOffsetLength{ FLT_MAX }; | 136 | float _maxHipsOffsetLength{ FLT_MAX }; |
| @@ -118,6 +138,8 @@ protected: · 1 unmodified line | |||
| 118 | int _hipsIndex { -1 }; | 138 | int _hipsIndex { -1 }; |
| 119 | int _hipsParentIndex { -1 }; | 139 | int _hipsParentIndex { -1 }; |
| 120 | int _hipsTargetIndex { -1 }; | 140 | int _hipsTargetIndex { -1 }; |
| 141 | int _leftHandIndex { -1 }; | ||
| 142 | int _rightHandIndex { -1 }; | ||
| 121 | 143 | ||
| 122 | // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses | 144 | // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses |
| 123 | // during the the cyclic coordinate descent algorithm | 145 | // during the the cyclic coordinate descent algorithm |
| @@ -127,6 +149,10 @@ protected: · 3 unmodified lines | |||
| 127 | bool _previousEnableDebugIKTargets { false }; | 149 | bool _previousEnableDebugIKTargets { false }; |
| 128 | SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; | 150 | SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; |
| 129 | QString _solutionSourceVar; | 151 | QString _solutionSourceVar; |
| 152 | |||
| 153 | AnimPose _uncontrolledLeftHandPose { AnimPose() }; | ||
| 154 | AnimPose _uncontrolledRightHandPose { AnimPose() }; | ||
| 155 | AnimPose _uncontrolledHipsPose { AnimPose() }; | ||
| 130 | }; | 156 | }; |
| 131 | 157 | ||
| 132 | #endif // hifi_AnimInverseKinematics_h | 158 | #endif // hifi_AnimInverseKinematics_h |
| @@ -44,6 +44,9 @@ void IKTarget::setType(int type) { · 43 unmodified lines | |||
| 44 | case (int)Type::HipsRelativeRotationAndPosition: | 44 | case (int)Type::HipsRelativeRotationAndPosition: |
| 45 | _type = Type::HipsRelativeRotationAndPosition; | 45 | _type = Type::HipsRelativeRotationAndPosition; |
| 46 | break; | 46 | break; |
| 47 | case (int)Type::Spline: | ||
| 48 | _type = Type::Spline; | ||
| 49 | break; | ||
| 47 | default: | 50 | default: |
| 48 | _type = Type::Unknown; | 51 | _type = Type::Unknown; |
| 49 | } | 52 | } |
| @@ -21,6 +21,7 @@ public: · 20 unmodified lines | |||
| 21 | RotationOnly, | 21 | RotationOnly, |
| 22 | HmdHead, | 22 | HmdHead, |
| 23 | HipsRelativeRotationAndPosition, | 23 | HipsRelativeRotationAndPosition, |
| 24 | Spline, | ||
| 24 | Unknown | 25 | Unknown |
| 25 | }; | 26 | }; |
| 26 | 27 | ||
| @@ -28,6 +28,7 @@ · 27 unmodified lines | |||
| 28 | #include "AnimClip.h" | 28 | #include "AnimClip.h" |
| 29 | #include "AnimInverseKinematics.h" | 29 | #include "AnimInverseKinematics.h" |
| 30 | #include "AnimSkeleton.h" | 30 | #include "AnimSkeleton.h" |
| 31 | #include "AnimUtil.h" | ||
| 31 | #include "IKTarget.h" | 32 | #include "IKTarget.h" |
| 32 | 33 | ||
| 33 | static bool isEqual(const glm::vec3& u, const glm::vec3& v) { | 34 | static bool isEqual(const glm::vec3& u, const glm::vec3& v) { |
| @@ -401,16 +402,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo · 367 unmodified lines | |||
| 401 | } | 402 | } |
| 402 | } | 403 | } |
| 403 | 404 | ||
| 404 | void Rig::restoreJointRotation(int index, float fraction, float priority) { | ||
| 405 | // AJT: DEAD CODE? | ||
| 406 | ASSERT(false); | ||
| 407 | } | ||
| 408 | |||
| 409 | void Rig::restoreJointTranslation(int index, float fraction, float priority) { | ||
| 410 | // AJT: DEAD CODE? | ||
| 411 | ASSERT(false); | ||
| 412 | } | ||
| 413 | |||
| 414 | bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { | 405 | bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { |
| 415 | if (isIndexValid(jointIndex)) { | 406 | if (isIndexValid(jointIndex)) { |
| 416 | position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation; | 407 | position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation; |
| @@ -1040,8 +1031,8 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { · 623 unmodified lines | |||
| 1040 | _animVars.set("hipsType", (int)IKTarget::Type::Unknown); | 1031 | _animVars.set("hipsType", (int)IKTarget::Type::Unknown); |
| 1041 | } | 1032 | } |
| 1042 | 1033 | ||
| 1043 | if (params.spine2Enabled) { | 1034 | if (params.hipsEnabled && params.spine2Enabled) { |
| 1044 | _animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition); | 1035 | _animVars.set("spine2Type", (int)IKTarget::Type::Spline); |
| 1045 | _animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); | 1036 | _animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); |
| 1046 | _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); | 1037 | _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); |
| 1047 | } else { | 1038 | } else { |
| @@ -1051,7 +1042,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { · 3 unmodified lines | |||
| 1051 | if (params.leftArmEnabled) { | 1042 | if (params.leftArmEnabled) { |
| 1052 | _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition); | 1043 | _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition); |
| 1053 | _animVars.set("leftArmPosition", params.leftArmPosition); | 1044 | _animVars.set("leftArmPosition", params.leftArmPosition); |
| 1054 | _animVars.set("leftArmRotation", params.leftArmRotation); | 1045 | _animVars.set("leftArmRotation", params.leftArmRotation); |
| 1055 | } else { | 1046 | } else { |
| 1056 | _animVars.set("leftArmType", (int)IKTarget::Type::Unknown); | 1047 | _animVars.set("leftArmType", (int)IKTarget::Type::Unknown); |
| 1057 | } | 1048 | } |
| @@ -1101,9 +1092,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) { · 43 unmodified lines | |||
| 1101 | _animVars.set("headPosition", params.rigHeadPosition); | 1092 | _animVars.set("headPosition", params.rigHeadPosition); |
| 1102 | _animVars.set("headRotation", params.rigHeadOrientation); | 1093 | _animVars.set("headRotation", params.rigHeadOrientation); |
| 1103 | if (params.hipsEnabled) { | 1094 | if (params.hipsEnabled) { |
| 1104 | // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type. | 1095 | // Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type. |
| 1105 | // this will allow the spine to bend more, ensuring that it can reach the head target position. | 1096 | // this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position. |
| 1106 | _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); | 1097 | _animVars.set("headType", (int)IKTarget::Type::Spline); |
| 1107 | _animVars.unset("headWeight"); // use the default weight for this target. | 1098 | _animVars.unset("headWeight"); // use the default weight for this target. |
| 1108 | } else { | 1099 | } else { |
| 1109 | // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, | 1100 | // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, |
| @@ -1135,9 +1126,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm · 25 unmodified lines | |||
| 1135 | } | 1126 | } |
| 1136 | 1127 | ||
| 1137 | glm::vec3 headUp = headQuat * Vectors::UNIT_Y; | 1128 | glm::vec3 headUp = headQuat * Vectors::UNIT_Y; |
| 1138 | glm::vec3 z, y, x; | 1129 | glm::vec3 z, y, zCrossY; |
| 1139 | generateBasisVectors(lookAtVector, headUp, z, y, x); | 1130 | generateBasisVectors(lookAtVector, headUp, z, y, zCrossY); |
| 1140 | glm::mat3 m(glm::cross(y, z), y, z); | 1131 | glm::mat3 m(-zCrossY, y, z); |
| 1141 | glm::quat desiredQuat = glm::normalize(glm::quat_cast(m)); | 1132 | glm::quat desiredQuat = glm::normalize(glm::quat_cast(m)); |
| 1142 | 1133 | ||
| 1143 | glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); | 1134 | glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); |
| @@ -1172,10 +1163,10 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 28 unmodified lines | |||
| 1172 | // TODO: add isHipsEnabled | 1163 | // TODO: add isHipsEnabled |
| 1173 | bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; | 1164 | bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; |
| 1174 | 1165 | ||
| 1175 | if (params.isLeftEnabled) { | 1166 | const float RELAX_DURATION = 0.6f; |
| 1176 | 1167 | ||
| 1168 | if (params.isLeftEnabled) { | ||
| 1177 | glm::vec3 handPosition = params.leftPosition; | 1169 | glm::vec3 handPosition = params.leftPosition; |
| 1178 | |||
| 1179 | if (!bodySensorTrackingEnabled) { | 1170 | if (!bodySensorTrackingEnabled) { |
| 1180 | // prevent the hand IK targets from intersecting the body capsule | 1171 | // prevent the hand IK targets from intersecting the body capsule |
| 1181 | glm::vec3 displacement; | 1172 | glm::vec3 displacement; |
| @@ -1187,16 +1178,38 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 5 unmodified lines | |||
| 1187 | _animVars.set("leftHandPosition", handPosition); | 1178 | _animVars.set("leftHandPosition", handPosition); |
| 1188 | _animVars.set("leftHandRotation", params.leftOrientation); | 1179 | _animVars.set("leftHandRotation", params.leftOrientation); |
| 1189 | _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); | 1180 | _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); |
| 1181 | |||
| 1182 | _isLeftHandControlled = true; | ||
| 1183 | _lastLeftHandControlledPose = AnimPose(glm::vec3(1.0f), params.leftOrientation, handPosition); | ||
| 1190 | } else { | 1184 | } else { |
| 1191 | _animVars.unset("leftHandPosition"); | 1185 | if (_isLeftHandControlled) { |
| 1192 | _animVars.unset("leftHandRotation"); | 1186 | _leftHandRelaxDuration = RELAX_DURATION; |
| 1193 | _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); | 1187 | _isLeftHandControlled = false; |
| 1188 | } | ||
| 1189 | |||
| 1190 | if (_leftHandRelaxDuration > 0) { | ||
| 1191 | // Move hand from controlled position to non-controlled position. | ||
| 1192 | _leftHandRelaxDuration = std::max(_leftHandRelaxDuration - dt, 0.0f); | ||
| 1193 | auto ikNode = getAnimInverseKinematicsNode(); | ||
| 1194 | if (ikNode) { | ||
| 1195 | float alpha = 1.0f - _leftHandRelaxDuration / RELAX_DURATION; | ||
| 1196 | const AnimPose geometryToRigTransform(_geometryToRigTransform); | ||
| 1197 | AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledLeftHandPose(); | ||
| 1198 | AnimPose handPose; | ||
| 1199 | ::blend(1, &_lastLeftHandControlledPose, &uncontrolledHandPose, alpha, &handPose); | ||
| 1200 | _animVars.set("leftHandPosition", handPose.trans()); | ||
| 1201 | _animVars.set("leftHandRotation", handPose.rot()); | ||
| 1202 | _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); | ||
| 1203 | } | ||
| 1204 | } else { | ||
| 1205 | _animVars.unset("leftHandPosition"); | ||
| 1206 | _animVars.unset("leftHandRotation"); | ||
| 1207 | _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); | ||
| 1208 | } | ||
| 1194 | } | 1209 | } |
| 1195 | 1210 | ||
| 1196 | if (params.isRightEnabled) { | 1211 | if (params.isRightEnabled) { |
| 1197 | |||
| 1198 | glm::vec3 handPosition = params.rightPosition; | 1212 | glm::vec3 handPosition = params.rightPosition; |
| 1199 | |||
| 1200 | if (!bodySensorTrackingEnabled) { | 1213 | if (!bodySensorTrackingEnabled) { |
| 1201 | // prevent the hand IK targets from intersecting the body capsule | 1214 | // prevent the hand IK targets from intersecting the body capsule |
| 1202 | glm::vec3 displacement; | 1215 | glm::vec3 displacement; |
| @@ -1208,10 +1221,34 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 5 unmodified lines | |||
| 1208 | _animVars.set("rightHandPosition", handPosition); | 1221 | _animVars.set("rightHandPosition", handPosition); |
| 1209 | _animVars.set("rightHandRotation", params.rightOrientation); | 1222 | _animVars.set("rightHandRotation", params.rightOrientation); |
| 1210 | _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); | 1223 | _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); |
| 1224 | |||
| 1225 | _isRightHandControlled = true; | ||
| 1226 | _lastRightHandControlledPose = AnimPose(glm::vec3(1.0f), params.rightOrientation, handPosition); | ||
| 1211 | } else { | 1227 | } else { |
| 1212 | _animVars.unset("rightHandPosition"); | 1228 | if (_isRightHandControlled) { |
| 1213 | _animVars.unset("rightHandRotation"); | 1229 | _rightHandRelaxDuration = RELAX_DURATION; |
| 1214 | _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); | 1230 | _isRightHandControlled = false; |
| 1231 | } | ||
| 1232 | |||
| 1233 | if (_rightHandRelaxDuration > 0) { | ||
| 1234 | // Move hand from controlled position to non-controlled position. | ||
| 1235 | _rightHandRelaxDuration = std::max(_rightHandRelaxDuration - dt, 0.0f); | ||
| 1236 | auto ikNode = getAnimInverseKinematicsNode(); | ||
| 1237 | if (ikNode) { | ||
| 1238 | float alpha = 1.0f - _rightHandRelaxDuration / RELAX_DURATION; | ||
| 1239 | const AnimPose geometryToRigTransform(_geometryToRigTransform); | ||
| 1240 | AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledRightHandPose(); | ||
| 1241 | AnimPose handPose; | ||
| 1242 | ::blend(1, &_lastRightHandControlledPose, &uncontrolledHandPose, alpha, &handPose); | ||
| 1243 | _animVars.set("rightHandPosition", handPose.trans()); | ||
| 1244 | _animVars.set("rightHandRotation", handPose.rot()); | ||
| 1245 | _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); | ||
| 1246 | } | ||
| 1247 | } else { | ||
| 1248 | _animVars.unset("rightHandPosition"); | ||
| 1249 | _animVars.unset("rightHandRotation"); | ||
| 1250 | _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); | ||
| 1251 | } | ||
| 1215 | } | 1252 | } |
| 1216 | 1253 | ||
| 1217 | if (params.isLeftFootEnabled) { | 1254 | if (params.isLeftFootEnabled) { |
| @@ -1233,7 +1270,6 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f · 15 unmodified lines | |||
| 1233 | _animVars.unset("rightFootRotation"); | 1270 | _animVars.unset("rightFootRotation"); |
| 1234 | _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); | 1271 | _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); |
| 1235 | } | 1272 | } |
| 1236 | |||
| 1237 | } | 1273 | } |
| 1238 | } | 1274 | } |
| 1239 | 1275 | ||
| @@ -1509,5 +1545,3 @@ void Rig::computeAvatarBoundingCapsule( · 269 unmodified lines | |||
| 1509 | glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum))); | 1545 | glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum))); |
| 1510 | localOffsetOut = rigCenter - (geometryToRig * rootPosition); | 1546 | localOffsetOut = rigCenter - (geometryToRig * rootPosition); |
| 1511 | } | 1547 | } |
| 1512 | |||
| 1513 | |||
| @@ -134,10 +134,6 @@ public: · 133 unmodified lines | |||
| 134 | void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); | 134 | void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); |
| 135 | void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); | 135 | void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); |
| 136 | 136 | ||
| 137 | // legacy | ||
| 138 | void restoreJointRotation(int index, float fraction, float priority); | ||
| 139 | void restoreJointTranslation(int index, float fraction, float priority); | ||
| 140 | |||
| 141 | // if translation and rotation is identity, position will be in rig space | 137 | // if translation and rotation is identity, position will be in rig space |
| 142 | bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, | 138 | bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, |
| 143 | glm::vec3 translation, glm::quat rotation) const; | 139 | glm::vec3 translation, glm::quat rotation) const; |
| @@ -355,6 +351,13 @@ private: · 211 unmodified lines | |||
| 355 | QMap<int, StateHandler> _stateHandlers; | 351 | QMap<int, StateHandler> _stateHandlers; |
| 356 | int _nextStateHandlerId { 0 }; | 352 | int _nextStateHandlerId { 0 }; |
| 357 | QMutex _stateMutex; | 353 | QMutex _stateMutex; |
| 354 | |||
| 355 | bool _isLeftHandControlled { false }; | ||
| 356 | bool _isRightHandControlled { false }; | ||
| 357 | float _leftHandRelaxDuration { 0.0f }; | ||
| 358 | float _rightHandRelaxDuration { 0.0f }; | ||
| 359 | AnimPose _lastLeftHandControlledPose; | ||
| 360 | AnimPose _lastRightHandControlledPose; | ||
| 358 | }; | 361 | }; |
| 359 | 362 | ||
| 360 | #endif /* defined(__hifi__Rig__) */ | 363 | #endif /* defined(__hifi__Rig__) */ |
| @@ -1,5 +1,5 @@ | |||
| 1 | // | 1 | // |
| 2 | // RotationAccumulator.h | 2 | // RotationAccumulator.cpp |
| 3 | // | 3 | // |
| 4 | // Copyright 2015 High Fidelity, Inc. | 4 | // Copyright 2015 High Fidelity, Inc. |
| 5 | // | 5 | // |
| @@ -27,7 +27,7 @@ void RotationAccumulator::clear() { · 21 unmodified lines | |||
| 27 | _numRotations = 0; | 27 | _numRotations = 0; |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | void RotationAccumulator::clearAndClean() { | 30 | void RotationAccumulator::clearAndClean() { |
| 31 | clear(); | 31 | clear(); |
| 32 | _isDirty = false; | 32 | _isDirty = false; |
| 33 | } | 33 | } |
| @@ -0,0 +1,34 @@ | |||
| 1 | // | ||
| 2 | // TranslationAccumulator.cpp | ||
| 3 | // | ||
| 4 | // Copyright 2015 High Fidelity, Inc. | ||
| 5 | // | ||
| 6 | // Distributed under the Apache License, Version 2.0. | ||
| 7 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 8 | // | ||
| 9 | |||
| 10 | #include "TranslationAccumulator.h" | ||
| 11 | |||
| 12 | void TranslationAccumulator::add(const glm::vec3& translation, float weight) { | ||
| 13 | _accum += weight * translation; | ||
| 14 | _totalWeight += weight; | ||
| 15 | _isDirty = true; | ||
| 16 | } | ||
| 17 | |||
| 18 | glm::vec3 TranslationAccumulator::getAverage() { | ||
| 19 | if (_totalWeight > 0.0f) { | ||
| 20 | return _accum / _totalWeight; | ||
| 21 | } else { | ||
| 22 | return glm::vec3(); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | void TranslationAccumulator::clear() { | ||
| 27 | _accum *= 0.0f; | ||
| 28 | _totalWeight = 0.0f; | ||
| 29 | } | ||
| 30 | |||
| 31 | void TranslationAccumulator::clearAndClean() { | ||
| 32 | clear(); | ||
| 33 | _isDirty = false; | ||
| 34 | } | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | // | ||
| 2 | // TranslationAccumulator.h | ||
| 3 | // | ||
| 4 | // Copyright 2017 High Fidelity, Inc. | ||
| 5 | // | ||
| 6 | // Distributed under the Apache License, Version 2.0. | ||
| 7 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef hifi_TranslationAccumulator_h | ||
| 11 | #define hifi_TranslationAccumulator_h | ||
| 12 | |||
| 13 | #include <glm/glm.hpp> | ||
| 14 | |||
| 15 | class TranslationAccumulator { | ||
| 16 | public: | ||
| 17 | TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { } | ||
| 18 | |||
| 19 | int size() const { return _totalWeight > 0.0f; } | ||
| 20 | |||
| 21 | /// \param translation translation to add | ||
| 22 | /// \param weight contribution factor of this translation to total accumulation | ||
| 23 | void add(const glm::vec3& translation, float weight = 1.0f); | ||
| 24 | |||
| 25 | glm::vec3 getAverage(); | ||
| 26 | |||
| 27 | /// \return true if any translation were accumulated | ||
| 28 | bool isDirty() const { return _isDirty; } | ||
| 29 | |||
| 30 | /// \brief clear accumulated translation but don't change _isDirty | ||
| 31 | void clear(); | ||
| 32 | |||
| 33 | /// \brief clear accumulated translation and set _isDirty to false | ||
| 34 | void clearAndClean(); | ||
| 35 | |||
| 36 | private: | ||
| 37 | glm::vec3 _accum; | ||
| 38 | float _totalWeight; | ||
| 39 | bool _isDirty; | ||
| 40 | }; | ||
| 41 | |||
| 42 | #endif // hifi_TranslationAccumulator_h | ||
| @@ -31,10 +31,13 @@ · 30 unmodified lines | |||
| 31 | #include <VersionHelpers.h> | 31 | #include <VersionHelpers.h> |
| 32 | #endif | 32 | #endif |
| 33 | 33 | ||
| 34 | #include <QtConcurrent/QtConcurrent> | ||
| 35 | #include <QtCore/QThreadPool> | ||
| 34 | #include <QtCore/QBuffer> | 36 | #include <QtCore/QBuffer> |
| 35 | #include <QtMultimedia/QAudioInput> | 37 | #include <QtMultimedia/QAudioInput> |
| 36 | #include <QtMultimedia/QAudioOutput> | 38 | #include <QtMultimedia/QAudioOutput> |
| 37 | 39 | ||
| 40 | #include <ThreadHelpers.h> | ||
| 38 | #include <NodeList.h> | 41 | #include <NodeList.h> |
| 39 | #include <plugins/CodecPlugin.h> | 42 | #include <plugins/CodecPlugin.h> |
| 40 | #include <plugins/PluginManager.h> | 43 | #include <plugins/PluginManager.h> |
| @@ -83,52 +86,11 @@ QList<QAudioDeviceInfo> getAvailableDevices(QAudio::Mode mode) { · 42 unmodified lines | |||
| 83 | return QAudioDeviceInfo::availableDevices(mode); | 86 | return QAudioDeviceInfo::availableDevices(mode); |
| 84 | } | 87 | } |
| 85 | 88 | ||
| 86 | class BackgroundThread : public QThread { | ||
| 87 | public: | ||
| 88 | BackgroundThread(AudioClient* client) : QThread((QObject*)client), _client(client) {} | ||
| 89 | virtual void join() = 0; | ||
| 90 | protected: | ||
| 91 | AudioClient* _client; | ||
| 92 | }; | ||
| 93 | |||
| 94 | // background thread continuously polling device changes | ||
| 95 | class CheckDevicesThread : public BackgroundThread { | ||
| 96 | public: | ||
| 97 | CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {} | ||
| 98 | |||
| 99 | void join() override { | ||
| 100 | _shouldQuit = true; | ||
| 101 | std::unique_lock<std::mutex> lock(_joinMutex); | ||
| 102 | _joinCondition.wait(lock, [&]{ return !_isRunning; }); | ||
| 103 | } | ||
| 104 | |||
| 105 | protected: | ||
| 106 | void run() override { | ||
| 107 | while (!_shouldQuit) { | ||
| 108 | _client->checkDevices(); | ||
| 109 | |||
| 110 | const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; | ||
| 111 | QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); | ||
| 112 | } | ||
| 113 | std::lock_guard<std::mutex> lock(_joinMutex); | ||
| 114 | _isRunning = false; | ||
| 115 | _joinCondition.notify_one(); | ||
| 116 | } | ||
| 117 | |||
| 118 | private: | ||
| 119 | std::atomic<bool> _shouldQuit { false }; | ||
| 120 | bool _isRunning { true }; | ||
| 121 | std::mutex _joinMutex; | ||
| 122 | std::condition_variable _joinCondition; | ||
| 123 | }; | ||
| 124 | |||
| 125 | // now called from a background thread, to keep blocking operations off the audio thread | 89 | // now called from a background thread, to keep blocking operations off the audio thread |
| 126 | void AudioClient::checkDevices() { | 90 | void AudioClient::checkDevices() { |
| 127 | auto inputDevices = getAvailableDevices(QAudio::AudioInput); | 91 | auto inputDevices = getAvailableDevices(QAudio::AudioInput); |
| 128 | auto outputDevices = getAvailableDevices(QAudio::AudioOutput); | 92 | auto outputDevices = getAvailableDevices(QAudio::AudioOutput); |
| 129 | 93 | ||
| 130 | Lock lock(_deviceMutex); | ||
| 131 | |||
| 132 | if (inputDevices != _inputDevices) { | 94 | if (inputDevices != _inputDevices) { |
| 133 | _inputDevices.swap(inputDevices); | 95 | _inputDevices.swap(inputDevices); |
| 134 | emit devicesChanged(QAudio::AudioInput, _inputDevices); | 96 | emit devicesChanged(QAudio::AudioInput, _inputDevices); |
| @@ -160,21 +122,6 @@ QList<QAudioDeviceInfo> AudioClient::getAudioDevices(QAudio::Mode mode) const { · 25 unmodified lines | |||
| 160 | } | 122 | } |
| 161 | } | 123 | } |
| 162 | 124 | ||
| 163 | |||
| 164 | // background thread buffering local injectors | ||
| 165 | class LocalInjectorsThread : public BackgroundThread { | ||
| 166 | Q_OBJECT | ||
| 167 | public: | ||
| 168 | LocalInjectorsThread(AudioClient* client) : BackgroundThread(client) {} | ||
| 169 | |||
| 170 | void join() override { return; } | ||
| 171 | |||
| 172 | private slots: | ||
| 173 | void prepare() { _client->prepareLocalAudioInjectors(); } | ||
| 174 | }; | ||
| 175 | |||
| 176 | #include "AudioClient.moc" | ||
| 177 | |||
| 178 | static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) { | 125 | static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) { |
| 179 | for (int i = 0; i < numSamples/2; i++) { | 126 | for (int i = 0; i < numSamples/2; i++) { |
| 180 | 127 | ||
| @@ -262,7 +209,10 @@ AudioClient::AudioClient() : · 81 unmodified lines | |||
| 262 | 209 | ||
| 263 | connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, | 210 | connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, |
| 264 | this, &AudioClient::processReceivedSamples, Qt::DirectConnection); | 211 | this, &AudioClient::processReceivedSamples, Qt::DirectConnection); |
| 265 | connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { switchOutputToAudioDevice(outputDeviceInfo); }); | 212 | connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { |
| 213 | qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; | ||
| 214 | switchOutputToAudioDevice(outputDeviceInfo); | ||
| 215 | }); | ||
| 266 | 216 | ||
| 267 | connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); | 217 | connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); |
| 268 | 218 | ||
| @@ -270,16 +220,15 @@ AudioClient::AudioClient() : · 1 unmodified line | |||
| 270 | getAvailableDevices(QAudio::AudioInput); | 220 | getAvailableDevices(QAudio::AudioInput); |
| 271 | getAvailableDevices(QAudio::AudioOutput); | 221 | getAvailableDevices(QAudio::AudioOutput); |
| 272 | 222 | ||
| 223 | |||
| 273 | // start a thread to detect any device changes | 224 | // start a thread to detect any device changes |
| 274 | _checkDevicesThread = new CheckDevicesThread(this); | 225 | _checkDevicesTimer = new QTimer(this); |
| 275 | _checkDevicesThread->setObjectName("AudioClient CheckDevices Thread"); | 226 | connect(_checkDevicesTimer, &QTimer::timeout, [this] { |
| 276 | _checkDevicesThread->setPriority(QThread::LowPriority); | 227 | QtConcurrent::run(QThreadPool::globalInstance(), [this] { |
| 277 | _checkDevicesThread->start(); | 228 | checkDevices(); |
| 229 | }); | ||
| 230 | }); | ||
| 278 | 231 | ||
| 279 | // start a thread to process local injectors | ||
| 280 | _localInjectorsThread = new LocalInjectorsThread(this); | ||
| 281 | _localInjectorsThread->setObjectName("AudioClient LocalInjectors Thread"); | ||
| 282 | _localInjectorsThread->start(); | ||
| 283 | 232 | ||
| 284 | configureReverb(); | 233 | configureReverb(); |
| 285 | 234 | ||
| @@ -310,15 +259,7 @@ void AudioClient::cleanupBeforeQuit() { · 24 unmodified lines | |||
| 310 | 259 | ||
| 311 | stop(); | 260 | stop(); |
| 312 | 261 | ||
| 313 | if (_checkDevicesThread) { | 262 | _checkDevicesTimer->stop(); |
| 314 | static_cast<BackgroundThread*>(_checkDevicesThread)->join(); | ||
| 315 | delete _checkDevicesThread; | ||
| 316 | } | ||
| 317 | |||
| 318 | if (_localInjectorsThread) { | ||
| 319 | static_cast<BackgroundThread*>(_localInjectorsThread)->join(); | ||
| 320 | delete _localInjectorsThread; | ||
| 321 | } | ||
| 322 | } | 263 | } |
| 323 | 264 | ||
| 324 | void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { | 265 | void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { |
| @@ -478,7 +419,8 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { · 153 unmodified lines | |||
| 478 | CoUninitialize(); | 419 | CoUninitialize(); |
| 479 | } | 420 | } |
| 480 | 421 | ||
| 481 | qCDebug(audioclient) << "[" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; | 422 | qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input") |
| 423 | << " [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; | ||
| 482 | 424 | ||
| 483 | return getNamedAudioDeviceForMode(mode, deviceName); | 425 | return getNamedAudioDeviceForMode(mode, deviceName); |
| 484 | #endif | 426 | #endif |
| @@ -654,8 +596,12 @@ void AudioClient::start() { · 169 unmodified lines | |||
| 654 | } | 596 | } |
| 655 | 597 | ||
| 656 | void AudioClient::stop() { | 598 | void AudioClient::stop() { |
| 599 | |||
| 657 | // "switch" to invalid devices in order to shut down the state | 600 | // "switch" to invalid devices in order to shut down the state |
| 601 | qCDebug(audioclient) << "AudioClient::stop(), about to call switchInputToAudioDevice(null)"; | ||
| 658 | switchInputToAudioDevice(QAudioDeviceInfo()); | 602 | switchInputToAudioDevice(QAudioDeviceInfo()); |
| 603 | |||
| 604 | qCDebug(audioclient) << "AudioClient::stop(), about to call switchOutputToAudioDevice(null)"; | ||
| 659 | switchOutputToAudioDevice(QAudioDeviceInfo()); | 605 | switchOutputToAudioDevice(QAudioDeviceInfo()); |
| 660 | } | 606 | } |
| 661 | 607 | ||
| @@ -1402,10 +1348,8 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) { · 740 unmodified lines | |||
| 1402 | if (!_activeLocalAudioInjectors.contains(injector)) { | 1348 | if (!_activeLocalAudioInjectors.contains(injector)) { |
| 1403 | qCDebug(audioclient) << "adding new injector"; | 1349 | qCDebug(audioclient) << "adding new injector"; |
| 1404 | _activeLocalAudioInjectors.append(injector); | 1350 | _activeLocalAudioInjectors.append(injector); |
| 1405 | |||
| 1406 | // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) | 1351 | // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) |
| 1407 | injectorBuffer->setParent(nullptr); | 1352 | injectorBuffer->setParent(nullptr); |
| 1408 | injectorBuffer->moveToThread(_localInjectorsThread); | ||
| 1409 | 1353 | ||
| 1410 | // update the flag | 1354 | // update the flag |
| 1411 | _localInjectorsAvailable.exchange(true, std::memory_order_release); | 1355 | _localInjectorsAvailable.exchange(true, std::memory_order_release); |
| @@ -1428,6 +1372,7 @@ void AudioClient::outputFormatChanged() { · 16 unmodified lines | |||
| 1428 | } | 1372 | } |
| 1429 | 1373 | ||
| 1430 | bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { | 1374 | bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { |
| 1375 | qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]"; | ||
| 1431 | bool supportedFormat = false; | 1376 | bool supportedFormat = false; |
| 1432 | 1377 | ||
| 1433 | // NOTE: device start() uses the Qt internal device list | 1378 | // NOTE: device start() uses the Qt internal device list |
| @@ -1542,6 +1487,8 @@ void AudioClient::outputNotify() { · 108 unmodified lines | |||
| 1542 | } | 1487 | } |
| 1543 | 1488 | ||
| 1544 | bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { | 1489 | bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { |
| 1490 | qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; | ||
| 1491 | |||
| 1545 | bool supportedFormat = false; | 1492 | bool supportedFormat = false; |
| 1546 | 1493 | ||
| 1547 | // NOTE: device start() uses the Qt internal device list | 1494 | // NOTE: device start() uses the Qt internal device list |
| @@ -1678,7 +1625,11 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice · 130 unmodified lines | |||
| 1678 | } | 1625 | } |
| 1679 | 1626 | ||
| 1680 | int AudioClient::setOutputBufferSize(int numFrames, bool persist) { | 1627 | int AudioClient::setOutputBufferSize(int numFrames, bool persist) { |
| 1628 | qCDebug(audioclient) << __FUNCTION__ << "numFrames:" << numFrames << "persist:" << persist; | ||
| 1629 | |||
| 1681 | numFrames = std::min(std::max(numFrames, MIN_BUFFER_FRAMES), MAX_BUFFER_FRAMES); | 1630 | numFrames = std::min(std::max(numFrames, MIN_BUFFER_FRAMES), MAX_BUFFER_FRAMES); |
| 1631 | qCDebug(audioclient) << __FUNCTION__ << "clamped numFrames:" << numFrames << "_sessionOutputBufferSizeFrames:" << _sessionOutputBufferSizeFrames; | ||
| 1632 | |||
| 1682 | if (numFrames != _sessionOutputBufferSizeFrames) { | 1633 | if (numFrames != _sessionOutputBufferSizeFrames) { |
| 1683 | qCInfo(audioclient, "Audio output buffer set to %d frames", numFrames); | 1634 | qCInfo(audioclient, "Audio output buffer set to %d frames", numFrames); |
| 1684 | _sessionOutputBufferSizeFrames = numFrames; | 1635 | _sessionOutputBufferSizeFrames = numFrames; |
| @@ -1690,6 +1641,7 @@ int AudioClient::setOutputBufferSize(int numFrames, bool persist) { · 5 unmodified lines | |||
| 1690 | // The buffer size can't be adjusted after QAudioOutput::start() has been called, so | 1641 | // The buffer size can't be adjusted after QAudioOutput::start() has been called, so |
| 1691 | // recreate the device by switching to the default. | 1642 | // recreate the device by switching to the default. |
| 1692 | QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); | 1643 | QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); |
| 1644 | qCDebug(audioclient) << __FUNCTION__ << "about to send changeDevice signal outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; | ||
| 1693 | emit changeDevice(outputDeviceInfo); // On correct thread, please, as setOutputBufferSize can be called from main thread. | 1645 | emit changeDevice(outputDeviceInfo); // On correct thread, please, as setOutputBufferSize can be called from main thread. |
| 1694 | } | 1646 | } |
| 1695 | } | 1647 | } |
| @@ -1817,7 +1769,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { · 121 unmodified lines | |||
| 1817 | } | 1769 | } |
| 1818 | 1770 | ||
| 1819 | // prepare injectors for the next callback | 1771 | // prepare injectors for the next callback |
| 1820 | QMetaObject::invokeMethod(_audio->_localInjectorsThread, "prepare", Qt::QueuedConnection); | 1772 | QtConcurrent::run(QThreadPool::globalInstance(), [this] { |
| 1773 | _audio->prepareLocalAudioInjectors(); | ||
| 1774 | }); | ||
| 1821 | 1775 | ||
| 1822 | int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped); | 1776 | int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped); |
| 1823 | int framesPopped = samplesPopped / AudioConstants::STEREO; | 1777 | int framesPopped = samplesPopped / AudioConstants::STEREO; |
| @@ -1877,3 +1831,8 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca · 53 unmodified lines | |||
| 1877 | avatarBoundingBoxCorner = corner; | 1831 | avatarBoundingBoxCorner = corner; |
| 1878 | avatarBoundingBoxScale = scale; | 1832 | avatarBoundingBoxScale = scale; |
| 1879 | } | 1833 | } |
| 1834 | |||
| 1835 | |||
| 1836 | void AudioClient::startThread() { | ||
| 1837 | moveToNewNamedThread(this, "Audio Thread", [this] { start(); }); | ||
| 1838 | } | ||
| @@ -104,6 +104,7 @@ public: · 103 unmodified lines | |||
| 104 | int _unfulfilledReads; | 104 | int _unfulfilledReads; |
| 105 | }; | 105 | }; |
| 106 | 106 | ||
| 107 | void startThread(); | ||
| 107 | void negotiateAudioFormat(); | 108 | void negotiateAudioFormat(); |
| 108 | void selectAudioFormat(const QString& selectedCodecName); | 109 | void selectAudioFormat(const QString& selectedCodecName); |
| 109 | 110 | ||
| @@ -386,8 +387,7 @@ private: · 276 unmodified lines | |||
| 386 | RateCounter<> _silentInbound; | 387 | RateCounter<> _silentInbound; |
| 387 | RateCounter<> _audioInbound; | 388 | RateCounter<> _audioInbound; |
| 388 | 389 | ||
| 389 | QThread* _checkDevicesThread { nullptr }; | 390 | QTimer* _checkDevicesTimer { nullptr }; |
| 390 | QThread* _localInjectorsThread { nullptr }; | ||
| 391 | }; | 391 | }; |
| 392 | 392 | ||
| 393 | 393 | ||
| @@ -13,6 +13,8 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | #include <glm/glm.hpp> | 14 | #include <glm/glm.hpp> |
| 15 | 15 | ||
| 16 | #include <QRunnable> | ||
| 17 | #include <QThreadPool> | ||
| 16 | #include <QDataStream> | 18 | #include <QDataStream> |
| 17 | #include <QtCore/QDebug> | 19 | #include <QtCore/QDebug> |
| 18 | #include <QtNetwork/QNetworkRequest> | 20 | #include <QtNetwork/QNetworkRequest> |
| @@ -49,13 +51,43 @@ Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : · 30 unmodified lines | |||
| 49 | _isAmbisonic(isAmbisonic), | 51 | _isAmbisonic(isAmbisonic), |
| 50 | _isReady(false) | 52 | _isReady(false) |
| 51 | { | 53 | { |
| 52 | |||
| 53 | } | 54 | } |
| 54 | 55 | ||
| 55 | void Sound::downloadFinished(const QByteArray& data) { | 56 | void Sound::downloadFinished(const QByteArray& data) { |
| 57 | // this is a QRunnable, will delete itself after it has finished running | ||
| 58 | SoundProcessor* soundProcessor = new SoundProcessor(_url, data, _isStereo, _isAmbisonic); | ||
| 59 | connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess); | ||
| 60 | connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError); | ||
| 61 | QThreadPool::globalInstance()->start(soundProcessor); | ||
| 62 | } | ||
| 63 | |||
| 64 | void Sound::soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration) { | ||
| 65 | |||
| 66 | qCDebug(audio) << "Setting ready state for sound file" << _url.toDisplayString(); | ||
| 67 | |||
| 68 | _byteArray = data; | ||
| 69 | _isStereo = stereo; | ||
| 70 | _isAmbisonic = ambisonic; | ||
| 71 | _duration = duration; | ||
| 72 | _isReady = true; | ||
| 73 | finishedLoading(true); | ||
| 74 | |||
| 75 | emit ready(); | ||
| 76 | } | ||
| 77 | |||
| 78 | void Sound::soundProcessError(int error, QString str) { | ||
| 79 | qCCritical(audio) << "Failed to process sound file" << _url.toDisplayString() << "code =" << error << str; | ||
| 80 | emit failed(QNetworkReply::UnknownContentError); | ||
| 81 | finishedLoading(false); | ||
| 82 | } | ||
| 83 | |||
| 84 | void SoundProcessor::run() { | ||
| 85 | |||
| 86 | qCDebug(audio) << "Processing sound file" << _url.toDisplayString(); | ||
| 87 | |||
| 56 | // replace our byte array with the downloaded data | 88 | // replace our byte array with the downloaded data |
| 57 | QByteArray rawAudioByteArray = QByteArray(data); | 89 | QByteArray rawAudioByteArray = QByteArray(_data); |
| 58 | QString fileName = getURL().fileName().toLower(); | 90 | QString fileName = _url.fileName().toLower(); |
| 59 | 91 | ||
| 60 | static const QString WAV_EXTENSION = ".wav"; | 92 | static const QString WAV_EXTENSION = ".wav"; |
| 61 | static const QString RAW_EXTENSION = ".raw"; | 93 | static const QString RAW_EXTENSION = ".raw"; |
| @@ -72,31 +104,28 @@ void Sound::downloadFinished(const QByteArray& data) { · 10 unmodified lines | |||
| 72 | // since it's raw the only way for us to know that is if the file was called .stereo.raw | 104 | // since it's raw the only way for us to know that is if the file was called .stereo.raw |
| 73 | if (fileName.toLower().endsWith("stereo.raw")) { | 105 | if (fileName.toLower().endsWith("stereo.raw")) { |
| 74 | _isStereo = true; | 106 | _isStereo = true; |
| 75 | qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file."; | 107 | qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << _url << "as stereo audio file."; |
| 76 | } | 108 | } |
| 77 | 109 | ||
| 78 | // Process as 48khz RAW file | 110 | // Process as 48khz RAW file |
| 79 | downSample(rawAudioByteArray, 48000); | 111 | downSample(rawAudioByteArray, 48000); |
| 80 | } else { | 112 | } else { |
| 81 | qCDebug(audio) << "Unknown sound file type"; | 113 | qCDebug(audio) << "Unknown sound file type"; |
| 114 | emit onError(300, "Failed to load sound file, reason: unknown sound file type"); | ||
| 115 | return; | ||
| 82 | } | 116 | } |
| 83 | 117 | ||
| 84 | finishedLoading(true); | 118 | emit onSuccess(_data, _isStereo, _isAmbisonic, _duration); |
| 85 | |||
| 86 | _isReady = true; | ||
| 87 | emit ready(); | ||
| 88 | } | 119 | } |
| 89 | 120 | ||
| 90 | void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) { | 121 | void SoundProcessor::downSample(const QByteArray& rawAudioByteArray, int sampleRate) { |
| 91 | 122 | ||
| 92 | // we want to convert it to the format that the audio-mixer wants | 123 | // we want to convert it to the format that the audio-mixer wants |
| 93 | // which is signed, 16-bit, 24Khz | 124 | // which is signed, 16-bit, 24Khz |
| 94 | 125 | ||
| 95 | if (sampleRate == AudioConstants::SAMPLE_RATE) { | 126 | if (sampleRate == AudioConstants::SAMPLE_RATE) { |
| 96 | |||
| 97 | // no resampling needed | 127 | // no resampling needed |
| 98 | _byteArray = rawAudioByteArray; | 128 | _data = rawAudioByteArray; |
| 99 | |||
| 100 | } else { | 129 | } else { |
| 101 | 130 | ||
| 102 | int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO); | 131 | int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO); |
| @@ -106,15 +135,15 @@ void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) { · 3 unmodified lines | |||
| 106 | int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample)); | 135 | int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample)); |
| 107 | int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames); | 136 | int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames); |
| 108 | int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); | 137 | int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); |
| 109 | _byteArray.resize(maxDestinationBytes); | 138 | _data.resize(maxDestinationBytes); |
| 110 | 139 | ||
| 111 | int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(), | 140 | int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(), |
| 112 | (int16_t*)_byteArray.data(), | 141 | (int16_t*)_data.data(), |
| 113 | numSourceFrames); | 142 | numSourceFrames); |
| 114 | 143 | ||
| 115 | // truncate to actual output | 144 | // truncate to actual output |
| 116 | int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); | 145 | int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); |
| 117 | _byteArray.resize(numDestinationBytes); | 146 | _data.resize(numDestinationBytes); |
| 118 | } | 147 | } |
| 119 | } | 148 | } |
| 120 | 149 | ||
| @@ -163,7 +192,7 @@ struct WAVEFormat { · 42 unmodified lines | |||
| 163 | }; | 192 | }; |
| 164 | 193 | ||
| 165 | // returns wavfile sample rate, used for resampling | 194 | // returns wavfile sample rate, used for resampling |
| 166 | int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) { | 195 | int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) { |
| 167 | 196 | ||
| 168 | // Create a data stream to analyze the data | 197 | // Create a data stream to analyze the data |
| 169 | QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly); | 198 | QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly); |
| @@ -12,6 +12,7 @@ · 11 unmodified lines | |||
| 12 | #ifndef hifi_Sound_h | 12 | #ifndef hifi_Sound_h |
| 13 | #define hifi_Sound_h | 13 | #define hifi_Sound_h |
| 14 | 14 | ||
| 15 | #include <QRunnable> | ||
| 15 | #include <QtCore/QObject> | 16 | #include <QtCore/QObject> |
| 16 | #include <QtNetwork/QNetworkReply> | 17 | #include <QtNetwork/QNetworkReply> |
| 17 | #include <QtScript/qscriptengine.h> | 18 | #include <QtScript/qscriptengine.h> |
| @@ -28,12 +29,15 @@ public: · 10 unmodified lines | |||
| 28 | bool isAmbisonic() const { return _isAmbisonic; } | 29 | bool isAmbisonic() const { return _isAmbisonic; } |
| 29 | bool isReady() const { return _isReady; } | 30 | bool isReady() const { return _isReady; } |
| 30 | float getDuration() const { return _duration; } | 31 | float getDuration() const { return _duration; } |
| 31 | |||
| 32 | 32 | ||
| 33 | const QByteArray& getByteArray() const { return _byteArray; } | 33 | const QByteArray& getByteArray() const { return _byteArray; } |
| 34 | 34 | ||
| 35 | signals: | 35 | signals: |
| 36 | void ready(); | 36 | void ready(); |
| 37 | |||
| 38 | protected slots: | ||
| 39 | void soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration); | ||
| 40 | void soundProcessError(int error, QString str); | ||
| 37 | 41 | ||
| 38 | private: | 42 | private: |
| 39 | QByteArray _byteArray; | 43 | QByteArray _byteArray; |
| @@ -42,10 +46,33 @@ private: · 2 unmodified lines | |||
| 42 | bool _isReady; | 46 | bool _isReady; |
| 43 | float _duration; // In seconds | 47 | float _duration; // In seconds |
| 44 | 48 | ||
| 49 | virtual void downloadFinished(const QByteArray& data) override; | ||
| 50 | }; | ||
| 51 | |||
| 52 | class SoundProcessor : public QObject, public QRunnable { | ||
| 53 | Q_OBJECT | ||
| 54 | |||
| 55 | public: | ||
| 56 | SoundProcessor(const QUrl& url, const QByteArray& data, bool stereo, bool ambisonic) | ||
| 57 | : _url(url), _data(data), _isStereo(stereo), _isAmbisonic(ambisonic) | ||
| 58 | { | ||
| 59 | } | ||
| 60 | |||
| 61 | virtual void run() override; | ||
| 62 | |||
| 45 | void downSample(const QByteArray& rawAudioByteArray, int sampleRate); | 63 | void downSample(const QByteArray& rawAudioByteArray, int sampleRate); |
| 46 | int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); | 64 | int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); |
| 47 | 65 | ||
| 48 | virtual void downloadFinished(const QByteArray& data) override; | 66 | signals: |
| 67 | void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration); | ||
| 68 | void onError(int error, QString str); | ||
| 69 | |||
| 70 | private: | ||
| 71 | QUrl _url; | ||
| 72 | QByteArray _data; | ||
| 73 | bool _isStereo; | ||
| 74 | bool _isAmbisonic; | ||
| 75 | float _duration; | ||
| 49 | }; | 76 | }; |
| 50 | 77 | ||
| 51 | typedef QSharedPointer<Sound> SharedSoundPointer; | 78 | typedef QSharedPointer<Sound> SharedSoundPointer; |
| @@ -304,18 +304,6 @@ glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const { · 303 unmodified lines | |||
| 304 | return rotationBetween(orientation * IDENTITY_FORWARD, lookAtDelta + glm::length(lookAtDelta) * _saccade) * orientation; | 304 | return rotationBetween(orientation * IDENTITY_FORWARD, lookAtDelta + glm::length(lookAtDelta) * _saccade) * orientation; |
| 305 | } | 305 | } |
| 306 | 306 | ||
| 307 | void Head::setFinalPitch(float finalPitch) { | ||
| 308 | _deltaPitch = glm::clamp(finalPitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH) - _basePitch; | ||
| 309 | } | ||
| 310 | |||
| 311 | void Head::setFinalYaw(float finalYaw) { | ||
| 312 | _deltaYaw = glm::clamp(finalYaw, MIN_HEAD_YAW, MAX_HEAD_YAW) - _baseYaw; | ||
| 313 | } | ||
| 314 | |||
| 315 | void Head::setFinalRoll(float finalRoll) { | ||
| 316 | _deltaRoll = glm::clamp(finalRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL) - _baseRoll; | ||
| 317 | } | ||
| 318 | |||
| 319 | float Head::getFinalYaw() const { | 307 | float Head::getFinalYaw() const { |
| 320 | return glm::clamp(_baseYaw + _deltaYaw, MIN_HEAD_YAW, MAX_HEAD_YAW); | 308 | return glm::clamp(_baseYaw + _deltaYaw, MIN_HEAD_YAW, MAX_HEAD_YAW); |
| 321 | } | 309 | } |
| @@ -71,9 +71,6 @@ public: · 70 unmodified lines | |||
| 71 | void setDeltaRoll(float roll) { _deltaRoll = roll; } | 71 | void setDeltaRoll(float roll) { _deltaRoll = roll; } |
| 72 | float getDeltaRoll() const { return _deltaRoll; } | 72 | float getDeltaRoll() const { return _deltaRoll; } |
| 73 | 73 | ||
| 74 | virtual void setFinalYaw(float finalYaw) override; | ||
| 75 | virtual void setFinalPitch(float finalPitch) override; | ||
| 76 | virtual void setFinalRoll(float finalRoll) override; | ||
| 77 | virtual float getFinalPitch() const override; | 74 | virtual float getFinalPitch() const override; |
| 78 | virtual float getFinalYaw() const override; | 75 | virtual float getFinalYaw() const override; |
| 79 | virtual float getFinalRoll() const override; | 76 | virtual float getFinalRoll() const override; |
| @@ -850,7 +850,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { · 849 unmodified lines | |||
| 850 | glm::quat sensorToWorldQuat; | 850 | glm::quat sensorToWorldQuat; |
| 851 | unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat); | 851 | unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat); |
| 852 | float sensorToWorldScale; | 852 | float sensorToWorldScale; |
| 853 | unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&data->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX); | 853 | // Grab a local copy of sensorToWorldScale to be able to use the unpack function with a pointer on it, |
| 854 | // a direct pointer on the struct attribute triggers warnings because of potential misalignement. | ||
| 855 | auto srcSensorToWorldScale = data->sensorToWorldScale; | ||
| 856 | unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&srcSensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX); | ||
| 854 | glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]); | 857 | glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]); |
| 855 | glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); | 858 | glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); |
| 856 | if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { | 859 | if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { |
| @@ -45,9 +45,6 @@ public: · 44 unmodified lines | |||
| 45 | float getBaseRoll() const { return _baseRoll; } | 45 | float getBaseRoll() const { return _baseRoll; } |
| 46 | void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } | 46 | void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } |
| 47 | 47 | ||
| 48 | virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; } | ||
| 49 | virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; } | ||
| 50 | virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; } | ||
| 51 | virtual float getFinalYaw() const { return _baseYaw; } | 48 | virtual float getFinalYaw() const { return _baseYaw; } |
| 52 | virtual float getFinalPitch() const { return _basePitch; } | 49 | virtual float getFinalPitch() const { return _basePitch; } |
| 53 | virtual float getFinalRoll() const { return _baseRoll; } | 50 | virtual float getFinalRoll() const { return _baseRoll; } |
| @@ -42,8 +42,6 @@ enum class Action { · 41 unmodified lines | |||
| 42 | 42 | ||
| 43 | LEFT_HAND = NUM_COMBINED_AXES, | 43 | LEFT_HAND = NUM_COMBINED_AXES, |
| 44 | RIGHT_HAND, | 44 | RIGHT_HAND, |
| 45 | LEFT_ARM, | ||
| 46 | RIGHT_ARM, | ||
| 47 | LEFT_FOOT, | 45 | LEFT_FOOT, |
| 48 | RIGHT_FOOT, | 46 | RIGHT_FOOT, |
| 49 | HIPS, | 47 | HIPS, |
| @@ -103,6 +101,8 @@ enum class Action { · 53 unmodified lines | |||
| 103 | // Bisected aliases for TRANSLATE_CAMERA_Z | 101 | // Bisected aliases for TRANSLATE_CAMERA_Z |
| 104 | BOOM_IN, | 102 | BOOM_IN, |
| 105 | BOOM_OUT, | 103 | BOOM_OUT, |
| 104 | LEFT_ARM, | ||
| 105 | RIGHT_ARM, | ||
| 106 | 106 | ||
| 107 | 107 | ||
| 108 | NUM_ACTIONS, | 108 | NUM_ACTIONS, |
| @@ -9,7 +9,6 @@ · 8 unmodified lines | |||
| 9 | #include "InputRecorder.h" | 9 | #include "InputRecorder.h" |
| 10 | 10 | ||
| 11 | #include <QJsonArray> | 11 | #include <QJsonArray> |
| 12 | #include <QJsonObject> | ||
| 13 | #include <QJsonDocument> | 12 | #include <QJsonDocument> |
| 14 | #include <QFile> | 13 | #include <QFile> |
| 15 | #include <QDir> | 14 | #include <QDir> |
| @@ -19,13 +18,17 @@ · 3 unmodified lines | |||
| 19 | #include <QByteArray> | 18 | #include <QByteArray> |
| 20 | #include <QStandardPaths> | 19 | #include <QStandardPaths> |
| 21 | #include <PathUtils.h> | 20 | #include <PathUtils.h> |
| 21 | #include <QUrl> | ||
| 22 | #include <Gzip.h> | ||
| 22 | 23 | ||
| 23 | #include <BuildInfo.h> | 24 | #include <BuildInfo.h> |
| 24 | #include <GLMHelpers.h> | 25 | #include <GLMHelpers.h> |
| 26 | #include <DependencyManager.h> | ||
| 27 | #include "UserInputMapper.h" | ||
| 25 | 28 | ||
| 26 | QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/"; | 29 | QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/"; |
| 27 | QString FILE_PREFIX_NAME = "input-recording-"; | 30 | QString FILE_PREFIX_NAME = "input-recording-"; |
| 28 | QString COMPRESS_EXTENSION = "json.gz"; | 31 | QString COMPRESS_EXTENSION = ".json.gz"; |
| 29 | namespace controller { | 32 | namespace controller { |
| 30 | 33 | ||
| 31 | QJsonObject poseToJsonObject(const Pose pose) { | 34 | QJsonObject poseToJsonObject(const Pose pose) { |
| @@ -91,23 +94,27 @@ namespace controller { · 59 unmodified lines | |||
| 91 | } | 94 | } |
| 92 | 95 | ||
| 93 | 96 | ||
| 94 | void exportToFile(QJsonObject& object) { | 97 | void exportToFile(const QJsonObject& object, const QString& fileName) { |
| 95 | if (!QDir(SAVE_DIRECTORY).exists()) { | 98 | if (!QDir(SAVE_DIRECTORY).exists()) { |
| 96 | QDir().mkdir(SAVE_DIRECTORY); | 99 | QDir().mkdir(SAVE_DIRECTORY); |
| 97 | } | 100 | } |
| 98 | 101 | ||
| 99 | QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate); | ||
| 100 | timeStamp.replace(":", "-"); | ||
| 101 | QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION; | ||
| 102 | qDebug() << fileName; | ||
| 103 | QFile saveFile (fileName); | 102 | QFile saveFile (fileName); |
| 104 | if (!saveFile.open(QIODevice::WriteOnly)) { | 103 | if (!saveFile.open(QIODevice::WriteOnly)) { |
| 105 | qWarning() << "could not open file: " << fileName; | 104 | qWarning() << "could not open file: " << fileName; |
| 106 | return; | 105 | return; |
| 107 | } | 106 | } |
| 108 | QJsonDocument saveData(object); | 107 | QJsonDocument saveData(object); |
| 109 | QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact)); | 108 | QByteArray jsonData = saveData.toJson(QJsonDocument::Indented); |
| 110 | saveFile.write(compressedData); | 109 | QByteArray jsonDataForFile; |
| 110 | |||
| 111 | if (!gzip(jsonData, jsonDataForFile, -1)) { | ||
| 112 | qCritical("unable to gzip while saving to json."); | ||
| 113 | return; | ||
| 114 | } | ||
| 115 | |||
| 116 | saveFile.write(jsonDataForFile); | ||
| 117 | saveFile.close(); | ||
| 111 | } | 118 | } |
| 112 | 119 | ||
| 113 | QJsonObject openFile(const QString& file, bool& status) { | 120 | QJsonObject openFile(const QString& file, bool& status) { |
| @@ -118,10 +125,19 @@ namespace controller { · 4 unmodified lines | |||
| 118 | status = false; | 125 | status = false; |
| 119 | return object; | 126 | return object; |
| 120 | } | 127 | } |
| 121 | QByteArray compressedData = qUncompress(openFile.readAll()); | 128 | QByteArray compressedData = openFile.readAll(); |
| 122 | QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData); | 129 | QByteArray jsonData; |
| 130 | |||
| 131 | if (!gunzip(compressedData, jsonData)) { | ||
| 132 | qCritical() << "json file not in gzip format: " << file; | ||
| 133 | status = false; | ||
| 134 | return object; | ||
| 135 | } | ||
| 136 | |||
| 137 | QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); | ||
| 123 | object = jsonDoc.object(); | 138 | object = jsonDoc.object(); |
| 124 | status = true; | 139 | status = true; |
| 140 | openFile.close(); | ||
| 125 | return object; | 141 | return object; |
| 126 | } | 142 | } |
| 127 | 143 | ||
| @@ -146,31 +162,47 @@ namespace controller { · 18 unmodified lines | |||
| 146 | _actionStateList.clear(); | 162 | _actionStateList.clear(); |
| 147 | } | 163 | } |
| 148 | 164 | ||
| 149 | void InputRecorder::saveRecording() { | 165 | QJsonObject InputRecorder::recordDataToJson() { |
| 150 | QJsonObject data; | 166 | QJsonObject data; |
| 151 | data["frameCount"] = _framesRecorded; | 167 | data["frameCount"] = _framesRecorded; |
| 152 | 168 | data["version"] = "0.0"; | |
| 169 | |||
| 153 | QJsonArray actionArrayList; | 170 | QJsonArray actionArrayList; |
| 154 | QJsonArray poseArrayList; | 171 | QJsonArray poseArrayList; |
| 155 | for(const ActionStates actionState: _actionStateList) { | 172 | for(const ActionStates actionState: _actionStateList) { |
| 156 | QJsonArray actionArray; | 173 | QJsonArray actionArray; |
| 157 | for (const float value: actionState) { | 174 | for (const auto action: actionState) { |
| 158 | actionArray.append(value); | 175 | QJsonObject actionJson; |
| 176 | actionJson["name"] = action.first; | ||
| 177 | actionJson["value"] = action.second; | ||
| 178 | actionArray.append(actionJson); | ||
| 159 | } | 179 | } |
| 160 | actionArrayList.append(actionArray); | 180 | actionArrayList.append(actionArray); |
| 161 | } | 181 | } |
| 162 | 182 | ||
| 163 | for (const PoseStates poseState: _poseStateList) { | 183 | for (const PoseStates poseState: _poseStateList) { |
| 164 | QJsonArray poseArray; | 184 | QJsonArray poseArray; |
| 165 | for (const Pose pose: poseState) { | 185 | for (const auto pose: poseState) { |
| 166 | poseArray.append(poseToJsonObject(pose)); | 186 | QJsonObject poseJson; |
| 187 | poseJson["name"] = pose.first; | ||
| 188 | poseJson["pose"] = poseToJsonObject(pose.second); | ||
| 189 | poseArray.append(poseJson); | ||
| 167 | } | 190 | } |
| 168 | poseArrayList.append(poseArray); | 191 | poseArrayList.append(poseArray); |
| 169 | } | 192 | } |
| 170 | 193 | ||
| 171 | data["actionList"] = actionArrayList; | 194 | data["actionList"] = actionArrayList; |
| 172 | data["poseList"] = poseArrayList; | 195 | data["poseList"] = poseArrayList; |
| 173 | exportToFile(data); | 196 | |
| 197 | return data; | ||
| 198 | } | ||
| 199 | |||
| 200 | void InputRecorder::saveRecording() { | ||
| 201 | QJsonObject jsonData = recordDataToJson(); | ||
| 202 | QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate); | ||
| 203 | timeStamp.replace(":", "-"); | ||
| 204 | QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION; | ||
| 205 | exportToFile(jsonData, fileName); | ||
| 174 | } | 206 | } |
| 175 | 207 | ||
| 176 | void InputRecorder::loadRecording(const QString& path) { | 208 | void InputRecorder::loadRecording(const QString& path) { |
| @@ -181,17 +213,20 @@ namespace controller { · 4 unmodified lines | |||
| 181 | resetFrame(); | 213 | resetFrame(); |
| 182 | _poseStateList.clear(); | 214 | _poseStateList.clear(); |
| 183 | _actionStateList.clear(); | 215 | _actionStateList.clear(); |
| 184 | QString filePath = path; | 216 | QUrl urlPath(path); |
| 185 | filePath.remove(0,8); | 217 | QString filePath = urlPath.toLocalFile(); |
| 186 | QFileInfo info(filePath); | 218 | QFileInfo info(filePath); |
| 187 | QString extension = info.suffix(); | 219 | QString extension = info.suffix(); |
| 220 | |||
| 188 | if (extension != "gz") { | 221 | if (extension != "gz") { |
| 189 | qWarning() << "can not load file with exentsion of " << extension; | 222 | qWarning() << "can not load file with exentsion of " << extension; |
| 190 | return; | 223 | return; |
| 191 | } | 224 | } |
| 225 | |||
| 192 | bool success = false; | 226 | bool success = false; |
| 193 | QJsonObject data = openFile(info.absoluteFilePath(), success); | 227 | QJsonObject data = openFile(filePath, success); |
| 194 | if (success) { | 228 | auto keyValue = data.find("version"); |
| 229 | if (success && keyValue != data.end()) { | ||
| 195 | _framesRecorded = data["frameCount"].toInt(); | 230 | _framesRecorded = data["frameCount"].toInt(); |
| 196 | QJsonArray actionArrayList = data["actionList"].toArray(); | 231 | QJsonArray actionArrayList = data["actionList"].toArray(); |
| 197 | QJsonArray poseArrayList = data["poseList"].toArray(); | 232 | QJsonArray poseArrayList = data["poseList"].toArray(); |
| @@ -199,30 +234,31 @@ namespace controller { · 1 unmodified line | |||
| 199 | for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) { | 234 | for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) { |
| 200 | QJsonArray actionState = actionArrayList[actionIndex].toArray(); | 235 | QJsonArray actionState = actionArrayList[actionIndex].toArray(); |
| 201 | for (int index = 0; index < actionState.size(); index++) { | 236 | for (int index = 0; index < actionState.size(); index++) { |
| 202 | _currentFrameActions[index] = actionState[index].toDouble(); | 237 | QJsonObject actionObject = actionState[index].toObject(); |
| 238 | _currentFrameActions[actionObject["name"].toString()] = actionObject["value"].toDouble(); | ||
| 203 | } | 239 | } |
| 204 | _actionStateList.push_back(_currentFrameActions); | 240 | _actionStateList.push_back(_currentFrameActions); |
| 205 | _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS)); | 241 | _currentFrameActions.clear(); |
| 206 | } | 242 | } |
| 207 | 243 | ||
| 208 | for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) { | 244 | for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) { |
| 209 | QJsonArray poseState = poseArrayList[poseIndex].toArray(); | 245 | QJsonArray poseState = poseArrayList[poseIndex].toArray(); |
| 210 | for (int index = 0; index < poseState.size(); index++) { | 246 | for (int index = 0; index < poseState.size(); index++) { |
| 211 | _currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject()); | 247 | QJsonObject poseObject = poseState[index].toObject(); |
| 248 | _currentFramePoses[poseObject["name"].toString()] = jsonObjectToPose(poseObject["pose"].toObject()); | ||
| 212 | } | 249 | } |
| 213 | _poseStateList.push_back(_currentFramePoses); | 250 | _poseStateList.push_back(_currentFramePoses); |
| 214 | _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS)); | 251 | _currentFramePoses.clear(); |
| 215 | } | 252 | } |
| 216 | } | 253 | } |
| 217 | |||
| 218 | _loading = false; | 254 | _loading = false; |
| 219 | } | 255 | } |
| 220 | 256 | ||
| 221 | void InputRecorder::stopRecording() { | 257 | void InputRecorder::stopRecording() { |
| 222 | _recording = false; | 258 | _recording = false; |
| 223 | _framesRecorded = (int)_actionStateList.size(); | 259 | _framesRecorded = (int)_actionStateList.size(); |
| 224 | } | 260 | } |
| 225 | 261 | ||
| 226 | void InputRecorder::startPlayback() { | 262 | void InputRecorder::startPlayback() { |
| 227 | _playback = true; | 263 | _playback = true; |
| 228 | _recording = false; | 264 | _recording = false; |
| @@ -234,41 +270,36 @@ namespace controller { · 5 unmodified lines | |||
| 234 | _playCount = 0; | 270 | _playCount = 0; |
| 235 | } | 271 | } |
| 236 | 272 | ||
| 237 | void InputRecorder::setActionState(controller::Action action, float value) { | 273 | void InputRecorder::setActionState(const QString& action, float value) { |
| 238 | if (_recording) { | 274 | if (_recording) { |
| 239 | _currentFrameActions[toInt(action)] += value; | 275 | _currentFrameActions[action] += value; |
| 240 | } | 276 | } |
| 241 | } | 277 | } |
| 242 | 278 | ||
| 243 | void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) { | 279 | void InputRecorder::setActionState(const QString& action, const controller::Pose& pose) { |
| 244 | if (_recording) { | 280 | if (_recording) { |
| 245 | _currentFramePoses[toInt(action)] = pose; | 281 | _currentFramePoses[action] = pose; |
| 246 | } | 282 | } |
| 247 | } | 283 | } |
| 248 | 284 | ||
| 249 | void InputRecorder::resetFrame() { | 285 | void InputRecorder::resetFrame() { |
| 250 | if (_recording) { | 286 | if (_recording) { |
| 251 | for(auto& channel : _currentFramePoses) { | 287 | _currentFramePoses.clear(); |
| 252 | channel = Pose(); | 288 | _currentFrameActions.clear(); |
| 253 | } | ||
| 254 | |||
| 255 | for(auto& channel : _currentFrameActions) { | ||
| 256 | channel = 0.0f; | ||
| 257 | } | ||
| 258 | } | 289 | } |
| 259 | } | 290 | } |
| 260 | 291 | ||
| 261 | float InputRecorder::getActionState(controller::Action action) { | 292 | float InputRecorder::getActionState(const QString& action) { |
| 262 | if (_actionStateList.size() > 0 ) { | 293 | if (_actionStateList.size() > 0 ) { |
| 263 | return _actionStateList[_playCount][toInt(action)]; | 294 | return _actionStateList[_playCount][action]; |
| 264 | } | 295 | } |
| 265 | 296 | ||
| 266 | return 0.0f; | 297 | return 0.0f; |
| 267 | } | 298 | } |
| 268 | 299 | ||
| 269 | controller::Pose InputRecorder::getPoseState(controller::Action action) { | 300 | controller::Pose InputRecorder::getPoseState(const QString& action) { |
| 270 | if (_poseStateList.size() > 0) { | 301 | if (_poseStateList.size() > 0) { |
| 271 | return _poseStateList[_playCount][toInt(action)]; | 302 | return _poseStateList[_playCount][action]; |
| 272 | } | 303 | } |
| 273 | 304 | ||
| 274 | return Pose(); | 305 | return Pose(); |
| @@ -12,8 +12,10 @@ · 11 unmodified lines | |||
| 12 | #include <mutex> | 12 | #include <mutex> |
| 13 | #include <atomic> | 13 | #include <atomic> |
| 14 | #include <vector> | 14 | #include <vector> |
| 15 | #include <map> | ||
| 15 | 16 | ||
| 16 | #include <QString> | 17 | #include <QString> |
| 18 | #include <QJsonObject> | ||
| 17 | 19 | ||
| 18 | #include "Pose.h" | 20 | #include "Pose.h" |
| 19 | #include "Actions.h" | 21 | #include "Actions.h" |
| @@ -21,8 +23,8 @@ · 1 unmodified line | |||
| 21 | namespace controller { | 23 | namespace controller { |
| 22 | class InputRecorder { | 24 | class InputRecorder { |
| 23 | public: | 25 | public: |
| 24 | using PoseStates = std::vector<Pose>; | 26 | using PoseStates = std::map<QString, Pose>; |
| 25 | using ActionStates = std::vector<float>; | 27 | using ActionStates = std::map<QString, float>; |
| 26 | 28 | ||
| 27 | InputRecorder(); | 29 | InputRecorder(); |
| 28 | ~InputRecorder(); | 30 | ~InputRecorder(); |
| @@ -40,20 +42,21 @@ namespace controller { · 11 unmodified lines | |||
| 40 | void resetFrame(); | 42 | void resetFrame(); |
| 41 | bool isRecording() { return _recording; } | 43 | bool isRecording() { return _recording; } |
| 42 | bool isPlayingback() { return (_playback && !_loading); } | 44 | bool isPlayingback() { return (_playback && !_loading); } |
| 43 | void setActionState(controller::Action action, float value); | 45 | void setActionState(const QString& action, float value); |
| 44 | void setActionState(controller::Action action, const controller::Pose pose); | 46 | void setActionState(const QString& action, const controller::Pose& pose); |
| 45 | float getActionState(controller::Action action); | 47 | float getActionState(const QString& action); |
| 46 | controller::Pose getPoseState(controller::Action action); | 48 | controller::Pose getPoseState(const QString& action); |
| 47 | QString getSaveDirectory(); | 49 | QString getSaveDirectory(); |
| 48 | void frameTick(); | 50 | void frameTick(); |
| 49 | private: | 51 | private: |
| 52 | QJsonObject recordDataToJson(); | ||
| 50 | bool _recording { false }; | 53 | bool _recording { false }; |
| 51 | bool _playback { false }; | 54 | bool _playback { false }; |
| 52 | bool _loading { false }; | 55 | bool _loading { false }; |
| 53 | std::vector<PoseStates> _poseStateList = std::vector<PoseStates>(); | 56 | std::vector<PoseStates> _poseStateList = std::vector<PoseStates>(); |
| 54 | std::vector<ActionStates> _actionStateList = std::vector<ActionStates>(); | 57 | std::vector<ActionStates> _actionStateList = std::vector<ActionStates>(); |
| 55 | PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS)); | 58 | PoseStates _currentFramePoses; |
| 56 | ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS)); | 59 | ActionStates _currentFrameActions; |
| 57 | 60 | ||
| 58 | int _framesRecorded { 0 }; | 61 | int _framesRecorded { 0 }; |
| 59 | int _playCount { 0 }; | 62 | int _playCount { 0 }; |
| @@ -39,6 +39,7 @@ namespace controller { · 38 unmodified lines | |||
| 39 | quat getRotation() const { return rotation; } | 39 | quat getRotation() const { return rotation; } |
| 40 | vec3 getVelocity() const { return velocity; } | 40 | vec3 getVelocity() const { return velocity; } |
| 41 | vec3 getAngularVelocity() const { return angularVelocity; } | 41 | vec3 getAngularVelocity() const { return angularVelocity; } |
| 42 | mat4 getMatrix() const { return createMatFromQuatAndPos(rotation, translation); } | ||
| 42 | 43 | ||
| 43 | Pose transform(const glm::mat4& mat) const; | 44 | Pose transform(const glm::mat4& mat) const; |
| 44 | Pose postTransform(const glm::mat4& mat) const; | 45 | Pose postTransform(const glm::mat4& mat) const; |
| @@ -329,6 +329,16 @@ QString UserInputMapper::getActionName(Action action) const { · 328 unmodified lines | |||
| 329 | return QString(); | 329 | return QString(); |
| 330 | } | 330 | } |
| 331 | 331 | ||
| 332 | QString UserInputMapper::getStandardPoseName(uint16_t pose) { | ||
| 333 | Locker locker(_lock); | ||
| 334 | for (auto posePair : getStandardInputs()) { | ||
| 335 | if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) { | ||
| 336 | return posePair.second; | ||
| 337 | } | ||
| 338 | } | ||
| 339 | return QString(); | ||
| 340 | } | ||
| 341 | |||
| 332 | QVector<QString> UserInputMapper::getActionNames() const { | 342 | QVector<QString> UserInputMapper::getActionNames() const { |
| 333 | Locker locker(_lock); | 343 | Locker locker(_lock); |
| 334 | QVector<QString> result; | 344 | QVector<QString> result; |
| @@ -80,6 +80,7 @@ namespace controller { · 79 unmodified lines | |||
| 80 | 80 | ||
| 81 | QVector<Action> getAllActions() const; | 81 | QVector<Action> getAllActions() const; |
| 82 | QString getActionName(Action action) const; | 82 | QString getActionName(Action action) const; |
| 83 | QString getStandardPoseName(uint16_t pose); | ||
| 83 | float getActionState(Action action) const { return _actionStates[toInt(action)]; } | 84 | float getActionState(Action action) const { return _actionStates[toInt(action)]; } |
| 84 | Pose getPoseState(Action action) const; | 85 | Pose getPoseState(Action action) const; |
| 85 | int findAction(const QString& actionName) const; | 86 | int findAction(const QString& actionName) const; |
| @@ -17,31 +17,30 @@ using namespace controller; · 16 unmodified lines | |||
| 17 | 17 | ||
| 18 | void ActionEndpoint::apply(float newValue, const Pointer& source) { | 18 | void ActionEndpoint::apply(float newValue, const Pointer& source) { |
| 19 | InputRecorder* inputRecorder = InputRecorder::getInstance(); | 19 | InputRecorder* inputRecorder = InputRecorder::getInstance(); |
| 20 | auto userInputMapper = DependencyManager::get<UserInputMapper>(); | ||
| 21 | QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); | ||
| 20 | if(inputRecorder->isPlayingback()) { | 22 | if(inputRecorder->isPlayingback()) { |
| 21 | newValue = inputRecorder->getActionState(Action(_input.getChannel())); | 23 | newValue = inputRecorder->getActionState(actionName); |
| 22 | } | 24 | } |
| 23 | 25 | ||
| 24 | _currentValue += newValue; | 26 | _currentValue += newValue; |
| 25 | if (_input != Input::INVALID_INPUT) { | 27 | if (_input != Input::INVALID_INPUT) { |
| 26 | auto userInputMapper = DependencyManager::get<UserInputMapper>(); | ||
| 27 | userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); | 28 | userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); |
| 28 | } | 29 | } |
| 29 | inputRecorder->setActionState(Action(_input.getChannel()), newValue); | 30 | inputRecorder->setActionState(actionName, newValue); |
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | void ActionEndpoint::apply(const Pose& value, const Pointer& source) { | 33 | void ActionEndpoint::apply(const Pose& value, const Pointer& source) { |
| 33 | _currentPose = value; | 34 | _currentPose = value; |
| 34 | InputRecorder* inputRecorder = InputRecorder::getInstance(); | 35 | InputRecorder* inputRecorder = InputRecorder::getInstance(); |
| 35 | inputRecorder->setActionState(Action(_input.getChannel()), _currentPose); | 36 | auto userInputMapper = DependencyManager::get<UserInputMapper>(); |
| 36 | if (inputRecorder->isPlayingback()) { | 37 | QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); |
| 37 | _currentPose = inputRecorder->getPoseState(Action(_input.getChannel())); | 38 | inputRecorder->setActionState(actionName, _currentPose); |
| 38 | } | ||
| 39 | 39 | ||
| 40 | if (!_currentPose.isValid()) { | 40 | if (!_currentPose.isValid()) { |
| 41 | return; | 41 | return; |
| 42 | } | 42 | } |
| 43 | if (_input != Input::INVALID_INPUT) { | 43 | if (_input != Input::INVALID_INPUT) { |
| 44 | auto userInputMapper = DependencyManager::get<UserInputMapper>(); | ||
| 45 | userInputMapper->setActionState(Action(_input.getChannel()), _currentPose); | 44 | userInputMapper->setActionState(Action(_input.getChannel()), _currentPose); |
| 46 | } | 45 | } |
| 47 | } | 46 | } |
| @@ -12,6 +12,11 @@ · 11 unmodified lines | |||
| 12 | 12 | ||
| 13 | #include "../Endpoint.h" | 13 | #include "../Endpoint.h" |
| 14 | 14 | ||
| 15 | #include <DependencyManager.h> | ||
| 16 | |||
| 17 | #include "../../InputRecorder.h" | ||
| 18 | #include "../../UserInputMapper.h" | ||
| 19 | |||
| 15 | namespace controller { | 20 | namespace controller { |
| 16 | 21 | ||
| 17 | class StandardEndpoint : public VirtualEndpoint { | 22 | class StandardEndpoint : public VirtualEndpoint { |
| @@ -40,6 +45,12 @@ public: · 22 unmodified lines | |||
| 40 | 45 | ||
| 41 | virtual Pose pose() override { | 46 | virtual Pose pose() override { |
| 42 | _read = true; | 47 | _read = true; |
| 48 | InputRecorder* inputRecorder = InputRecorder::getInstance(); | ||
| 49 | if (inputRecorder->isPlayingback()) { | ||
| 50 | auto userInputMapper = DependencyManager::get<UserInputMapper>(); | ||
| 51 | QString actionName = userInputMapper->getStandardPoseName(_input.getChannel()); | ||
| 52 | return inputRecorder->getPoseState(actionName); | ||
| 53 | } | ||
| 43 | return VirtualEndpoint::pose(); | 54 | return VirtualEndpoint::pose(); |
| 44 | } | 55 | } |
| 45 | 56 | ||
| @@ -21,9 +21,9 @@ namespace controller { · 20 unmodified lines | |||
| 21 | LowVelocityFilter(float rotationConstant, float translationConstant) : | 21 | LowVelocityFilter(float rotationConstant, float translationConstant) : |
| 22 | _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} | 22 | _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} |
| 23 | 23 | ||
| 24 | virtual float apply(float value) const override { return value; } | 24 | float apply(float value) const override { return value; } |
| 25 | virtual Pose apply(Pose newPose) const; | 25 | Pose apply(Pose newPose) const override; |
| 26 | virtual bool parseParameters(const QJsonValue& parameters) override; | 26 | bool parseParameters(const QJsonValue& parameters) override; |
| 27 | 27 | ||
| 28 | private: | 28 | private: |
| 29 | float _translationConstant { 0.1f }; | 29 | float _translationConstant { 0.1f }; |
| @@ -339,6 +339,18 @@ void OpenGLDisplayPlugin::deactivate() { · 338 unmodified lines | |||
| 339 | Parent::deactivate(); | 339 | Parent::deactivate(); |
| 340 | } | 340 | } |
| 341 | 341 | ||
| 342 | bool OpenGLDisplayPlugin::startStandBySession() { | ||
| 343 | if (!activateStandBySession()) { | ||
| 344 | return false; | ||
| 345 | } | ||
| 346 | return Parent::startStandBySession(); | ||
| 347 | } | ||
| 348 | |||
| 349 | void OpenGLDisplayPlugin::endSession() { | ||
| 350 | deactivateSession(); | ||
| 351 | Parent::endSession(); | ||
| 352 | } | ||
| 353 | |||
| 342 | void OpenGLDisplayPlugin::customizeContext() { | 354 | void OpenGLDisplayPlugin::customizeContext() { |
| 343 | auto presentThread = DependencyManager::get<PresentThread>(); | 355 | auto presentThread = DependencyManager::get<PresentThread>(); |
| 344 | Q_ASSERT(thread() == presentThread->thread()); | 356 | Q_ASSERT(thread() == presentThread->thread()); |
| @@ -42,6 +42,8 @@ public: · 41 unmodified lines | |||
| 42 | // between the main thread and the presentation thread | 42 | // between the main thread and the presentation thread |
| 43 | bool activate() override final; | 43 | bool activate() override final; |
| 44 | void deactivate() override final; | 44 | void deactivate() override final; |
| 45 | bool startStandBySession() override final; | ||
| 46 | void endSession() override final; | ||
| 45 | bool eventFilter(QObject* receiver, QEvent* event) override; | 47 | bool eventFilter(QObject* receiver, QEvent* event) override; |
| 46 | bool isDisplayVisible() const override { return true; } | 48 | bool isDisplayVisible() const override { return true; } |
| 47 | 49 | ||
| @@ -99,6 +101,10 @@ protected: · 51 unmodified lines | |||
| 99 | // Returns true on successful activation | 101 | // Returns true on successful activation |
| 100 | virtual bool internalActivate() { return true; } | 102 | virtual bool internalActivate() { return true; } |
| 101 | virtual void internalDeactivate() {} | 103 | virtual void internalDeactivate() {} |
| 104 | |||
| 105 | // Returns true on successful activation of standby session | ||
| 106 | virtual bool activateStandBySession() { return true; } | ||
| 107 | virtual void deactivateSession() {} | ||
| 102 | 108 | ||
| 103 | // Plugin specific functionality to send the composed scene to the output window or device | 109 | // Plugin specific functionality to send the composed scene to the output window or device |
| 104 | virtual void internalPresent(); | 110 | virtual void internalPresent(); |
| @@ -221,6 +221,12 @@ void HmdDisplayPlugin::internalPresent() { · 220 unmodified lines | |||
| 221 | float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2); | 221 | float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2); |
| 222 | float newWidth = sourceSize.x - shiftLeftBy; | 222 | float newWidth = sourceSize.x - shiftLeftBy; |
| 223 | 223 | ||
| 224 | // Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center... | ||
| 225 | static float SCALE_WIDTH = 0.9f; | ||
| 226 | static float SCALE_OFFSET = 2.0f; | ||
| 227 | newWidth *= SCALE_WIDTH; | ||
| 228 | shiftLeftBy *= SCALE_OFFSET; | ||
| 229 | |||
| 224 | const unsigned int RATIO_Y = 9; | 230 | const unsigned int RATIO_Y = 9; |
| 225 | const unsigned int RATIO_X = 16; | 231 | const unsigned int RATIO_X = 16; |
| 226 | glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X }; | 232 | glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X }; |
| @@ -23,7 +23,6 @@ · 22 unmodified lines | |||
| 23 | #include <PerfStat.h> | 23 | #include <PerfStat.h> |
| 24 | #include <SceneScriptingInterface.h> | 24 | #include <SceneScriptingInterface.h> |
| 25 | #include <ScriptEngine.h> | 25 | #include <ScriptEngine.h> |
| 26 | #include <procedural/ProceduralSkybox.h> | ||
| 27 | 26 | ||
| 28 | #include "EntityTreeRenderer.h" | 27 | #include "EntityTreeRenderer.h" |
| 29 | 28 | ||
| @@ -141,7 +140,6 @@ void EntityTreeRenderer::clear() { · 111 unmodified lines | |||
| 141 | 140 | ||
| 142 | // reset the zone to the default (while we load the next scene) | 141 | // reset the zone to the default (while we load the next scene) |
| 143 | _layeredZones.clear(); | 142 | _layeredZones.clear(); |
| 144 | applyZoneAndHasSkybox(nullptr); | ||
| 145 | 143 | ||
| 146 | OctreeRenderer::clear(); | 144 | OctreeRenderer::clear(); |
| 147 | } | 145 | } |
| @@ -194,15 +192,7 @@ void EntityTreeRenderer::update() { · 46 unmodified lines | |||
| 194 | tree->update(); | 192 | tree->update(); |
| 195 | 193 | ||
| 196 | // Handle enter/leave entity logic | 194 | // Handle enter/leave entity logic |
| 197 | bool updated = checkEnterLeaveEntities(); | 195 | checkEnterLeaveEntities(); |
| 198 | |||
| 199 | // If we haven't already updated and previously attempted to load a texture, | ||
| 200 | // check if the texture loaded and apply it | ||
| 201 | if (!updated && | ||
| 202 | ((_pendingAmbientTexture && (!_ambientTexture || _ambientTexture->isLoaded())) || | ||
| 203 | (_pendingSkyboxTexture && (!_skyboxTexture || _skyboxTexture->isLoaded())))) { | ||
| 204 | applySkyboxAndHasAmbient(); | ||
| 205 | } | ||
| 206 | 196 | ||
| 207 | // Even if we're not moving the mouse, if we started clicking on an entity and we have | 197 | // Even if we're not moving the mouse, if we started clicking on an entity and we have |
| 208 | // not yet released the hold then this is still considered a holdingClickOnEntity event | 198 | // not yet released the hold then this is still considered a holdingClickOnEntity event |
| @@ -371,176 +361,6 @@ bool EntityTreeRenderer::applyLayeredZones() { · 162 unmodified lines | |||
| 371 | return true; | 361 | return true; |
| 372 | } | 362 | } |
| 373 | 363 | ||
| 374 | |||
| 375 | bool EntityTreeRenderer::applyZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone) { | ||
| 376 | auto textureCache = DependencyManager::get<TextureCache>(); | ||
| 377 | auto scene = DependencyManager::get<SceneScriptingInterface>(); | ||
| 378 | auto sceneStage = scene->getStage(); | ||
| 379 | auto skyStage = scene->getSkyStage(); | ||
| 380 | auto sceneKeyLight = sceneStage->getKeyLight(); | ||
| 381 | |||
| 382 | // If there is no zone, use the default background | ||
| 383 | if (!zone) { | ||
| 384 | _zoneUserData = QString(); | ||
| 385 | skyStage->getSkybox()->clear(); | ||
| 386 | |||
| 387 | _pendingSkyboxTexture = false; | ||
| 388 | _skyboxTexture.clear(); | ||
| 389 | |||
| 390 | _pendingAmbientTexture = false; | ||
| 391 | _ambientTexture.clear(); | ||
| 392 | |||
| 393 | sceneKeyLight->resetAmbientSphere(); | ||
| 394 | sceneKeyLight->setAmbientMap(nullptr); | ||
| 395 | |||
| 396 | skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT); | ||
| 397 | return false; | ||
| 398 | } | ||
| 399 | |||
| 400 | // Set the keylight | ||
| 401 | sceneKeyLight->setColor(ColorUtils::toVec3(zone->getKeyLightProperties().getColor())); | ||
| 402 | sceneKeyLight->setIntensity(zone->getKeyLightProperties().getIntensity()); | ||
| 403 | sceneKeyLight->setAmbientIntensity(zone->getKeyLightProperties().getAmbientIntensity()); | ||
| 404 | sceneKeyLight->setDirection(zone->getKeyLightProperties().getDirection()); | ||
| 405 | |||
| 406 | // Set the stage | ||
| 407 | bool isSunModelEnabled = zone->getStageProperties().getSunModelEnabled(); | ||
| 408 | sceneStage->setSunModelEnable(isSunModelEnabled); | ||
| 409 | if (isSunModelEnabled) { | ||
| 410 | sceneStage->setLocation(zone->getStageProperties().getLongitude(), | ||
| 411 | zone->getStageProperties().getLatitude(), | ||
| 412 | zone->getStageProperties().getAltitude()); | ||
| 413 | |||
| 414 | auto sceneTime = sceneStage->getTime(); | ||
| 415 | sceneTime->setHour(zone->getStageProperties().calculateHour()); | ||
| 416 | sceneTime->setDay(zone->getStageProperties().calculateDay()); | ||
| 417 | } | ||
| 418 | |||
| 419 | // Set the ambient texture | ||
| 420 | _ambientTextureURL = zone->getKeyLightProperties().getAmbientURL(); | ||
| 421 | if (_ambientTextureURL.isEmpty()) { | ||
| 422 | _pendingAmbientTexture = false; | ||
| 423 | _ambientTexture.clear(); | ||
| 424 | } else { | ||
| 425 | _pendingAmbientTexture = true; | ||
| 426 | } | ||
| 427 | |||
| 428 | // Set the skybox texture | ||
| 429 | return layerZoneAndHasSkybox(zone); | ||
| 430 | } | ||
| 431 | |||
| 432 | bool EntityTreeRenderer::layerZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone) { | ||
| 433 | assert(zone); | ||
| 434 | |||
| 435 | auto textureCache = DependencyManager::get<TextureCache>(); | ||
| 436 | auto scene = DependencyManager::get<SceneScriptingInterface>(); | ||
| 437 | auto skyStage = scene->getSkyStage(); | ||
| 438 | auto skybox = skyStage->getSkybox(); | ||
| 439 | |||
| 440 | bool hasSkybox = false; | ||
| 441 | |||
| 442 | switch (zone->getBackgroundMode()) { | ||
| 443 | case BACKGROUND_MODE_SKYBOX: | ||
| 444 | hasSkybox = true; | ||
| 445 | |||
| 446 | skybox->setColor(zone->getSkyboxProperties().getColorVec3()); | ||
| 447 | |||
| 448 | if (_zoneUserData != zone->getUserData()) { | ||
| 449 | _zoneUserData = zone->getUserData(); | ||
| 450 | std::dynamic_pointer_cast<ProceduralSkybox>(skybox)->parse(_zoneUserData); | ||
| 451 | } | ||
| 452 | |||
| 453 | _skyboxTextureURL = zone->getSkyboxProperties().getURL(); | ||
| 454 | if (_skyboxTextureURL.isEmpty()) { | ||
| 455 | _pendingSkyboxTexture = false; | ||
| 456 | _skyboxTexture.clear(); | ||
| 457 | } else { | ||
| 458 | _pendingSkyboxTexture = true; | ||
| 459 | } | ||
| 460 | |||
| 461 | applySkyboxAndHasAmbient(); | ||
| 462 | skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX); | ||
| 463 | |||
| 464 | break; | ||
| 465 | |||
| 466 | case BACKGROUND_MODE_INHERIT: | ||
| 467 | default: | ||
| 468 | // Clear the skybox to release its textures | ||
| 469 | skybox->clear(); | ||
| 470 | _zoneUserData = QString(); | ||
| 471 | |||
| 472 | _pendingSkyboxTexture = false; | ||
| 473 | _skyboxTexture.clear(); | ||
| 474 | |||
| 475 | // Let the application background through | ||
| 476 | if (applySkyboxAndHasAmbient()) { | ||
| 477 | skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT_TEXTURE); | ||
| 478 | } else { | ||
| 479 | skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE); | ||
| 480 | } | ||
| 481 | |||
| 482 | break; | ||
| 483 | } | ||
| 484 | |||
| 485 | return hasSkybox; | ||
| 486 | } | ||
| 487 | |||
| 488 | bool EntityTreeRenderer::applySkyboxAndHasAmbient() { | ||
| 489 | auto textureCache = DependencyManager::get<TextureCache>(); | ||
| 490 | auto scene = DependencyManager::get<SceneScriptingInterface>(); | ||
| 491 | auto sceneStage = scene->getStage(); | ||
| 492 | auto skyStage = scene->getSkyStage(); | ||
| 493 | auto sceneKeyLight = sceneStage->getKeyLight(); | ||
| 494 | auto skybox = skyStage->getSkybox(); | ||
| 495 | |||
| 496 | bool isAmbientSet = false; | ||
| 497 | if (_pendingAmbientTexture && !_ambientTexture) { | ||
| 498 | _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE); | ||
| 499 | } | ||
| 500 | if (_ambientTexture && _ambientTexture->isLoaded()) { | ||
| 501 | _pendingAmbientTexture = false; | ||
| 502 | |||
| 503 | auto texture = _ambientTexture->getGPUTexture(); | ||
| 504 | if (texture) { | ||
| 505 | isAmbientSet = true; | ||
| 506 | sceneKeyLight->setAmbientSphere(texture->getIrradiance()); | ||
| 507 | sceneKeyLight->setAmbientMap(texture); | ||
| 508 | } else { | ||
| 509 | qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL(); | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | if (_pendingSkyboxTexture && | ||
| 514 | (!_skyboxTexture || (_skyboxTexture->getURL() != _skyboxTextureURL))) { | ||
| 515 | _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE); | ||
| 516 | } | ||
| 517 | if (_skyboxTexture && _skyboxTexture->isLoaded()) { | ||
| 518 | _pendingSkyboxTexture = false; | ||
| 519 | |||
| 520 | auto texture = _skyboxTexture->getGPUTexture(); | ||
| 521 | if (texture) { | ||
| 522 | skybox->setCubemap(texture); | ||
| 523 | if (!isAmbientSet) { | ||
| 524 | sceneKeyLight->setAmbientSphere(texture->getIrradiance()); | ||
| 525 | sceneKeyLight->setAmbientMap(texture); | ||
| 526 | isAmbientSet = true; | ||
| 527 | } | ||
| 528 | } else { | ||
| 529 | qCDebug(entitiesrenderer) << "Failed to load skybox texture:" << _skyboxTexture->getURL(); | ||
| 530 | skybox->setCubemap(nullptr); | ||
| 531 | } | ||
| 532 | } else { | ||
| 533 | skybox->setCubemap(nullptr); | ||
| 534 | } | ||
| 535 | |||
| 536 | if (!isAmbientSet) { | ||
| 537 | sceneKeyLight->resetAmbientSphere(); | ||
| 538 | sceneKeyLight->setAmbientMap(nullptr); | ||
| 539 | } | ||
| 540 | |||
| 541 | return isAmbientSet; | ||
| 542 | } | ||
| 543 | |||
| 544 | const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) { | 364 | const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) { |
| 545 | const FBXGeometry* result = NULL; | 365 | const FBXGeometry* result = NULL; |
| 546 | 366 | ||
| @@ -1186,8 +1006,6 @@ std::pair<EntityTreeRenderer::LayeredZones::iterator, bool> EntityTreeRenderer:: · 639 unmodified lines | |||
| 1186 | 1006 | ||
| 1187 | void EntityTreeRenderer::LayeredZones::apply() { | 1007 | void EntityTreeRenderer::LayeredZones::apply() { |
| 1188 | assert(_entityTreeRenderer); | 1008 | assert(_entityTreeRenderer); |
| 1189 | |||
| 1190 | applyPartial(begin()); | ||
| 1191 | } | 1009 | } |
| 1192 | 1010 | ||
| 1193 | void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) { | 1011 | void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) { |
| @@ -1202,12 +1020,6 @@ void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zo · 8 unmodified lines | |||
| 1202 | } else { | 1020 | } else { |
| 1203 | LayeredZone zoneLayer(zone); | 1021 | LayeredZone zoneLayer(zone); |
| 1204 | 1022 | ||
| 1205 | // should we update? only if this zone is tighter than the current skybox zone | ||
| 1206 | bool shouldUpdate = false; | ||
| 1207 | if (_skyboxLayer == end() || zoneLayer <= *_skyboxLayer) { | ||
| 1208 | shouldUpdate = true; | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | // find this zone's layer, if it exists | 1023 | // find this zone's layer, if it exists |
| 1212 | iterator layer = end(); | 1024 | iterator layer = end(); |
| 1213 | auto it = _map.find(zoneLayer.id); | 1025 | auto it = _map.find(zoneLayer.id); |
| @@ -1227,41 +1039,9 @@ void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zo · 13 unmodified lines | |||
| 1227 | std::tie(layer, std::ignore) = insert(zoneLayer); | 1039 | std::tie(layer, std::ignore) = insert(zoneLayer); |
| 1228 | _map.emplace(layer->id, layer); | 1040 | _map.emplace(layer->id, layer); |
| 1229 | } | 1041 | } |
| 1230 | |||
| 1231 | if (shouldUpdate) { | ||
| 1232 | applyPartial(layer); | ||
| 1233 | } | ||
| 1234 | } | 1042 | } |
| 1235 | } | 1043 | } |
| 1236 | 1044 | ||
| 1237 | void EntityTreeRenderer::LayeredZones::applyPartial(iterator layer) { | ||
| 1238 | bool hasSkybox = false; | ||
| 1239 | _skyboxLayer = end(); | ||
| 1240 | |||
| 1241 | if (layer == end()) { | ||
| 1242 | if (empty()) { | ||
| 1243 | _entityTreeRenderer->applyZoneAndHasSkybox(nullptr); | ||
| 1244 | return; | ||
| 1245 | } else { // a layer was removed - reapply from beginning | ||
| 1246 | layer = begin(); | ||
| 1247 | } | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | if (layer == begin()) { | ||
| 1251 | hasSkybox = _entityTreeRenderer->applyZoneAndHasSkybox(layer->zone); | ||
| 1252 | } else { | ||
| 1253 | hasSkybox = _entityTreeRenderer->layerZoneAndHasSkybox(layer->zone); | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | if (layer != end()) { | ||
| 1257 | while (!hasSkybox && ++layer != end()) { | ||
| 1258 | hasSkybox = _entityTreeRenderer->layerZoneAndHasSkybox(layer->zone); | ||
| 1259 | } | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | _skyboxLayer = layer; | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) { | 1045 | bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) { |
| 1266 | bool result = std::equal(other.begin(), other._skyboxLayer, begin()); | 1046 | bool result = std::equal(other.begin(), other._skyboxLayer, begin()); |
| 1267 | if (result) { | 1047 | if (result) { |
| @@ -147,9 +147,6 @@ private: · 146 unmodified lines | |||
| 147 | void addEntityToScene(EntityItemPointer entity); | 147 | void addEntityToScene(EntityItemPointer entity); |
| 148 | bool findBestZoneAndMaybeContainingEntities(QVector<EntityItemID>* entitiesContainingAvatar = nullptr); | 148 | bool findBestZoneAndMaybeContainingEntities(QVector<EntityItemID>* entitiesContainingAvatar = nullptr); |
| 149 | 149 | ||
| 150 | bool applyZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone); | ||
| 151 | bool layerZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone); | ||
| 152 | bool applySkyboxAndHasAmbient(); | ||
| 153 | bool applyLayeredZones(); | 150 | bool applyLayeredZones(); |
| 154 | 151 | ||
| 155 | void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); | 152 | void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); |
| @@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori · 604 unmodified lines | |||
| 605 | 605 | ||
| 606 | QString extraInfo; | 606 | QString extraInfo; |
| 607 | return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, | 607 | return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, |
| 608 | face, surfaceNormal, extraInfo, precisionPicking); | 608 | face, surfaceNormal, extraInfo, precisionPicking, false); |
| 609 | } | 609 | } |
| 610 | 610 | ||
| 611 | void RenderableModelEntityItem::getCollisionGeometryResource() { | 611 | void RenderableModelEntityItem::getCollisionGeometryResource() { |
| @@ -13,6 +13,7 @@ · 12 unmodified lines | |||
| 13 | #include <DependencyManager.h> | 13 | #include <DependencyManager.h> |
| 14 | #include <PerfStat.h> | 14 | #include <PerfStat.h> |
| 15 | #include <GeometryCache.h> | 15 | #include <GeometryCache.h> |
| 16 | #include <StencilMaskPass.h> | ||
| 16 | #include <AbstractViewStateInterface.h> | 17 | #include <AbstractViewStateInterface.h> |
| 17 | #include "EntitiesRendererLogging.h" | 18 | #include "EntitiesRendererLogging.h" |
| 18 | 19 | ||
| @@ -292,6 +293,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { · 273 unmodified lines | |||
| 292 | state->setDepthTest(true, false, gpu::LESS_EQUAL); | 293 | state->setDepthTest(true, false, gpu::LESS_EQUAL); |
| 293 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, | 294 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, |
| 294 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 295 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| 296 | PrepareStencil::testMask(*state); | ||
| 295 | 297 | ||
| 296 | auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert)); | 298 | auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert)); |
| 297 | auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag)); | 299 | auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag)); |
| @@ -305,6 +307,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { · 7 unmodified lines | |||
| 305 | state->setDepthTest(true, false, gpu::LESS_EQUAL); | 307 | state->setDepthTest(true, false, gpu::LESS_EQUAL); |
| 306 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, | 308 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, |
| 307 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 309 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| 310 | PrepareStencil::testMask(*state); | ||
| 308 | 311 | ||
| 309 | auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert)); | 312 | auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert)); |
| 310 | auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); | 313 | auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); |
| @@ -12,6 +12,7 @@ · 11 unmodified lines | |||
| 12 | #include <glm/gtx/quaternion.hpp> | 12 | #include <glm/gtx/quaternion.hpp> |
| 13 | 13 | ||
| 14 | #include <GeometryCache.h> | 14 | #include <GeometryCache.h> |
| 15 | #include <StencilMaskPass.h> | ||
| 15 | #include <TextureCache.h> | 16 | #include <TextureCache.h> |
| 16 | #include <PathUtils.h> | 17 | #include <PathUtils.h> |
| 17 | #include <PerfStat.h> | 18 | #include <PerfStat.h> |
| @@ -69,6 +70,7 @@ void RenderablePolyLineEntityItem::createPipeline() { · 51 unmodified lines | |||
| 69 | 70 | ||
| 70 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | 71 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); |
| 71 | state->setDepthTest(true, true, gpu::LESS_EQUAL); | 72 | state->setDepthTest(true, true, gpu::LESS_EQUAL); |
| 73 | PrepareStencil::testMask(*state); | ||
| 72 | state->setBlendFunction(true, | 74 | state->setBlendFunction(true, |
| 73 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, | 75 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, |
| 74 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 76 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| @@ -46,6 +46,9 @@ · 45 unmodified lines | |||
| 46 | #endif | 46 | #endif |
| 47 | 47 | ||
| 48 | #include "model/Geometry.h" | 48 | #include "model/Geometry.h" |
| 49 | |||
| 50 | #include "StencilMaskPass.h" | ||
| 51 | |||
| 49 | #include "EntityTreeRenderer.h" | 52 | #include "EntityTreeRenderer.h" |
| 50 | #include "polyvox_vert.h" | 53 | #include "polyvox_vert.h" |
| 51 | #include "polyvox_frag.h" | 54 | #include "polyvox_frag.h" |
| @@ -743,6 +746,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { · 691 unmodified lines | |||
| 743 | auto state = std::make_shared<gpu::State>(); | 746 | auto state = std::make_shared<gpu::State>(); |
| 744 | state->setCullMode(gpu::State::CULL_BACK); | 747 | state->setCullMode(gpu::State::CULL_BACK); |
| 745 | state->setDepthTest(true, true, gpu::LESS_EQUAL); | 748 | state->setDepthTest(true, true, gpu::LESS_EQUAL); |
| 749 | PrepareStencil::testMaskDrawShape(*state); | ||
| 746 | 750 | ||
| 747 | _pipeline = gpu::Pipeline::create(program, state); | 751 | _pipeline = gpu::Pipeline::create(program, state); |
| 748 | 752 | ||
| @@ -750,6 +754,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { · 1 unmodified line | |||
| 750 | wireframeState->setCullMode(gpu::State::CULL_BACK); | 754 | wireframeState->setCullMode(gpu::State::CULL_BACK); |
| 751 | wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL); | 755 | wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL); |
| 752 | wireframeState->setFillMode(gpu::State::FILL_LINE); | 756 | wireframeState->setFillMode(gpu::State::FILL_LINE); |
| 757 | PrepareStencil::testMaskDrawShape(*wireframeState); | ||
| 753 | 758 | ||
| 754 | _wireframePipeline = gpu::Pipeline::create(program, wireframeState); | 759 | _wireframePipeline = gpu::Pipeline::create(program, wireframeState); |
| 755 | } | 760 | } |
| @@ -13,6 +13,7 @@ · 12 unmodified lines | |||
| 13 | #include <gpu/Batch.h> | 13 | #include <gpu/Batch.h> |
| 14 | 14 | ||
| 15 | #include <DependencyManager.h> | 15 | #include <DependencyManager.h> |
| 16 | #include <StencilMaskPass.h> | ||
| 16 | #include <GeometryCache.h> | 17 | #include <GeometryCache.h> |
| 17 | #include <PerfStat.h> | 18 | #include <PerfStat.h> |
| 18 | 19 | ||
| @@ -93,6 +94,7 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { · 74 unmodified lines | |||
| 93 | _procedural->_fragmentSource = simple_frag; | 94 | _procedural->_fragmentSource = simple_frag; |
| 94 | _procedural->_opaqueState->setCullMode(gpu::State::CULL_NONE); | 95 | _procedural->_opaqueState->setCullMode(gpu::State::CULL_NONE); |
| 95 | _procedural->_opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); | 96 | _procedural->_opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); |
| 97 | PrepareStencil::testMaskDrawShape(*_procedural->_opaqueState); | ||
| 96 | _procedural->_opaqueState->setBlendFunction(false, | 98 | _procedural->_opaqueState->setBlendFunction(false, |
| 97 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, | 99 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, |
| 98 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 100 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| @@ -127,7 +127,7 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer> · 126 unmodified lines | |||
| 127 | 127 | ||
| 128 | _webSurface->resume(); | 128 | _webSurface->resume(); |
| 129 | _webSurface->getRootItem()->setProperty("url", _sourceUrl); | 129 | _webSurface->getRootItem()->setProperty("url", _sourceUrl); |
| 130 | _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); | 130 | _webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant()); |
| 131 | // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. | 131 | // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. |
| 132 | 132 | ||
| 133 | // forward web events to EntityScriptingInterface | 133 | // forward web events to EntityScriptingInterface |
| @@ -271,7 +271,7 @@ void RenderableWebEntityItem::loadSourceURL() { · 137 unmodified lines | |||
| 271 | }); | 271 | }); |
| 272 | 272 | ||
| 273 | _webSurface->getRootItem()->setProperty("url", _sourceUrl); | 273 | _webSurface->getRootItem()->setProperty("url", _sourceUrl); |
| 274 | _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); | 274 | _webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant()); |
| 275 | 275 | ||
| 276 | } else { | 276 | } else { |
| 277 | _contentType = qmlContent; | 277 | _contentType = qmlContent; |
| @@ -284,7 +284,7 @@ void RenderableWebEntityItem::loadSourceURL() { · 6 unmodified lines | |||
| 284 | _webSurface->getRootItem(), _webSurface.data()); | 284 | _webSurface->getRootItem(), _webSurface.data()); |
| 285 | } | 285 | } |
| 286 | } | 286 | } |
| 287 | _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); | 287 | _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); |
| 288 | } | 288 | } |
| 289 | 289 | ||
| 290 | 290 | ||
| @@ -420,7 +420,7 @@ void RenderableWebEntityItem::update(const quint64& now) { · 129 unmodified lines | |||
| 420 | 420 | ||
| 421 | if (_webSurface) { | 421 | if (_webSurface) { |
| 422 | // update globalPosition | 422 | // update globalPosition |
| 423 | _webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); | 423 | _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); |
| 424 | } | 424 | } |
| 425 | 425 | ||
| 426 | auto interval = now - _lastRenderTime; | 426 | auto interval = now - _lastRenderTime; |
| @@ -13,14 +13,90 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | #include <gpu/Batch.h> | 14 | #include <gpu/Batch.h> |
| 15 | 15 | ||
| 16 | #include <model/Stage.h> | ||
| 17 | |||
| 16 | #include <AbstractViewStateInterface.h> | 18 | #include <AbstractViewStateInterface.h> |
| 17 | #include <DependencyManager.h> | 19 | #include <DependencyManager.h> |
| 18 | #include <GeometryCache.h> | 20 | #include <GeometryCache.h> |
| 19 | #include <PerfStat.h> | 21 | #include <PerfStat.h> |
| 22 | #include <procedural/ProceduralSkybox.h> | ||
| 20 | 23 | ||
| 21 | #include "EntityTreeRenderer.h" | 24 | #include "EntityTreeRenderer.h" |
| 22 | #include "RenderableEntityItem.h" | 25 | #include "RenderableEntityItem.h" |
| 23 | 26 | ||
| 27 | #include <LightPayload.h> | ||
| 28 | #include "DeferredLightingEffect.h" | ||
| 29 | |||
| 30 | |||
| 31 | class RenderableZoneEntityItemMeta { | ||
| 32 | public: | ||
| 33 | RenderableZoneEntityItemMeta(EntityItemPointer entity); | ||
| 34 | ~RenderableZoneEntityItemMeta(); | ||
| 35 | |||
| 36 | typedef render::Payload<RenderableZoneEntityItemMeta> Payload; | ||
| 37 | typedef Payload::DataPointer Pointer; | ||
| 38 | |||
| 39 | EntityItemPointer entity; | ||
| 40 | |||
| 41 | void render(RenderArgs* args); | ||
| 42 | |||
| 43 | void setVisible(bool visible) { _isVisible = visible; } | ||
| 44 | bool isVisible() const { return _isVisible; } | ||
| 45 | |||
| 46 | render::Item::Bound& editBound() { _needUpdate = true; return _bound; } | ||
| 47 | |||
| 48 | model::LightPointer editSunLight() { _needSunUpdate = true; return _sunLight; } | ||
| 49 | model::LightPointer editAmbientLight() { _needAmbientUpdate = true; return _ambientLight; } | ||
| 50 | model::SunSkyStagePointer editBackground() { _needBackgroundUpdate = true; return _background; } | ||
| 51 | model::SkyboxPointer editSkybox() { return editBackground()->getSkybox(); } | ||
| 52 | |||
| 53 | void setAmbientURL(const QString& ambientUrl); | ||
| 54 | |||
| 55 | void setSkyboxURL(const QString& skyboxUrl); | ||
| 56 | |||
| 57 | void setBackgroundMode(BackgroundMode mode); | ||
| 58 | void setSkyboxColor(const glm::vec3& color); | ||
| 59 | void setProceduralUserData(QString userData); | ||
| 60 | |||
| 61 | protected: | ||
| 62 | render::Item::Bound _bound; | ||
| 63 | |||
| 64 | model::LightPointer _sunLight; | ||
| 65 | model::LightPointer _ambientLight; | ||
| 66 | model::SunSkyStagePointer _background; | ||
| 67 | BackgroundMode _backgroundMode { BACKGROUND_MODE_INHERIT }; | ||
| 68 | |||
| 69 | LightStagePointer _stage; | ||
| 70 | LightStage::Index _sunIndex { LightStage::INVALID_INDEX }; | ||
| 71 | LightStage::Index _ambientIndex { LightStage::INVALID_INDEX }; | ||
| 72 | |||
| 73 | BackgroundStagePointer _backgroundStage; | ||
| 74 | BackgroundStage::Index _backgroundIndex { BackgroundStage::INVALID_INDEX }; | ||
| 75 | |||
| 76 | bool _needUpdate { true }; | ||
| 77 | bool _needSunUpdate { true }; | ||
| 78 | bool _needAmbientUpdate { true }; | ||
| 79 | bool _needBackgroundUpdate { true }; | ||
| 80 | bool _isVisible { true }; | ||
| 81 | |||
| 82 | |||
| 83 | void updateAmbientMap(); | ||
| 84 | void updateSkyboxMap(); | ||
| 85 | |||
| 86 | // More attributes used for rendering: | ||
| 87 | QString _ambientTextureURL; | ||
| 88 | NetworkTexturePointer _ambientTexture; | ||
| 89 | bool _pendingAmbientTexture { false }; | ||
| 90 | bool _validAmbientTexture { false }; | ||
| 91 | |||
| 92 | QString _skyboxTextureURL; | ||
| 93 | NetworkTexturePointer _skyboxTexture; | ||
| 94 | bool _pendingSkyboxTexture { false }; | ||
| 95 | bool _validSkyboxTexture { false }; | ||
| 96 | |||
| 97 | QString _proceduralUserData; | ||
| 98 | }; | ||
| 99 | |||
| 24 | // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 | 100 | // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 |
| 25 | // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. | 101 | // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. |
| 26 | static const float SPHERE_ENTITY_SCALE = 0.5f; | 102 | static const float SPHERE_ENTITY_SCALE = 0.5f; |
| @@ -67,8 +143,15 @@ bool RenderableZoneEntityItem::setProperties(const EntityItemProperties& propert · 40 unmodified lines | |||
| 67 | 143 | ||
| 68 | void RenderableZoneEntityItem::somethingChangedNotification() { | 144 | void RenderableZoneEntityItem::somethingChangedNotification() { |
| 69 | DependencyManager::get<EntityTreeRenderer>()->updateZone(_id); | 145 | DependencyManager::get<EntityTreeRenderer>()->updateZone(_id); |
| 146 | |||
| 147 | // If graphics elements are changed, we need to update the render items | ||
| 148 | notifyChangedRenderItem(); | ||
| 149 | |||
| 150 | // Poopagate back to parent | ||
| 151 | ZoneEntityItem::somethingChangedNotification(); | ||
| 70 | } | 152 | } |
| 71 | 153 | ||
| 154 | |||
| 72 | int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, | 155 | int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, |
| 73 | ReadBitstreamToTreeParams& args, | 156 | ReadBitstreamToTreeParams& args, |
| 74 | EntityPropertyFlags& propertyFlags, bool overwriteLocalData, | 157 | EntityPropertyFlags& propertyFlags, bool overwriteLocalData, |
| @@ -174,18 +257,157 @@ bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { · 99 unmodified lines | |||
| 174 | return false; | 257 | return false; |
| 175 | } | 258 | } |
| 176 | 259 | ||
| 177 | class RenderableZoneEntityItemMeta { | 260 | bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene, |
| 178 | public: | 261 | render::Transaction& transaction) { |
| 179 | RenderableZoneEntityItemMeta(EntityItemPointer entity) : entity(entity){ } | 262 | _myMetaItem = scene->allocateID(); |
| 180 | typedef render::Payload<RenderableZoneEntityItemMeta> Payload; | 263 | |
| 181 | typedef Payload::DataPointer Pointer; | 264 | auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self); |
| 265 | auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData); | ||
| 266 | updateKeyZoneItemFromEntity((*renderData)); | ||
| 267 | updateKeySunFromEntity((*renderData)); | ||
| 268 | updateKeyAmbientFromEntity((*renderData)); | ||
| 269 | updateKeyBackgroundFromEntity((*renderData)); | ||
| 270 | |||
| 271 | render::Item::Status::Getters statusGetters; | ||
| 272 | makeEntityItemStatusGetters(getThisPointer(), statusGetters); | ||
| 273 | renderPayload->addStatusGetters(statusGetters); | ||
| 274 | |||
| 275 | transaction.resetItem(_myMetaItem, renderPayload); | ||
| 276 | |||
| 277 | return true; | ||
| 278 | } | ||
| 279 | |||
| 280 | void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, const render::ScenePointer& scene, | ||
| 281 | render::Transaction& transaction) { | ||
| 282 | transaction.removeItem(_myMetaItem); | ||
| 283 | render::Item::clearID(_myMetaItem); | ||
| 284 | if (_model) { | ||
| 285 | _model->removeFromScene(scene, transaction); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | void RenderableZoneEntityItem::notifyBoundChanged() { | ||
| 290 | notifyChangedRenderItem(); | ||
| 291 | } | ||
| 292 | |||
| 293 | void RenderableZoneEntityItem::updateKeySunFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) { | ||
| 294 | auto sunLight = keyZonePayload.editSunLight(); | ||
| 295 | sunLight->setType(model::Light::SUN); | ||
| 296 | |||
| 297 | sunLight->setPosition(this->getPosition()); | ||
| 298 | sunLight->setOrientation(this->getRotation()); | ||
| 299 | |||
| 300 | // Set the keylight | ||
| 301 | sunLight->setColor(ColorUtils::toVec3(this->getKeyLightProperties().getColor())); | ||
| 302 | sunLight->setIntensity(this->getKeyLightProperties().getIntensity()); | ||
| 303 | sunLight->setDirection(this->getKeyLightProperties().getDirection()); | ||
| 304 | } | ||
| 305 | |||
| 306 | void RenderableZoneEntityItem::updateKeyAmbientFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) { | ||
| 307 | auto ambientLight = keyZonePayload.editAmbientLight(); | ||
| 308 | ambientLight->setType(model::Light::AMBIENT); | ||
| 309 | |||
| 310 | ambientLight->setPosition(this->getPosition()); | ||
| 311 | ambientLight->setOrientation(this->getRotation()); | ||
| 312 | |||
| 313 | |||
| 314 | // Set the keylight | ||
| 315 | ambientLight->setAmbientIntensity(this->getKeyLightProperties().getAmbientIntensity()); | ||
| 316 | |||
| 317 | if (this->getKeyLightProperties().getAmbientURL().isEmpty()) { | ||
| 318 | keyZonePayload.setAmbientURL(this->getSkyboxProperties().getURL()); | ||
| 319 | } else { | ||
| 320 | keyZonePayload.setAmbientURL(this->getKeyLightProperties().getAmbientURL()); | ||
| 321 | } | ||
| 322 | |||
| 323 | } | ||
| 324 | |||
| 325 | void RenderableZoneEntityItem::updateKeyBackgroundFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) { | ||
| 326 | auto background = keyZonePayload.editBackground(); | ||
| 327 | |||
| 328 | keyZonePayload.setBackgroundMode(this->getBackgroundMode()); | ||
| 329 | keyZonePayload.setSkyboxColor(this->getSkyboxProperties().getColorVec3()); | ||
| 330 | keyZonePayload.setProceduralUserData(this->getUserData()); | ||
| 331 | keyZonePayload.setSkyboxURL(this->getSkyboxProperties().getURL()); | ||
| 332 | } | ||
| 333 | |||
| 334 | |||
| 335 | void RenderableZoneEntityItem::updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) { | ||
| 336 | |||
| 337 | keyZonePayload.setVisible(this->getVisible()); | ||
| 338 | |||
| 339 | bool success; | ||
| 340 | keyZonePayload.editBound() = this->getAABox(success); | ||
| 341 | if (!success) { | ||
| 342 | keyZonePayload.editBound() = render::Item::Bound(); | ||
| 343 | } | ||
| 344 | |||
| 345 | /* TODO: Implement the sun model behavior / Keep this code here for reference, this is how we | ||
| 346 | { | ||
| 347 | // Set the stage | ||
| 348 | bool isSunModelEnabled = this->getStageProperties().getSunModelEnabled(); | ||
| 349 | sceneStage->setSunModelEnable(isSunModelEnabled); | ||
| 350 | if (isSunModelEnabled) { | ||
| 351 | sceneStage->setLocation(this->getStageProperties().getLongitude(), | ||
| 352 | this->getStageProperties().getLatitude(), | ||
| 353 | this->getStageProperties().getAltitude()); | ||
| 354 | |||
| 355 | auto sceneTime = sceneStage->getTime(); | ||
| 356 | sceneTime->setHour(this->getStageProperties().calculateHour()); | ||
| 357 | sceneTime->setDay(this->getStageProperties().calculateDay()); | ||
| 358 | } | ||
| 359 | }*/ | ||
| 360 | } | ||
| 361 | |||
| 362 | |||
| 363 | void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transaction& transaction) { | ||
| 364 | if (!render::Item::isValidID(_myMetaItem)) { | ||
| 365 | return; | ||
| 366 | } | ||
| 367 | |||
| 368 | bool sunChanged = _keyLightPropertiesChanged; | ||
| 369 | bool backgroundChanged = _backgroundPropertiesChanged; | ||
| 370 | bool skyboxChanged = _skyboxPropertiesChanged; | ||
| 371 | |||
| 372 | transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [=](RenderableZoneEntityItemMeta& data) { | ||
| 373 | |||
| 374 | updateKeyZoneItemFromEntity(data); | ||
| 375 | |||
| 376 | if (sunChanged) { | ||
| 377 | updateKeySunFromEntity(data); | ||
| 378 | } | ||
| 379 | |||
| 380 | if (sunChanged || skyboxChanged) { | ||
| 381 | updateKeyAmbientFromEntity(data); | ||
| 382 | } | ||
| 383 | if (backgroundChanged || skyboxChanged) { | ||
| 384 | updateKeyBackgroundFromEntity(data); | ||
| 385 | } | ||
| 386 | }); | ||
| 387 | } | ||
| 388 | |||
| 389 | void RenderableZoneEntityItem::notifyChangedRenderItem() { | ||
| 390 | if (!render::Item::isValidID(_myMetaItem)) { | ||
| 391 | return; | ||
| 392 | } | ||
| 393 | |||
| 394 | render::Transaction transaction; | ||
| 395 | render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); | ||
| 396 | sceneUpdateRenderItemFromEntity(transaction); | ||
| 397 | scene->enqueueTransaction(transaction); | ||
| 398 | } | ||
| 399 | |||
| 400 | |||
| 401 | |||
| 402 | |||
| 403 | |||
| 404 | |||
| 405 | |||
| 182 | 406 | ||
| 183 | EntityItemPointer entity; | ||
| 184 | }; | ||
| 185 | 407 | ||
| 186 | namespace render { | 408 | namespace render { |
| 187 | template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) { | 409 | template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) { |
| 188 | return ItemKey::Builder::opaqueShape(); | 410 | return ItemKey::Builder().withTypeMeta().build(); |
| 189 | } | 411 | } |
| 190 | 412 | ||
| 191 | template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) { | 413 | template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) { |
| @@ -200,51 +422,199 @@ namespace render { · 8 unmodified lines | |||
| 200 | return render::Item::Bound(); | 422 | return render::Item::Bound(); |
| 201 | } | 423 | } |
| 202 | template <> void payloadRender(const RenderableZoneEntityItemMeta::Pointer& payload, RenderArgs* args) { | 424 | template <> void payloadRender(const RenderableZoneEntityItemMeta::Pointer& payload, RenderArgs* args) { |
| 203 | if (args) { | 425 | payload->render(args); |
| 204 | if (payload && payload->entity) { | ||
| 205 | payload->entity->render(args); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | 426 | } |
| 209 | } | 427 | } |
| 210 | 428 | ||
| 211 | bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene, | 429 | RenderableZoneEntityItemMeta::RenderableZoneEntityItemMeta(EntityItemPointer entity) : |
| 212 | render::Transaction& transaction) { | 430 | entity(entity), |
| 213 | _myMetaItem = scene->allocateID(); | 431 | _sunLight(std::make_shared<model::Light>()), |
| 432 | _ambientLight(std::make_shared<model::Light>()), | ||
| 433 | _background(std::make_shared<model::SunSkyStage>()) | ||
| 434 | { | ||
| 435 | _background->setSkybox(std::make_shared<ProceduralSkybox>()); | ||
| 436 | } | ||
| 214 | 437 | ||
| 215 | auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self); | ||
| 216 | auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData); | ||
| 217 | 438 | ||
| 218 | render::Item::Status::Getters statusGetters; | 439 | RenderableZoneEntityItemMeta::~RenderableZoneEntityItemMeta() { |
| 219 | makeEntityItemStatusGetters(getThisPointer(), statusGetters); | 440 | if (_stage) { |
| 220 | renderPayload->addStatusGetters(statusGetters); | 441 | if (!LightStage::isIndexInvalid(_sunIndex)) { |
| 442 | _stage->removeLight(_sunIndex); | ||
| 443 | } | ||
| 444 | if (!LightStage::isIndexInvalid(_ambientIndex)) { | ||
| 445 | _stage->removeLight(_ambientIndex); | ||
| 446 | |||
| 447 | } | ||
| 448 | } | ||
| 221 | 449 | ||
| 222 | transaction.resetItem(_myMetaItem, renderPayload); | 450 | if (_backgroundStage) { |
| 223 | return true; | 451 | if (!BackgroundStage::isIndexInvalid(_backgroundIndex)) { |
| 452 | _backgroundStage->removeBackground(_backgroundIndex); | ||
| 453 | } | ||
| 454 | } | ||
| 224 | } | 455 | } |
| 225 | 456 | ||
| 226 | void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, const render::ScenePointer& scene, | 457 | void RenderableZoneEntityItemMeta::setAmbientURL(const QString& ambientUrl) { |
| 227 | render::Transaction& transaction) { | 458 | // nothing change if nothing change |
| 228 | transaction.removeItem(_myMetaItem); | 459 | if (_ambientTextureURL == ambientUrl) { |
| 229 | render::Item::clearID(_myMetaItem); | 460 | return; |
| 230 | if (_model) { | 461 | } |
| 231 | _model->removeFromScene(scene, transaction); | 462 | _ambientTextureURL = ambientUrl; |
| 463 | |||
| 464 | if (_ambientTextureURL.isEmpty()) { | ||
| 465 | _validAmbientTexture = false; | ||
| 466 | _pendingAmbientTexture = false; | ||
| 467 | _ambientTexture.clear(); | ||
| 468 | |||
| 469 | _ambientLight->setAmbientMap(nullptr); | ||
| 470 | _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY); | ||
| 471 | } else { | ||
| 472 | _pendingAmbientTexture = true; | ||
| 473 | auto textureCache = DependencyManager::get<TextureCache>(); | ||
| 474 | _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE); | ||
| 475 | |||
| 476 | // keep whatever is assigned on the ambient map/sphere until texture is loaded | ||
| 232 | } | 477 | } |
| 233 | } | 478 | } |
| 234 | 479 | ||
| 480 | void RenderableZoneEntityItemMeta::updateAmbientMap() { | ||
| 481 | if (_pendingAmbientTexture) { | ||
| 482 | if (_ambientTexture && _ambientTexture->isLoaded()) { | ||
| 483 | _pendingAmbientTexture = false; | ||
| 235 | 484 | ||
| 236 | void RenderableZoneEntityItem::notifyBoundChanged() { | 485 | auto texture = _ambientTexture->getGPUTexture(); |
| 237 | if (!render::Item::isValidID(_myMetaItem)) { | 486 | if (texture) { |
| 487 | if (texture->getIrradiance()) { | ||
| 488 | _ambientLight->setAmbientSphere(*texture->getIrradiance()); | ||
| 489 | } else { | ||
| 490 | _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY); | ||
| 491 | } | ||
| 492 | editAmbientLight()->setAmbientMap(texture); | ||
| 493 | _validAmbientTexture = true; | ||
| 494 | } else { | ||
| 495 | qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL(); | ||
| 496 | } | ||
| 497 | } | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | |||
| 502 | void RenderableZoneEntityItemMeta::setSkyboxURL(const QString& skyboxUrl) { | ||
| 503 | // nothing change if nothing change | ||
| 504 | if (_skyboxTextureURL == skyboxUrl) { | ||
| 238 | return; | 505 | return; |
| 239 | } | 506 | } |
| 240 | render::Transaction transaction; | 507 | _skyboxTextureURL = skyboxUrl; |
| 241 | render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); | ||
| 242 | if (scene) { | ||
| 243 | transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [](RenderableZoneEntityItemMeta& data) { | ||
| 244 | }); | ||
| 245 | 508 | ||
| 246 | scene->enqueueTransaction(transaction); | 509 | if (_skyboxTextureURL.isEmpty()) { |
| 510 | _validSkyboxTexture = false; | ||
| 511 | _pendingSkyboxTexture = false; | ||
| 512 | _skyboxTexture.clear(); | ||
| 513 | |||
| 514 | editSkybox()->setCubemap(nullptr); | ||
| 247 | } else { | 515 | } else { |
| 248 | qCWarning(entitiesrenderer) << "RenderableZoneEntityItem::notifyBoundChanged(), Unexpected null scene, possibly during application shutdown"; | 516 | _pendingSkyboxTexture = true; |
| 517 | auto textureCache = DependencyManager::get<TextureCache>(); | ||
| 518 | _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE); | ||
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | void RenderableZoneEntityItemMeta::updateSkyboxMap() { | ||
| 523 | if (_pendingSkyboxTexture) { | ||
| 524 | if (_skyboxTexture && _skyboxTexture->isLoaded()) { | ||
| 525 | _pendingSkyboxTexture = false; | ||
| 526 | |||
| 527 | auto texture = _skyboxTexture->getGPUTexture(); | ||
| 528 | if (texture) { | ||
| 529 | editSkybox()->setCubemap(texture); | ||
| 530 | _validSkyboxTexture = true; | ||
| 531 | } else { | ||
| 532 | qCDebug(entitiesrenderer) << "Failed to load Skybox texture:" << _skyboxTexture->getURL(); | ||
| 533 | } | ||
| 534 | } | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | void RenderableZoneEntityItemMeta::setBackgroundMode(BackgroundMode mode) { | ||
| 539 | _backgroundMode = mode; | ||
| 540 | } | ||
| 541 | |||
| 542 | void RenderableZoneEntityItemMeta::setSkyboxColor(const glm::vec3& color) { | ||
| 543 | editSkybox()->setColor(color); | ||
| 544 | } | ||
| 545 | |||
| 546 | void RenderableZoneEntityItemMeta::setProceduralUserData(QString userData) { | ||
| 547 | if (_proceduralUserData != userData) { | ||
| 548 | _proceduralUserData = userData; | ||
| 549 | std::dynamic_pointer_cast<ProceduralSkybox>(editSkybox())->parse(_proceduralUserData); | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | |||
| 554 | |||
| 555 | void RenderableZoneEntityItemMeta::render(RenderArgs* args) { | ||
| 556 | if (!_stage) { | ||
| 557 | _stage = DependencyManager::get<DeferredLightingEffect>()->getLightStage(); | ||
| 558 | } | ||
| 559 | |||
| 560 | if (!_backgroundStage) { | ||
| 561 | _backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage(); | ||
| 562 | } | ||
| 563 | |||
| 564 | { // Sun | ||
| 565 | // Need an update ? | ||
| 566 | if (_needSunUpdate) { | ||
| 567 | // Do we need to allocate the light in the stage ? | ||
| 568 | if (LightStage::isIndexInvalid(_sunIndex)) { | ||
| 569 | _sunIndex = _stage->addLight(_sunLight); | ||
| 570 | } else { | ||
| 571 | _stage->updateLightArrayBuffer(_sunIndex); | ||
| 572 | } | ||
| 573 | _needSunUpdate = false; | ||
| 574 | } | ||
| 575 | } | ||
| 576 | |||
| 577 | { // Ambient | ||
| 578 | updateAmbientMap(); | ||
| 579 | |||
| 580 | // Need an update ? | ||
| 581 | if (_needAmbientUpdate) { | ||
| 582 | // Do we need to allocate the light in the stage ? | ||
| 583 | if (LightStage::isIndexInvalid(_ambientIndex)) { | ||
| 584 | _ambientIndex = _stage->addLight(_ambientLight); | ||
| 585 | } else { | ||
| 586 | _stage->updateLightArrayBuffer(_ambientIndex); | ||
| 587 | } | ||
| 588 | _needAmbientUpdate = false; | ||
| 589 | } | ||
| 590 | } | ||
| 591 | |||
| 592 | { // Skybox | ||
| 593 | updateSkyboxMap(); | ||
| 594 | |||
| 595 | if (_needBackgroundUpdate) { | ||
| 596 | if (BackgroundStage::isIndexInvalid(_backgroundIndex)) { | ||
| 597 | _backgroundIndex = _backgroundStage->addBackground(_background); | ||
| 598 | } else { | ||
| 599 | |||
| 600 | } | ||
| 601 | _needBackgroundUpdate = false; | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | if (isVisible()) { | ||
| 606 | // FInally, push the light visible in the frame | ||
| 607 | // THe directional key light for sure | ||
| 608 | _stage->_currentFrame.pushSunLight(_sunIndex); | ||
| 609 | |||
| 610 | // The ambient light only if it has a valid texture to render with | ||
| 611 | if (_validAmbientTexture || _validSkyboxTexture) { | ||
| 612 | _stage->_currentFrame.pushAmbientLight(_ambientIndex); | ||
| 613 | } | ||
| 614 | |||
| 615 | // The background only if the mode is not inherit | ||
| 616 | if (_backgroundMode != BACKGROUND_MODE_INHERIT) { | ||
| 617 | _backgroundStage->_currentFrame.pushBackground(_backgroundIndex); | ||
| 618 | } | ||
| 249 | } | 619 | } |
| 250 | } | 620 | } |
| @@ -16,6 +16,9 @@ · 15 unmodified lines | |||
| 16 | #include <ZoneEntityItem.h> | 16 | #include <ZoneEntityItem.h> |
| 17 | 17 | ||
| 18 | class NetworkGeometry; | 18 | class NetworkGeometry; |
| 19 | class KeyLightPayload; | ||
| 20 | |||
| 21 | class RenderableZoneEntityItemMeta; | ||
| 19 | 22 | ||
| 20 | class RenderableZoneEntityItem : public ZoneEntityItem { | 23 | class RenderableZoneEntityItem : public ZoneEntityItem { |
| 21 | public: | 24 | public: |
| @@ -42,7 +45,7 @@ public: · 20 unmodified lines | |||
| 42 | virtual void removeFromScene(EntityItemPointer self, const render::ScenePointer& scene, render::Transaction& transaction) override; | 45 | virtual void removeFromScene(EntityItemPointer self, const render::ScenePointer& scene, render::Transaction& transaction) override; |
| 43 | 46 | ||
| 44 | render::ItemID getRenderItemID() const { return _myMetaItem; } | 47 | render::ItemID getRenderItemID() const { return _myMetaItem; } |
| 45 | 48 | ||
| 46 | private: | 49 | private: |
| 47 | virtual void locationChanged(bool tellPhysics = true) override { EntityItem::locationChanged(tellPhysics); notifyBoundChanged(); } | 50 | virtual void locationChanged(bool tellPhysics = true) override { EntityItem::locationChanged(tellPhysics); notifyBoundChanged(); } |
| 48 | virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); } | 51 | virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); } |
| @@ -52,10 +55,18 @@ private: · 3 unmodified lines | |||
| 52 | 55 | ||
| 53 | template<typename Lambda> | 56 | template<typename Lambda> |
| 54 | void changeProperties(Lambda functor); | 57 | void changeProperties(Lambda functor); |
| 55 | 58 | ||
| 59 | void notifyChangedRenderItem(); | ||
| 60 | void sceneUpdateRenderItemFromEntity(render::Transaction& transaction); | ||
| 61 | void updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload); | ||
| 62 | |||
| 63 | void updateKeySunFromEntity(RenderableZoneEntityItemMeta& keyZonePayload); | ||
| 64 | void updateKeyAmbientFromEntity(RenderableZoneEntityItemMeta& keyZonePayload); | ||
| 65 | void updateKeyBackgroundFromEntity(RenderableZoneEntityItemMeta& keyZonePayload); | ||
| 66 | |||
| 56 | ModelPointer _model; | 67 | ModelPointer _model; |
| 57 | bool _needsInitialSimulation; | 68 | bool _needsInitialSimulation; |
| 58 | 69 | ||
| 59 | render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID }; | 70 | render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID }; |
| 60 | }; | 71 | }; |
| 61 | 72 | ||
| @@ -681,7 +681,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef · 680 unmodified lines | |||
| 681 | // and pretend that we own it (we assume we'll recover it soon) | 681 | // and pretend that we own it (we assume we'll recover it soon) |
| 682 | 682 | ||
| 683 | // However, for now, when the server uses a newer time than what we sent, listen to what we're told. | 683 | // However, for now, when the server uses a newer time than what we sent, listen to what we're told. |
| 684 | if (overwriteLocalData) weOwnSimulation = false; | 684 | if (overwriteLocalData) { |
| 685 | weOwnSimulation = false; | ||
| 686 | } | ||
| 685 | } else if (_simulationOwner.set(newSimOwner)) { | 687 | } else if (_simulationOwner.set(newSimOwner)) { |
| 686 | markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); | 688 | markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); |
| 687 | somethingChanged = true; | 689 | somethingChanged = true; |
| @@ -1293,27 +1295,15 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c · 605 unmodified lines | |||
| 1293 | properties._accelerationChanged = true; | 1295 | properties._accelerationChanged = true; |
| 1294 | } | 1296 | } |
| 1295 | 1297 | ||
| 1296 | void EntityItem::pokeSimulationOwnership() { | 1298 | void EntityItem::flagForOwnershipBid(uint8_t priority) { |
| 1297 | markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE); | 1299 | markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); |
| 1298 | auto nodeList = DependencyManager::get<NodeList>(); | ||
| 1299 | if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { | ||
| 1300 | // we already own it | ||
| 1301 | _simulationOwner.promotePriority(SCRIPT_POKE_SIMULATION_PRIORITY); | ||
| 1302 | } else { | ||
| 1303 | // we don't own it yet | ||
| 1304 | _simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_PRIORITY, usecTimestampNow()); | ||
| 1305 | } | ||
| 1306 | } | ||
| 1307 | |||
| 1308 | void EntityItem::grabSimulationOwnership() { | ||
| 1309 | markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB); | ||
| 1310 | auto nodeList = DependencyManager::get<NodeList>(); | 1300 | auto nodeList = DependencyManager::get<NodeList>(); |
| 1311 | if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { | 1301 | if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { |
| 1312 | // we already own it | 1302 | // we already own it |
| 1313 | _simulationOwner.promotePriority(SCRIPT_GRAB_SIMULATION_PRIORITY); | 1303 | _simulationOwner.promotePriority(priority); |
| 1314 | } else { | 1304 | } else { |
| 1315 | // we don't own it yet | 1305 | // we don't own it yet |
| 1316 | _simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow()); | 1306 | _simulationOwner.setPendingPriority(priority, usecTimestampNow()); |
| 1317 | } | 1307 | } |
| 1318 | } | 1308 | } |
| 1319 | 1309 | ||
| @@ -321,6 +321,7 @@ public: · 320 unmodified lines | |||
| 321 | void updateSimulationOwner(const SimulationOwner& owner); | 321 | void updateSimulationOwner(const SimulationOwner& owner); |
| 322 | void clearSimulationOwnership(); | 322 | void clearSimulationOwnership(); |
| 323 | void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); | 323 | void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); |
| 324 | uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } | ||
| 324 | void rememberHasSimulationOwnershipBid() const; | 325 | void rememberHasSimulationOwnershipBid() const; |
| 325 | 326 | ||
| 326 | QString getMarketplaceID() const; | 327 | QString getMarketplaceID() const; |
| @@ -394,8 +395,7 @@ public: · 67 unmodified lines | |||
| 394 | 395 | ||
| 395 | void getAllTerseUpdateProperties(EntityItemProperties& properties) const; | 396 | void getAllTerseUpdateProperties(EntityItemProperties& properties) const; |
| 396 | 397 | ||
| 397 | void pokeSimulationOwnership(); | 398 | void flagForOwnershipBid(uint8_t priority); |
| 398 | void grabSimulationOwnership(); | ||
| 399 | void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; } | 399 | void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; } |
| 400 | 400 | ||
| 401 | QString actionsToDebugString(); | 401 | QString actionsToDebugString(); |
| @@ -230,6 +230,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties · 229 unmodified lines | |||
| 230 | } | 230 | } |
| 231 | 231 | ||
| 232 | entity->setLastBroadcast(usecTimestampNow()); | 232 | entity->setLastBroadcast(usecTimestampNow()); |
| 233 | // since we're creating this object we will immediately volunteer to own its simulation | ||
| 234 | entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); | ||
| 233 | propertiesWithSimID.setLastEdited(entity->getLastEdited()); | 235 | propertiesWithSimID.setLastEdited(entity->getLastEdited()); |
| 234 | } else { | 236 | } else { |
| 235 | qCDebug(entities) << "script failed to add new Entity to local Octree"; | 237 | qCDebug(entities) << "script failed to add new Entity to local Octree"; |
| @@ -440,7 +442,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& · 204 unmodified lines | |||
| 440 | } else { | 442 | } else { |
| 441 | // we make a bid for simulation ownership | 443 | // we make a bid for simulation ownership |
| 442 | properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); | 444 | properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); |
| 443 | entity->pokeSimulationOwnership(); | 445 | entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); |
| 444 | entity->rememberHasSimulationOwnershipBid(); | 446 | entity->rememberHasSimulationOwnershipBid(); |
| 445 | } | 447 | } |
| 446 | } | 448 | } |
| @@ -1194,7 +1196,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, · 747 unmodified lines | |||
| 1194 | } | 1196 | } |
| 1195 | action->setIsMine(true); | 1197 | action->setIsMine(true); |
| 1196 | success = entity->addAction(simulation, action); | 1198 | success = entity->addAction(simulation, action); |
| 1197 | entity->grabSimulationOwnership(); | 1199 | entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); |
| 1198 | return false; // Physics will cause a packet to be sent, so don't send from here. | 1200 | return false; // Physics will cause a packet to be sent, so don't send from here. |
| 1199 | }); | 1201 | }); |
| 1200 | if (success) { | 1202 | if (success) { |
| @@ -1210,7 +1212,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& · 9 unmodified lines | |||
| 1210 | return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { | 1212 | return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { |
| 1211 | bool success = entity->updateAction(simulation, actionID, arguments); | 1213 | bool success = entity->updateAction(simulation, actionID, arguments); |
| 1212 | if (success) { | 1214 | if (success) { |
| 1213 | entity->grabSimulationOwnership(); | 1215 | entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); |
| 1214 | } | 1216 | } |
| 1215 | return success; | 1217 | return success; |
| 1216 | }); | 1218 | }); |
| @@ -1224,7 +1226,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& · 7 unmodified lines | |||
| 1224 | success = entity->removeAction(simulation, actionID); | 1226 | success = entity->removeAction(simulation, actionID); |
| 1225 | if (success) { | 1227 | if (success) { |
| 1226 | // reduce from grab to poke | 1228 | // reduce from grab to poke |
| 1227 | entity->pokeSimulationOwnership(); | 1229 | entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); |
| 1228 | } | 1230 | } |
| 1229 | return false; // Physics will cause a packet to be sent, so don't send from here. | 1231 | return false; // Physics will cause a packet to be sent, so don't send from here. |
| 1230 | }); | 1232 | }); |
| @@ -26,12 +26,10 @@ namespace Simulation { · 25 unmodified lines | |||
| 26 | const uint32_t DIRTY_MATERIAL = 0x00400; | 26 | const uint32_t DIRTY_MATERIAL = 0x00400; |
| 27 | const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine | 27 | const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine |
| 28 | const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed | 28 | const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed |
| 29 | const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_POKE = 0x2000; // bid for simulation ownership at "poke" | 29 | const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed |
| 30 | const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB = 0x4000; // bid for simulation ownership at "grab" | ||
| 31 | 30 | ||
| 32 | const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; | 31 | const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; |
| 33 | const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; | 32 | const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; |
| 34 | const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = DIRTY_SIMULATION_OWNERSHIP_FOR_POKE | DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB; | ||
| 35 | }; | 33 | }; |
| 36 | 34 | ||
| 37 | #endif // hifi_SimulationFlags_h | 35 | #endif // hifi_SimulationFlags_h |
| @@ -26,9 +26,9 @@ const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; · 25 unmodified lines | |||
| 26 | SimulationOwner::SimulationOwner() : | 26 | SimulationOwner::SimulationOwner() : |
| 27 | _id(), | 27 | _id(), |
| 28 | _expiry(0), | 28 | _expiry(0), |
| 29 | _pendingTimestamp(0), | 29 | _pendingBidTimestamp(0), |
| 30 | _priority(0), | 30 | _priority(0), |
| 31 | _pendingPriority(0), | 31 | _pendingBidPriority(0), |
| 32 | _pendingState(PENDING_STATE_NOTHING) | 32 | _pendingState(PENDING_STATE_NOTHING) |
| 33 | { | 33 | { |
| 34 | } | 34 | } |
| @@ -36,9 +36,9 @@ SimulationOwner::SimulationOwner() : · 1 unmodified line | |||
| 36 | SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : | 36 | SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : |
| 37 | _id(id), | 37 | _id(id), |
| 38 | _expiry(0), | 38 | _expiry(0), |
| 39 | _pendingTimestamp(0), | 39 | _pendingBidTimestamp(0), |
| 40 | _priority(priority), | 40 | _priority(priority), |
| 41 | _pendingPriority(0) | 41 | _pendingBidPriority(0) |
| 42 | { | 42 | { |
| 43 | } | 43 | } |
| 44 | 44 | ||
| @@ -61,9 +61,9 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) { · 16 unmodified lines | |||
| 61 | void SimulationOwner::clear() { | 61 | void SimulationOwner::clear() { |
| 62 | _id = QUuid(); | 62 | _id = QUuid(); |
| 63 | _expiry = 0; | 63 | _expiry = 0; |
| 64 | _pendingTimestamp = 0; | 64 | _pendingBidTimestamp = 0; |
| 65 | _priority = 0; | 65 | _priority = 0; |
| 66 | _pendingPriority = 0; | 66 | _pendingBidPriority = 0; |
| 67 | _pendingState = PENDING_STATE_NOTHING; | 67 | _pendingState = PENDING_STATE_NOTHING; |
| 68 | } | 68 | } |
| 69 | 69 | ||
| @@ -102,9 +102,9 @@ bool SimulationOwner::set(const SimulationOwner& owner) { · 32 unmodified lines | |||
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { | 104 | void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { |
| 105 | _pendingPriority = priority; | 105 | _pendingBidPriority = priority; |
| 106 | _pendingTimestamp = timestamp; | 106 | _pendingBidTimestamp = timestamp; |
| 107 | _pendingState = (_pendingPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; | 107 | _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; |
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | void SimulationOwner::updateExpiry() { | 110 | void SimulationOwner::updateExpiry() { |
| @@ -113,11 +113,11 @@ void SimulationOwner::updateExpiry() { · 2 unmodified lines | |||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | bool SimulationOwner::pendingRelease(const quint64& timestamp) { | 115 | bool SimulationOwner::pendingRelease(const quint64& timestamp) { |
| 116 | return _pendingPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingTimestamp >= timestamp; | 116 | return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; |
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | bool SimulationOwner::pendingTake(const quint64& timestamp) { | 119 | bool SimulationOwner::pendingTake(const quint64& timestamp) { |
| 120 | return _pendingPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingTimestamp >= timestamp; | 120 | return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; |
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | void SimulationOwner::clearCurrentOwner() { | 123 | void SimulationOwner::clearCurrentOwner() { |
| @@ -66,6 +66,7 @@ public: · 65 unmodified lines | |||
| 66 | 66 | ||
| 67 | bool hasExpired() const { return usecTimestampNow() > _expiry; } | 67 | bool hasExpired() const { return usecTimestampNow() > _expiry; } |
| 68 | 68 | ||
| 69 | uint8_t getPendingPriority() const { return _pendingBidPriority; } | ||
| 69 | bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE | 70 | bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE |
| 70 | bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE | 71 | bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE |
| 71 | void clearCurrentOwner(); | 72 | void clearCurrentOwner(); |
| @@ -84,9 +85,9 @@ public: · 12 unmodified lines | |||
| 84 | private: | 85 | private: |
| 85 | QUuid _id; // owner | 86 | QUuid _id; // owner |
| 86 | quint64 _expiry; // time when ownership can transition at equal priority | 87 | quint64 _expiry; // time when ownership can transition at equal priority |
| 87 | quint64 _pendingTimestamp; // time when pending update was set | 88 | quint64 _pendingBidTimestamp; // time when pending bid was set |
| 88 | quint8 _priority; // priority of current owner | 89 | quint8 _priority; // priority of current owner |
| 89 | quint8 _pendingPriority; // priority of pendingTake | 90 | quint8 _pendingBidPriority; // priority at which we'd like to own it |
| 90 | quint8 _pendingState; // NOTHING, TAKE, or RELEASE | 91 | quint8 _pendingState; // NOTHING, TAKE, or RELEASE |
| 91 | }; | 92 | }; |
| 92 | 93 | ||
| @@ -71,9 +71,27 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { · 70 unmodified lines | |||
| 71 | bool somethingChanged = false; | 71 | bool somethingChanged = false; |
| 72 | somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class | 72 | somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class |
| 73 | 73 | ||
| 74 | bool somethingChangedInKeyLight = _keyLightProperties.setProperties(properties); | 74 | if (somethingChanged) { |
| 75 | 75 | bool wantDebug = false; | |
| 76 | bool somethingChangedInStage = _stageProperties.setProperties(properties); | 76 | if (wantDebug) { |
| 77 | uint64_t now = usecTimestampNow(); | ||
| 78 | int elapsed = now - getLastEdited(); | ||
| 79 | qCDebug(entities) << "ZoneEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << | ||
| 80 | "now=" << now << " getLastEdited()=" << getLastEdited(); | ||
| 81 | } | ||
| 82 | setLastEdited(properties._lastEdited); | ||
| 83 | } | ||
| 84 | |||
| 85 | return somethingChanged; | ||
| 86 | } | ||
| 87 | |||
| 88 | bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& properties) { | ||
| 89 | bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class | ||
| 90 | |||
| 91 | |||
| 92 | _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); | ||
| 93 | |||
| 94 | _stagePropertiesChanged = _stageProperties.setProperties(properties); | ||
| 77 | 95 | ||
| 78 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); | 96 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); |
| 79 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); | 97 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); |
| @@ -83,20 +101,10 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { · 3 unmodified lines | |||
| 83 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); | 101 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); |
| 84 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); | 102 | SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); |
| 85 | 103 | ||
| 86 | bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties); | 104 | _skyboxPropertiesChanged = _skyboxProperties.setProperties(properties); |
| 87 | 105 | ||
| 88 | somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInSkybox; | 106 | somethingChanged = somethingChanged || _keyLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged; |
| 89 | 107 | ||
| 90 | if (somethingChanged) { | ||
| 91 | bool wantDebug = false; | ||
| 92 | if (wantDebug) { | ||
| 93 | uint64_t now = usecTimestampNow(); | ||
| 94 | int elapsed = now - getLastEdited(); | ||
| 95 | qCDebug(entities) << "ZoneEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << | ||
| 96 | "now=" << now << " getLastEdited()=" << getLastEdited(); | ||
| 97 | } | ||
| 98 | setLastEdited(properties._lastEdited); | ||
| 99 | } | ||
| 100 | 108 | ||
| 101 | return somethingChanged; | 109 | return somethingChanged; |
| 102 | } | 110 | } |
| @@ -109,14 +117,14 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, · 6 unmodified lines | |||
| 109 | const unsigned char* dataAt = data; | 117 | const unsigned char* dataAt = data; |
| 110 | 118 | ||
| 111 | int bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, | 119 | int bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, |
| 112 | propertyFlags, overwriteLocalData, somethingChanged); | 120 | propertyFlags, overwriteLocalData, _keyLightPropertiesChanged); |
| 113 | 121 | somethingChanged = somethingChanged || _keyLightPropertiesChanged; | |
| 114 | bytesRead += bytesFromKeylight; | 122 | bytesRead += bytesFromKeylight; |
| 115 | dataAt += bytesFromKeylight; | 123 | dataAt += bytesFromKeylight; |
| 116 | 124 | ||
| 117 | int bytesFromStage = _stageProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, | 125 | int bytesFromStage = _stageProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, |
| 118 | propertyFlags, overwriteLocalData, somethingChanged); | 126 | propertyFlags, overwriteLocalData, _stagePropertiesChanged); |
| 119 | 127 | somethingChanged = somethingChanged || _stagePropertiesChanged; | |
| 120 | bytesRead += bytesFromStage; | 128 | bytesRead += bytesFromStage; |
| 121 | dataAt += bytesFromStage; | 129 | dataAt += bytesFromStage; |
| 122 | 130 | ||
| @@ -125,7 +133,8 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, · 2 unmodified lines | |||
| 125 | READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); | 133 | READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); |
| 126 | 134 | ||
| 127 | int bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, | 135 | int bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, |
| 128 | propertyFlags, overwriteLocalData, somethingChanged); | 136 | propertyFlags, overwriteLocalData, _skyboxPropertiesChanged); |
| 137 | somethingChanged = somethingChanged || _skyboxPropertiesChanged; | ||
| 129 | bytesRead += bytesFromSkybox; | 138 | bytesRead += bytesFromSkybox; |
| 130 | dataAt += bytesFromSkybox; | 139 | dataAt += bytesFromSkybox; |
| 131 | 140 | ||
| @@ -185,6 +194,16 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits · 53 unmodified lines | |||
| 185 | APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); | 194 | APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); |
| 186 | } | 195 | } |
| 187 | 196 | ||
| 197 | void ZoneEntityItem::somethingChangedNotification() { | ||
| 198 | EntityItem::somethingChangedNotification(); | ||
| 199 | withWriteLock([&] { | ||
| 200 | _keyLightPropertiesChanged = false; | ||
| 201 | _backgroundPropertiesChanged = false; | ||
| 202 | _stagePropertiesChanged = false; | ||
| 203 | _skyboxPropertiesChanged = false; | ||
| 204 | }); | ||
| 205 | } | ||
| 206 | |||
| 188 | void ZoneEntityItem::debugDump() const { | 207 | void ZoneEntityItem::debugDump() const { |
| 189 | quint64 now = usecTimestampNow(); | 208 | quint64 now = usecTimestampNow(); |
| 190 | qCDebug(entities) << " ZoneEntityItem id:" << getEntityItemID() << "---------------------------------------------"; | 209 | qCDebug(entities) << " ZoneEntityItem id:" << getEntityItemID() << "---------------------------------------------"; |
| @@ -29,10 +29,16 @@ public: · 28 unmodified lines | |||
| 29 | // methods for getting/setting all properties of an entity | 29 | // methods for getting/setting all properties of an entity |
| 30 | virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; | 30 | virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; |
| 31 | virtual bool setProperties(const EntityItemProperties& properties) override; | 31 | virtual bool setProperties(const EntityItemProperties& properties) override; |
| 32 | virtual bool setSubClassProperties(const EntityItemProperties& properties) override; | ||
| 32 | 33 | ||
| 33 | // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time | 34 | // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time |
| 34 | virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; | 35 | virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; |
| 35 | 36 | ||
| 37 | /// Override this in your derived class if you'd like to be informed when something about the state of the entity | ||
| 38 | /// has changed. This will be called with properties change or when new data is loaded from a stream | ||
| 39 | /// Overriding this function to capture the information that a keylight / Ambient / skybox properties has changed | ||
| 40 | virtual void somethingChangedNotification() override; | ||
| 41 | |||
| 36 | virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, | 42 | virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, |
| 37 | EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, | 43 | EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, |
| 38 | EntityPropertyFlags& requestedProperties, | 44 | EntityPropertyFlags& requestedProperties, |
| @@ -64,7 +70,7 @@ public: · 25 unmodified lines | |||
| 64 | 70 | ||
| 65 | const KeyLightPropertyGroup& getKeyLightProperties() const { return _keyLightProperties; } | 71 | const KeyLightPropertyGroup& getKeyLightProperties() const { return _keyLightProperties; } |
| 66 | 72 | ||
| 67 | void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; } | 73 | void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; _backgroundPropertiesChanged = true; } |
| 68 | BackgroundMode getBackgroundMode() const { return _backgroundMode; } | 74 | BackgroundMode getBackgroundMode() const { return _backgroundMode; } |
| 69 | 75 | ||
| 70 | const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; } | 76 | const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; } |
| @@ -106,6 +112,14 @@ protected: · 35 unmodified lines | |||
| 106 | bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; | 112 | bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; |
| 107 | QString _filterURL { DEFAULT_FILTER_URL }; | 113 | QString _filterURL { DEFAULT_FILTER_URL }; |
| 108 | 114 | ||
| 115 | // Dirty flags turn true when either keylight properties is changing values. | ||
| 116 | // This gets back to false in the somethingChangedNotification() call | ||
| 117 | // Which is called after a setProperties() or a readEntitySubClassFromBUfferCall on the entity. | ||
| 118 | bool _keyLightPropertiesChanged { false }; | ||
| 119 | bool _backgroundPropertiesChanged { false }; | ||
| 120 | bool _skyboxPropertiesChanged { false }; | ||
| 121 | bool _stagePropertiesChanged { false }; | ||
| 122 | |||
| 109 | static bool _drawZoneBoundaries; | 123 | static bool _drawZoneBoundaries; |
| 110 | static bool _zonesArePickable; | 124 | static bool _zonesArePickable; |
| 111 | }; | 125 | }; |
| @@ -460,17 +460,11 @@ FBXLight extractLight(const FBXNode& object) { · 459 unmodified lines | |||
| 460 | } | 460 | } |
| 461 | 461 | ||
| 462 | QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { | 462 | QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { |
| 463 | QString path = QFileInfo(url).path(); | 463 | // in order to match the behaviour when loading models from remote URLs |
| 464 | QByteArray filename = filepath; | 464 | // we assume that all external textures are right beside the loaded model |
| 465 | QFileInfo checkFile(path + "/" + filepath); | 465 | // ignoring any relative paths or absolute paths inside of models |
| 466 | |||
| 467 | // check if the file exists at the RelativeFilename | ||
| 468 | if (!(checkFile.exists() && checkFile.isFile())) { | ||
| 469 | // if not, assume it is in the fbx directory | ||
| 470 | filename = filename.mid(filename.lastIndexOf('/') + 1); | ||
| 471 | } | ||
| 472 | 466 | ||
| 473 | return filename; | 467 | return filepath.mid(filepath.lastIndexOf('/') + 1); |
| 474 | } | 468 | } |
| 475 | 469 | ||
| 476 | FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { | 470 | FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { |
| @@ -1,6 +1,6 @@ | |||
| 1 | set(TARGET_NAME gl) | 1 | set(TARGET_NAME gl) |
| 2 | setup_hifi_library(OpenGL Qml Quick) | 2 | setup_hifi_library(OpenGL Qml Quick) |
| 3 | link_hifi_libraries(shared) | 3 | link_hifi_libraries(shared networking) |
| 4 | 4 | ||
| 5 | target_opengl() | 5 | target_opengl() |
| 6 | 6 | ||
| @@ -33,6 +33,9 @@ · 32 unmodified lines | |||
| 33 | #include <NetworkAccessManager.h> | 33 | #include <NetworkAccessManager.h> |
| 34 | #include <GLMHelpers.h> | 34 | #include <GLMHelpers.h> |
| 35 | #include <shared/GlobalAppProperties.h> | 35 | #include <shared/GlobalAppProperties.h> |
| 36 | #include <FileTypeProfile.h> | ||
| 37 | #include <HFWebEngineProfile.h> | ||
| 38 | #include <HFTabletWebEngineProfile.h> | ||
| 36 | 39 | ||
| 37 | #include "OffscreenGLCanvas.h" | 40 | #include "OffscreenGLCanvas.h" |
| 38 | #include "GLHelpers.h" | 41 | #include "GLHelpers.h" |
| @@ -41,6 +44,7 @@ · 2 unmodified lines | |||
| 41 | 44 | ||
| 42 | Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") | 45 | Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") |
| 43 | Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl") | 46 | Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl") |
| 47 | Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") | ||
| 44 | 48 | ||
| 45 | struct TextureSet { | 49 | struct TextureSet { |
| 46 | // The number of surfaces with this size | 50 | // The number of surfaces with this size |
| @@ -254,8 +258,89 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) { · 207 unmodified lines | |||
| 254 | return new QmlNetworkAccessManager(parent); | 258 | return new QmlNetworkAccessManager(parent); |
| 255 | } | 259 | } |
| 256 | 260 | ||
| 257 | Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) | 261 | static QQmlEngine* globalEngine { nullptr }; |
| 258 | Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") | 262 | static size_t globalEngineRefCount { 0 }; |
| 263 | |||
| 264 | QString getEventBridgeJavascript() { | ||
| 265 | // FIXME: Refactor with similar code in RenderableWebEntityItem | ||
| 266 | QString javaScriptToInject; | ||
| 267 | QFile webChannelFile(":qtwebchannel/qwebchannel.js"); | ||
| 268 | QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js"); | ||
| 269 | if (webChannelFile.open(QFile::ReadOnly | QFile::Text) && | ||
| 270 | createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) { | ||
| 271 | QString webChannelStr = QTextStream(&webChannelFile).readAll(); | ||
| 272 | QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll(); | ||
| 273 | javaScriptToInject = webChannelStr + createGlobalEventBridgeStr; | ||
| 274 | } else { | ||
| 275 | qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js"; | ||
| 276 | } | ||
| 277 | return javaScriptToInject; | ||
| 278 | } | ||
| 279 | |||
| 280 | class EventBridgeWrapper : public QObject { | ||
| 281 | Q_OBJECT | ||
| 282 | Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT); | ||
| 283 | |||
| 284 | public: | ||
| 285 | EventBridgeWrapper(QObject* eventBridge, QObject* parent = nullptr) : QObject(parent), _eventBridge(eventBridge) { | ||
| 286 | } | ||
| 287 | |||
| 288 | QObject* getEventBridge() { | ||
| 289 | return _eventBridge; | ||
| 290 | } | ||
| 291 | |||
| 292 | private: | ||
| 293 | QObject* _eventBridge; | ||
| 294 | }; | ||
| 295 | |||
| 296 | |||
| 297 | |||
| 298 | QQmlEngine* acquireEngine(QQuickWindow* window) { | ||
| 299 | Q_ASSERT(QThread::currentThread() == qApp->thread()); | ||
| 300 | if (!globalEngine) { | ||
| 301 | Q_ASSERT(0 == globalEngineRefCount); | ||
| 302 | globalEngine = new QQmlEngine(); | ||
| 303 | globalEngine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); | ||
| 304 | |||
| 305 | auto importList = globalEngine->importPathList(); | ||
| 306 | importList.insert(importList.begin(), PathUtils::resourcesPath()); | ||
| 307 | globalEngine->setImportPathList(importList); | ||
| 308 | for (const auto& path : importList) { | ||
| 309 | qDebug() << path; | ||
| 310 | } | ||
| 311 | |||
| 312 | if (!globalEngine->incubationController()) { | ||
| 313 | globalEngine->setIncubationController(window->incubationController()); | ||
| 314 | } | ||
| 315 | auto rootContext = globalEngine->rootContext(); | ||
| 316 | rootContext->setContextProperty("GL", ::getGLContextData()); | ||
| 317 | rootContext->setContextProperty("urlHandler", new UrlHandler()); | ||
| 318 | rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); | ||
| 319 | rootContext->setContextProperty("pathToFonts", "../../"); | ||
| 320 | rootContext->setContextProperty("ApplicationInterface", qApp); | ||
| 321 | auto javaScriptToInject = getEventBridgeJavascript(); | ||
| 322 | if (!javaScriptToInject.isEmpty()) { | ||
| 323 | rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); | ||
| 324 | } | ||
| 325 | rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); | ||
| 326 | rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); | ||
| 327 | rootContext->setContextProperty("HFTabletWebEngineProfile", new HFTabletWebEngineProfile(rootContext)); | ||
| 328 | |||
| 329 | |||
| 330 | } | ||
| 331 | |||
| 332 | ++globalEngineRefCount; | ||
| 333 | return globalEngine; | ||
| 334 | } | ||
| 335 | |||
| 336 | void releaseEngine() { | ||
| 337 | Q_ASSERT(QThread::currentThread() == qApp->thread()); | ||
| 338 | Q_ASSERT(0 != globalEngineRefCount); | ||
| 339 | if (0 == --globalEngineRefCount) { | ||
| 340 | globalEngine->deleteLater(); | ||
| 341 | globalEngine = nullptr; | ||
| 342 | } | ||
| 343 | } | ||
| 259 | 344 | ||
| 260 | void OffscreenQmlSurface::cleanup() { | 345 | void OffscreenQmlSurface::cleanup() { |
| 261 | _canvas->makeCurrent(); | 346 | _canvas->makeCurrent(); |
| @@ -294,6 +379,7 @@ void OffscreenQmlSurface::render() { · 32 unmodified lines | |||
| 294 | GLuint texture = offscreenTextures.getNextTexture(_size); | 379 | GLuint texture = offscreenTextures.getNextTexture(_size); |
| 295 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); | 380 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); |
| 296 | glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); | 381 | glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); |
| 382 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | ||
| 297 | _renderControl->render(); | 383 | _renderControl->render(); |
| 298 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); | 384 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
| 299 | glBindTexture(GL_TEXTURE_2D, texture); | 385 | glBindTexture(GL_TEXTURE_2D, texture); |
| @@ -361,9 +447,8 @@ OffscreenQmlSurface::~OffscreenQmlSurface() { · 61 unmodified lines | |||
| 361 | 447 | ||
| 362 | _canvas->deleteLater(); | 448 | _canvas->deleteLater(); |
| 363 | _rootItem->deleteLater(); | 449 | _rootItem->deleteLater(); |
| 364 | _qmlComponent->deleteLater(); | ||
| 365 | _qmlEngine->deleteLater(); | ||
| 366 | _quickWindow->deleteLater(); | 450 | _quickWindow->deleteLater(); |
| 451 | releaseEngine(); | ||
| 367 | } | 452 | } |
| 368 | 453 | ||
| 369 | void OffscreenQmlSurface::onAboutToQuit() { | 454 | void OffscreenQmlSurface::onAboutToQuit() { |
| @@ -375,6 +460,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 5 unmodified lines | |||
| 375 | qCDebug(glLogging) << "Building QML surface"; | 460 | qCDebug(glLogging) << "Building QML surface"; |
| 376 | 461 | ||
| 377 | _renderControl = new QMyQuickRenderControl(); | 462 | _renderControl = new QMyQuickRenderControl(); |
| 463 | connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; }); | ||
| 464 | connect(_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { _render = _polish = true; }); | ||
| 378 | 465 | ||
| 379 | QQuickWindow::setDefaultAlphaBuffer(true); | 466 | QQuickWindow::setDefaultAlphaBuffer(true); |
| 380 | 467 | ||
| @@ -385,7 +472,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 4 unmodified lines | |||
| 385 | // so we wait until after its ctor to move object/context to this thread. | 472 | // so we wait until after its ctor to move object/context to this thread. |
| 386 | _quickWindow = new QQuickWindow(_renderControl); | 473 | _quickWindow = new QQuickWindow(_renderControl); |
| 387 | _quickWindow->setColor(QColor(255, 255, 255, 0)); | 474 | _quickWindow->setColor(QColor(255, 255, 255, 0)); |
| 388 | _quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground)); | 475 | _quickWindow->setClearBeforeRendering(false); |
| 389 | 476 | ||
| 390 | _renderControl->_renderWindow = _proxyWindow; | 477 | _renderControl->_renderWindow = _proxyWindow; |
| 391 | 478 | ||
| @@ -398,32 +485,21 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 6 unmodified lines | |||
| 398 | connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); | 485 | connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); |
| 399 | 486 | ||
| 400 | // Create a QML engine. | 487 | // Create a QML engine. |
| 401 | _qmlEngine = new QQmlEngine; | 488 | auto qmlEngine = acquireEngine(_quickWindow); |
| 402 | 489 | _qmlContext = new QQmlContext(qmlEngine->rootContext()); | |
| 403 | _qmlEngine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); | ||
| 404 | |||
| 405 | auto importList = _qmlEngine->importPathList(); | ||
| 406 | importList.insert(importList.begin(), PathUtils::resourcesPath()); | ||
| 407 | _qmlEngine->setImportPathList(importList); | ||
| 408 | if (!_qmlEngine->incubationController()) { | ||
| 409 | _qmlEngine->setIncubationController(_quickWindow->incubationController()); | ||
| 410 | } | ||
| 411 | |||
| 412 | // FIXME | ||
| 413 | _glData = ::getGLContextData(); // Initialize JSON structure so that it can be filled in later and then used in QML. | ||
| 414 | _qmlEngine->rootContext()->setContextProperty("GL", _glData); | ||
| 415 | _qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); | ||
| 416 | _qmlComponent = new QQmlComponent(_qmlEngine); | ||
| 417 | 490 | ||
| 491 | _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); | ||
| 492 | _qmlContext->setContextProperty("eventBridge", this); | ||
| 493 | _qmlContext->setContextProperty("webEntity", this); | ||
| 418 | 494 | ||
| 419 | connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; }); | 495 | // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper |
| 420 | connect(_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { _render = _polish = true; }); | 496 | // Find a way to flag older scripts using this mechanism and wanr that this is deprecated |
| 497 | _qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext)); | ||
| 421 | 498 | ||
| 422 | if (!_canvas->makeCurrent()) { | 499 | if (!_canvas->makeCurrent()) { |
| 423 | qWarning("Failed to make context current for QML Renderer"); | 500 | qWarning("Failed to make context current for QML Renderer"); |
| 424 | return; | 501 | return; |
| 425 | } | 502 | } |
| 426 | _glData = ::getGLContextData(); | ||
| 427 | _renderControl->initialize(_canvas->getContext()); | 503 | _renderControl->initialize(_canvas->getContext()); |
| 428 | 504 | ||
| 429 | // When Quick says there is a need to render, we will not render immediately. Instead, | 505 | // When Quick says there is a need to render, we will not render immediately. Instead, |
| @@ -433,11 +509,6 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { · 3 unmodified lines | |||
| 433 | _updateTimer.setTimerType(Qt::PreciseTimer); | 509 | _updateTimer.setTimerType(Qt::PreciseTimer); |
| 434 | _updateTimer.setInterval(MIN_TIMER_MS); // 5ms, Qt::PreciseTimer required | 510 | _updateTimer.setInterval(MIN_TIMER_MS); // 5ms, Qt::PreciseTimer required |
| 435 | _updateTimer.start(); | 511 | _updateTimer.start(); |
| 436 | |||
| 437 | auto rootContext = getRootContext(); | ||
| 438 | rootContext->setContextProperty("urlHandler", new UrlHandler()); | ||
| 439 | rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); | ||
| 440 | rootContext->setContextProperty("pathToFonts", "../../"); | ||
| 441 | } | 512 | } |
| 442 | 513 | ||
| 443 | static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) { | 514 | static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) { |
| @@ -460,7 +531,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { · 16 unmodified lines | |||
| 460 | return; | 531 | return; |
| 461 | } | 532 | } |
| 462 | 533 | ||
| 463 | _qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize); | 534 | _qmlContext->setContextProperty("surfaceSize", newSize); |
| 464 | 535 | ||
| 465 | if (_rootItem) { | 536 | if (_rootItem) { |
| 466 | _rootItem->setSize(newSize); | 537 | _rootItem->setSize(newSize); |
| @@ -520,73 +591,82 @@ QQuickItem* OffscreenQmlSurface::getRootItem() { · 53 unmodified lines | |||
| 520 | } | 591 | } |
| 521 | 592 | ||
| 522 | void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { | 593 | void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { |
| 523 | _qmlEngine->setBaseUrl(baseUrl); | 594 | _qmlContext->setBaseUrl(baseUrl); |
| 524 | } | 595 | } |
| 525 | 596 | ||
| 526 | QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) { | 597 | QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f) { |
| 527 | // Synchronous loading may take a while; restart the deadlock timer | 598 | // Synchronous loading may take a while; restart the deadlock timer |
| 528 | QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); | 599 | QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); |
| 529 | 600 | ||
| 530 | qDebug() << qmlSource; | 601 | QQmlContext* targetContext = _qmlContext; |
| 531 | _qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous); | 602 | if (_rootItem && createNewContext) { |
| 603 | targetContext = new QQmlContext(targetContext); | ||
| 604 | } | ||
| 532 | 605 | ||
| 533 | if (_qmlComponent->isLoading()) { | 606 | QUrl finalQmlSource = qmlSource; |
| 534 | connect(_qmlComponent, &QQmlComponent::statusChanged, this, | 607 | if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { |
| 535 | [this, f](QQmlComponent::Status){ | 608 | finalQmlSource = _qmlContext->resolvedUrl(qmlSource); |
| 536 | finishQmlLoad(f); | 609 | } |
| 537 | }); | 610 | |
| 611 | auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); | ||
| 612 | if (qmlComponent->isLoading()) { | ||
| 613 | connect(qmlComponent, &QQmlComponent::statusChanged, this, | ||
| 614 | [this, qmlComponent, targetContext, f](QQmlComponent::Status) { | ||
| 615 | finishQmlLoad(qmlComponent, targetContext, f); | ||
| 616 | }); | ||
| 538 | return nullptr; | 617 | return nullptr; |
| 539 | } | 618 | } |
| 540 | 619 | ||
| 541 | return finishQmlLoad(f); | 620 | return finishQmlLoad(qmlComponent, targetContext, f); |
| 621 | } | ||
| 622 | |||
| 623 | QObject* OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) { | ||
| 624 | return load(qmlSource, true, f); | ||
| 625 | } | ||
| 626 | |||
| 627 | QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) { | ||
| 628 | return load(qmlSource, false, f); | ||
| 542 | } | 629 | } |
| 543 | 630 | ||
| 544 | void OffscreenQmlSurface::clearCache() { | 631 | void OffscreenQmlSurface::clearCache() { |
| 545 | getRootContext()->engine()->clearComponentCache(); | 632 | _qmlContext->engine()->clearComponentCache(); |
| 546 | } | 633 | } |
| 547 | 634 | ||
| 548 | QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) { | 635 | QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f) { |
| 549 | disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); | 636 | disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); |
| 550 | if (_qmlComponent->isError()) { | 637 | if (qmlComponent->isError()) { |
| 551 | QList<QQmlError> errorList = _qmlComponent->errors(); | 638 | for (const auto& error : qmlComponent->errors()) { |
| 552 | foreach(const QQmlError& error, errorList) { | 639 | qCWarning(glLogging) << error.url() << error.line() << error; |
| 553 | qWarning() << error.url() << error.line() << error; | ||
| 554 | } | 640 | } |
| 641 | qmlComponent->deleteLater(); | ||
| 555 | return nullptr; | 642 | return nullptr; |
| 556 | } | 643 | } |
| 557 | 644 | ||
| 558 | // FIXME: Refactor with similar code in RenderableWebEntityItem | ||
| 559 | QString javaScriptToInject; | ||
| 560 | QFile webChannelFile(":qtwebchannel/qwebchannel.js"); | ||
| 561 | QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js"); | ||
| 562 | if (webChannelFile.open(QFile::ReadOnly | QFile::Text) && | ||
| 563 | createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) { | ||
| 564 | QString webChannelStr = QTextStream(&webChannelFile).readAll(); | ||
| 565 | QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll(); | ||
| 566 | javaScriptToInject = webChannelStr + createGlobalEventBridgeStr; | ||
| 567 | } else { | ||
| 568 | qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js"; | ||
| 569 | } | ||
| 570 | 645 | ||
| 571 | QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp); | 646 | QObject* newObject = qmlComponent->beginCreate(qmlContext); |
| 572 | QObject* newObject = _qmlComponent->beginCreate(newContext); | 647 | if (qmlComponent->isError()) { |
| 573 | if (_qmlComponent->isError()) { | 648 | for (const auto& error : qmlComponent->errors()) { |
| 574 | QList<QQmlError> errorList = _qmlComponent->errors(); | ||
| 575 | foreach(const QQmlError& error, errorList) | ||
| 576 | qCWarning(glLogging) << error.url() << error.line() << error; | 649 | qCWarning(glLogging) << error.url() << error.line() << error; |
| 650 | } | ||
| 577 | if (!_rootItem) { | 651 | if (!_rootItem) { |
| 578 | qFatal("Unable to finish loading QML root"); | 652 | qFatal("Unable to finish loading QML root"); |
| 579 | } | 653 | } |
| 654 | qmlComponent->deleteLater(); | ||
| 580 | return nullptr; | 655 | return nullptr; |
| 581 | } | 656 | } |
| 582 | 657 | ||
| 583 | _qmlEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); | 658 | qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); |
| 584 | newObject->setProperty("eventBridge", QVariant::fromValue(this)); | 659 | f(qmlContext, newObject); |
| 585 | 660 | ||
| 586 | newContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); | 661 | QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>(); |
| 662 | if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { | ||
| 663 | // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper | ||
| 664 | // Find a way to flag older scripts using this mechanism and wanr that this is deprecated | ||
| 665 | qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); | ||
| 666 | } | ||
| 587 | 667 | ||
| 588 | f(newContext, newObject); | 668 | qmlComponent->completeCreate(); |
| 589 | _qmlComponent->completeCreate(); | 669 | qmlComponent->deleteLater(); |
| 590 | 670 | ||
| 591 | 671 | ||
| 592 | // All quick items should be focusable | 672 | // All quick items should be focusable |
| @@ -736,7 +816,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even · 143 unmodified lines | |||
| 736 | mouseEvent->screenPos(), mouseEvent->button(), | 816 | mouseEvent->screenPos(), mouseEvent->button(), |
| 737 | mouseEvent->buttons(), mouseEvent->modifiers()); | 817 | mouseEvent->buttons(), mouseEvent->modifiers()); |
| 738 | if (event->type() == QEvent::MouseMove) { | 818 | if (event->type() == QEvent::MouseMove) { |
| 739 | _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos); | 819 | _qmlContext->setContextProperty("lastMousePosition", transformedPos); |
| 740 | } | 820 | } |
| 741 | mappedEvent.ignore(); | 821 | mappedEvent.ignore(); |
| 742 | if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { | 822 | if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { |
| @@ -763,9 +843,6 @@ void OffscreenQmlSurface::resume() { · 20 unmodified lines | |||
| 763 | if (getRootItem()) { | 843 | if (getRootItem()) { |
| 764 | getRootItem()->setProperty("eventBridge", QVariant::fromValue(this)); | 844 | getRootItem()->setProperty("eventBridge", QVariant::fromValue(this)); |
| 765 | } | 845 | } |
| 766 | if (getRootContext()) { | ||
| 767 | getRootContext()->setContextProperty("webEntity", this); | ||
| 768 | } | ||
| 769 | } | 846 | } |
| 770 | 847 | ||
| 771 | bool OffscreenQmlSurface::isPaused() const { | 848 | bool OffscreenQmlSurface::isPaused() const { |
| @@ -791,8 +868,8 @@ QSize OffscreenQmlSurface::size() const { · 19 unmodified lines | |||
| 791 | return _quickWindow->geometry().size(); | 868 | return _quickWindow->geometry().size(); |
| 792 | } | 869 | } |
| 793 | 870 | ||
| 794 | QQmlContext* OffscreenQmlSurface::getRootContext() { | 871 | QQmlContext* OffscreenQmlSurface::getSurfaceContext() { |
| 795 | return _qmlEngine->rootContext(); | 872 | return _qmlContext; |
| 796 | } | 873 | } |
| 797 | 874 | ||
| 798 | Q_DECLARE_METATYPE(std::function<void()>); | 875 | Q_DECLARE_METATYPE(std::function<void()>); |
| @@ -48,6 +48,8 @@ public: · 47 unmodified lines | |||
| 48 | void resize(const QSize& size, bool forceResize = false); | 48 | void resize(const QSize& size, bool forceResize = false); |
| 49 | QSize size() const; | 49 | QSize size() const; |
| 50 | 50 | ||
| 51 | Q_INVOKABLE QObject* load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); | ||
| 52 | Q_INVOKABLE QObject* loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); | ||
| 51 | Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); | 53 | Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); |
| 52 | Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) { | 54 | Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) { |
| 53 | return load(QUrl(qmlSourceFile), f); | 55 | return load(QUrl(qmlSourceFile), f); |
| @@ -73,7 +75,7 @@ public: · 19 unmodified lines | |||
| 73 | QQuickItem* getRootItem(); | 75 | QQuickItem* getRootItem(); |
| 74 | QQuickWindow* getWindow(); | 76 | QQuickWindow* getWindow(); |
| 75 | QObject* getEventHandler(); | 77 | QObject* getEventHandler(); |
| 76 | QQmlContext* getRootContext(); | 78 | QQmlContext* getSurfaceContext(); |
| 77 | 79 | ||
| 78 | QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget); | 80 | QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget); |
| 79 | bool eventFilter(QObject* originalDestination, QEvent* event) override; | 81 | bool eventFilter(QObject* originalDestination, QEvent* event) override; |
| @@ -118,7 +120,7 @@ protected: · 38 unmodified lines | |||
| 118 | void setFocusText(bool newFocusText); | 120 | void setFocusText(bool newFocusText); |
| 119 | 121 | ||
| 120 | private: | 122 | private: |
| 121 | QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f); | 123 | QObject* finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f); |
| 122 | QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); | 124 | QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); |
| 123 | void setupFbo(); | 125 | void setupFbo(); |
| 124 | bool allowNewFrame(uint8_t fps); | 126 | bool allowNewFrame(uint8_t fps); |
| @@ -133,11 +135,9 @@ private slots: · 8 unmodified lines | |||
| 133 | private: | 135 | private: |
| 134 | QQuickWindow* _quickWindow { nullptr }; | 136 | QQuickWindow* _quickWindow { nullptr }; |
| 135 | QMyQuickRenderControl* _renderControl{ nullptr }; | 137 | QMyQuickRenderControl* _renderControl{ nullptr }; |
| 136 | QQmlEngine* _qmlEngine { nullptr }; | 138 | QQmlContext* _qmlContext { nullptr }; |
| 137 | QQmlComponent* _qmlComponent { nullptr }; | ||
| 138 | QQuickItem* _rootItem { nullptr }; | 139 | QQuickItem* _rootItem { nullptr }; |
| 139 | OffscreenGLCanvas* _canvas { nullptr }; | 140 | OffscreenGLCanvas* _canvas { nullptr }; |
| 140 | QJsonObject _glData; | ||
| 141 | 141 | ||
| 142 | QTimer _updateTimer; | 142 | QTimer _updateTimer; |
| 143 | uint32_t _fbo { 0 }; | 143 | uint32_t _fbo { 0 }; |
| @@ -49,7 +49,6 @@ QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const · 48 unmodified lines | |||
| 49 | surface->create(currentContext); | 49 | surface->create(currentContext); |
| 50 | surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); | 50 | surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); |
| 51 | surface->load(rootSource); | 51 | surface->load(rootSource); |
| 52 | surface->getRootContext()->setContextProperty("ApplicationInterface", qApp); | ||
| 53 | surface->resize(QSize(100, 100)); | 52 | surface->resize(QSize(100, 100)); |
| 54 | currentContext->makeCurrent(currentSurface); | 53 | currentContext->makeCurrent(currentSurface); |
| 55 | return surface; | 54 | return surface; |
| @@ -63,11 +63,17 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { · 62 unmodified lines | |||
| 63 | int useScissor = batch._params[paramOffset + 0]._int; | 63 | int useScissor = batch._params[paramOffset + 0]._int; |
| 64 | 64 | ||
| 65 | GLuint glmask = 0; | 65 | GLuint glmask = 0; |
| 66 | bool restoreStencilMask = false; | ||
| 67 | uint8_t cacheStencilMask = 0xFF; | ||
| 66 | if (masks & Framebuffer::BUFFER_STENCIL) { | 68 | if (masks & Framebuffer::BUFFER_STENCIL) { |
| 67 | glClearStencil(stencil); | 69 | glClearStencil(stencil); |
| 68 | glmask |= GL_STENCIL_BUFFER_BIT; | 70 | glmask |= GL_STENCIL_BUFFER_BIT; |
| 69 | // TODO: we will probably need to also check the write mask of stencil like we do | 71 | |
| 70 | // for depth buffer, but as would say a famous Fez owner "We'll cross that bridge when we come to it" | 72 | cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront(); |
| 73 | if (cacheStencilMask != 0xFF) { | ||
| 74 | restoreStencilMask = true; | ||
| 75 | glStencilMask( 0xFF); | ||
| 76 | } | ||
| 71 | } | 77 | } |
| 72 | 78 | ||
| 73 | bool restoreDepthMask = false; | 79 | bool restoreDepthMask = false; |
| @@ -121,6 +127,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { · 47 unmodified lines | |||
| 121 | glDisable(GL_SCISSOR_TEST); | 127 | glDisable(GL_SCISSOR_TEST); |
| 122 | } | 128 | } |
| 123 | 129 | ||
| 130 | // Restore Stencil write mask | ||
| 131 | if (restoreStencilMask) { | ||
| 132 | glStencilMask(cacheStencilMask); | ||
| 133 | } | ||
| 134 | |||
| 124 | // Restore write mask meaning turn back off | 135 | // Restore write mask meaning turn back off |
| 125 | if (restoreDepthMask) { | 136 | if (restoreDepthMask) { |
| 126 | glDepthMask(GL_FALSE); | 137 | glDepthMask(GL_FALSE); |
| @@ -211,6 +211,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { · 210 unmodified lines | |||
| 211 | break; | 211 | break; |
| 212 | case gpu::NUINT32: | 212 | case gpu::NUINT32: |
| 213 | case gpu::NINT32: | 213 | case gpu::NINT32: |
| 214 | case gpu::COMPRESSED: | ||
| 214 | case gpu::NUM_TYPES: // quiet compiler | 215 | case gpu::NUM_TYPES: // quiet compiler |
| 215 | Q_UNREACHABLE(); | 216 | Q_UNREACHABLE(); |
| 216 | } | 217 | } |
| @@ -484,6 +485,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E · 267 unmodified lines | |||
| 484 | texel.internalFormat = GL_R8_SNORM; | 485 | texel.internalFormat = GL_R8_SNORM; |
| 485 | break; | 486 | break; |
| 486 | } | 487 | } |
| 488 | case gpu::COMPRESSED: | ||
| 487 | case gpu::NUM_TYPES: { // quiet compiler | 489 | case gpu::NUM_TYPES: { // quiet compiler |
| 488 | Q_UNREACHABLE(); | 490 | Q_UNREACHABLE(); |
| 489 | } | 491 | } |
| @@ -527,6 +529,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E · 37 unmodified lines | |||
| 527 | texel.internalFormat = GL_DEPTH_COMPONENT24; | 529 | texel.internalFormat = GL_DEPTH_COMPONENT24; |
| 528 | break; | 530 | break; |
| 529 | } | 531 | } |
| 532 | case gpu::COMPRESSED: | ||
| 530 | case gpu::NUM_TYPES: { // quiet compiler | 533 | case gpu::NUM_TYPES: { // quiet compiler |
| 531 | Q_UNREACHABLE(); | 534 | Q_UNREACHABLE(); |
| 532 | } | 535 | } |
| @@ -641,6 +644,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E · 108 unmodified lines | |||
| 641 | break; | 644 | break; |
| 642 | case gpu::NUINT32: | 645 | case gpu::NUINT32: |
| 643 | case gpu::NINT32: | 646 | case gpu::NINT32: |
| 647 | case gpu::COMPRESSED: | ||
| 644 | case gpu::NUM_TYPES: // quiet compiler | 648 | case gpu::NUM_TYPES: // quiet compiler |
| 645 | Q_UNREACHABLE(); | 649 | Q_UNREACHABLE(); |
| 646 | } | 650 | } |
| @@ -0,0 +1,28 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // Draw and transform the fed vertex position with the standard MVP stack | ||
| 6 | // Output the clip position | ||
| 7 | // | ||
| 8 | // Created by Sam Gateau on 5/30/2017 | ||
| 9 | // Copyright 2017 High Fidelity, Inc. | ||
| 10 | // | ||
| 11 | // Distributed under the Apache License, Version 2.0. | ||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 13 | // | ||
| 14 | |||
| 15 | <@include gpu/Transform.slh@> | ||
| 16 | |||
| 17 | <$declareStandardTransform()$> | ||
| 18 | |||
| 19 | layout(location = 0) in vec4 inPosition; | ||
| 20 | |||
| 21 | out vec3 varWorldPos; | ||
| 22 | |||
| 23 | void main(void) { | ||
| 24 | // standard transform | ||
| 25 | TransformCamera cam = getTransformCamera(); | ||
| 26 | TransformObject obj = getTransformObject(); | ||
| 27 | <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> | ||
| 28 | } | ||
| @@ -2,25 +2,18 @@ · 1 unmodified line | |||
| 2 | <$VERSION_HEADER$> | 2 | <$VERSION_HEADER$> |
| 3 | // Generated on <$_SCRIBE_DATE$> | 3 | // Generated on <$_SCRIBE_DATE$> |
| 4 | // | 4 | // |
| 5 | // hit_effect.vert | 5 | // Draw the fed vertex position, pass straight as clip pos |
| 6 | // vertex shader | 6 | // Output the clip position |
| 7 | // | 7 | // |
| 8 | // Created by Eric Levin on 7/20/15. | 8 | // Created by Sam Gateau on 5/30/2017 |
| 9 | // Copyright 2015 High Fidelity, Inc. | 9 | // Copyright 2017 High Fidelity, Inc. |
| 10 | // | 10 | // |
| 11 | // Distributed under the Apache License, Version 2.0. | 11 | // Distributed under the Apache License, Version 2.0. |
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 13 | // | 13 | // |
| 14 | 14 | ||
| 15 | <@include gpu/Inputs.slh@> | 15 | layout(location = 0) in vec4 inPosition; |
| 16 | |||
| 17 | <@include gpu/Transform.slh@> | ||
| 18 | |||
| 19 | <$declareStandardTransform()$> | ||
| 20 | |||
| 21 | out vec2 varQuadPosition; | ||
| 22 | 16 | ||
| 23 | void main(void) { | 17 | void main(void) { |
| 24 | varQuadPosition = inPosition.xy; | ||
| 25 | gl_Position = inPosition; | 18 | gl_Position = inPosition; |
| 26 | } | 19 | } |
| @@ -2,15 +2,17 @@ · 1 unmodified line | |||
| 2 | <$VERSION_HEADER$> | 2 | <$VERSION_HEADER$> |
| 3 | // Generated on <$_SCRIBE_DATE$> | 3 | // Generated on <$_SCRIBE_DATE$> |
| 4 | // | 4 | // |
| 5 | // drawOpaqueStencil.frag | 5 | // Draw white |
| 6 | // fragment shader | ||
| 7 | // | 6 | // |
| 8 | // Created by Sam Gateau on 9/29/15. | 7 | // Created by Sam Gateau on 5/30/2017 |
| 9 | // Copyright 2015 High Fidelity, Inc. | 8 | // Copyright 2017 High Fidelity, Inc. |
| 10 | // | 9 | // |
| 11 | // Distributed under the Apache License, Version 2.0. | 10 | // Distributed under the Apache License, Version 2.0. |
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 11 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 13 | // | 12 | // |
| 14 | 13 | ||
| 14 | out vec4 outFragColor; | ||
| 15 | |||
| 15 | void main(void) { | 16 | void main(void) { |
| 17 | outFragColor = vec4(1.0); | ||
| 16 | } | 18 | } |
| @@ -16,6 +16,12 @@ · 15 unmodified lines | |||
| 16 | #include "DrawTransformUnitQuad_vert.h" | 16 | #include "DrawTransformUnitQuad_vert.h" |
| 17 | #include "DrawTexcoordRectTransformUnitQuad_vert.h" | 17 | #include "DrawTexcoordRectTransformUnitQuad_vert.h" |
| 18 | #include "DrawViewportQuadTransformTexcoord_vert.h" | 18 | #include "DrawViewportQuadTransformTexcoord_vert.h" |
| 19 | #include "DrawVertexPosition_vert.h" | ||
| 20 | #include "DrawTransformVertexPosition_vert.h" | ||
| 21 | |||
| 22 | const char DrawNada_frag[] = "void main(void) {}"; // DrawNada is really simple... | ||
| 23 | |||
| 24 | #include "DrawWhite_frag.h" | ||
| 19 | #include "DrawTexture_frag.h" | 25 | #include "DrawTexture_frag.h" |
| 20 | #include "DrawTextureOpaque_frag.h" | 26 | #include "DrawTextureOpaque_frag.h" |
| 21 | #include "DrawColoredTexture_frag.h" | 27 | #include "DrawColoredTexture_frag.h" |
| @@ -26,6 +32,10 @@ ShaderPointer StandardShaderLib::_drawUnitQuadTexcoordVS; · 4 unmodified lines | |||
| 26 | ShaderPointer StandardShaderLib::_drawTransformUnitQuadVS; | 32 | ShaderPointer StandardShaderLib::_drawTransformUnitQuadVS; |
| 27 | ShaderPointer StandardShaderLib::_drawTexcoordRectTransformUnitQuadVS; | 33 | ShaderPointer StandardShaderLib::_drawTexcoordRectTransformUnitQuadVS; |
| 28 | ShaderPointer StandardShaderLib::_drawViewportQuadTransformTexcoordVS; | 34 | ShaderPointer StandardShaderLib::_drawViewportQuadTransformTexcoordVS; |
| 35 | ShaderPointer StandardShaderLib::_drawVertexPositionVS; | ||
| 36 | ShaderPointer StandardShaderLib::_drawTransformVertexPositionVS; | ||
| 37 | ShaderPointer StandardShaderLib::_drawNadaPS; | ||
| 38 | ShaderPointer StandardShaderLib::_drawWhitePS; | ||
| 29 | ShaderPointer StandardShaderLib::_drawTexturePS; | 39 | ShaderPointer StandardShaderLib::_drawTexturePS; |
| 30 | ShaderPointer StandardShaderLib::_drawTextureOpaquePS; | 40 | ShaderPointer StandardShaderLib::_drawTextureOpaquePS; |
| 31 | ShaderPointer StandardShaderLib::_drawColoredTexturePS; | 41 | ShaderPointer StandardShaderLib::_drawColoredTexturePS; |
| @@ -85,6 +95,34 @@ ShaderPointer StandardShaderLib::getDrawViewportQuadTransformTexcoordVS() { · 53 unmodified lines | |||
| 85 | return _drawViewportQuadTransformTexcoordVS; | 95 | return _drawViewportQuadTransformTexcoordVS; |
| 86 | } | 96 | } |
| 87 | 97 | ||
| 98 | ShaderPointer StandardShaderLib::getDrawVertexPositionVS() { | ||
| 99 | if (!_drawVertexPositionVS) { | ||
| 100 | _drawVertexPositionVS = gpu::Shader::createVertex(std::string(DrawVertexPosition_vert)); | ||
| 101 | } | ||
| 102 | return _drawVertexPositionVS; | ||
| 103 | } | ||
| 104 | |||
| 105 | ShaderPointer StandardShaderLib::getDrawTransformVertexPositionVS() { | ||
| 106 | if (!_drawTransformVertexPositionVS) { | ||
| 107 | _drawTransformVertexPositionVS = gpu::Shader::createVertex(std::string(DrawTransformVertexPosition_vert)); | ||
| 108 | } | ||
| 109 | return _drawTransformVertexPositionVS; | ||
| 110 | } | ||
| 111 | |||
| 112 | ShaderPointer StandardShaderLib::getDrawNadaPS() { | ||
| 113 | if (!_drawNadaPS) { | ||
| 114 | _drawNadaPS = gpu::Shader::createPixel(std::string(DrawNada_frag)); | ||
| 115 | } | ||
| 116 | return _drawNadaPS; | ||
| 117 | } | ||
| 118 | |||
| 119 | ShaderPointer StandardShaderLib::getDrawWhitePS() { | ||
| 120 | if (!_drawWhitePS) { | ||
| 121 | _drawWhitePS = gpu::Shader::createPixel(std::string(DrawWhite_frag)); | ||
| 122 | } | ||
| 123 | return _drawWhitePS; | ||
| 124 | } | ||
| 125 | |||
| 88 | ShaderPointer StandardShaderLib::getDrawTexturePS() { | 126 | ShaderPointer StandardShaderLib::getDrawTexturePS() { |
| 89 | if (!_drawTexturePS) { | 127 | if (!_drawTexturePS) { |
| 90 | _drawTexturePS = gpu::Shader::createPixel(std::string(DrawTexture_frag)); | 128 | _drawTexturePS = gpu::Shader::createPixel(std::string(DrawTexture_frag)); |
| @@ -99,8 +137,6 @@ ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { · 8 unmodified lines | |||
| 99 | return _drawTextureOpaquePS; | 137 | return _drawTextureOpaquePS; |
| 100 | } | 138 | } |
| 101 | 139 | ||
| 102 | |||
| 103 | |||
| 104 | ShaderPointer StandardShaderLib::getDrawColoredTexturePS() { | 140 | ShaderPointer StandardShaderLib::getDrawColoredTexturePS() { |
| 105 | if (!_drawColoredTexturePS) { | 141 | if (!_drawColoredTexturePS) { |
| 106 | _drawColoredTexturePS = gpu::Shader::createPixel(std::string(DrawColoredTexture_frag)); | 142 | _drawColoredTexturePS = gpu::Shader::createPixel(std::string(DrawColoredTexture_frag)); |
| @@ -37,6 +37,15 @@ public: · 36 unmodified lines | |||
| 37 | // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and transform the texcoord = [(0,0),(1,1)] by the model transform. | 37 | // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and transform the texcoord = [(0,0),(1,1)] by the model transform. |
| 38 | static ShaderPointer getDrawViewportQuadTransformTexcoordVS(); | 38 | static ShaderPointer getDrawViewportQuadTransformTexcoordVS(); |
| 39 | 39 | ||
| 40 | // Shader draw the fed vertex position and transform it by the full model transform stack (Model, View, Proj). | ||
| 41 | // simply output the world pos and the clip pos to the next stage | ||
| 42 | static ShaderPointer getDrawVertexPositionVS(); | ||
| 43 | static ShaderPointer getDrawTransformVertexPositionVS(); | ||
| 44 | |||
| 45 | // PShader does nothing, no really nothing, but still needed for defining a program triggering rasterization | ||
| 46 | static ShaderPointer getDrawNadaPS(); | ||
| 47 | |||
| 48 | static ShaderPointer getDrawWhitePS(); | ||
| 40 | static ShaderPointer getDrawTexturePS(); | 49 | static ShaderPointer getDrawTexturePS(); |
| 41 | static ShaderPointer getDrawTextureOpaquePS(); | 50 | static ShaderPointer getDrawTextureOpaquePS(); |
| 42 | static ShaderPointer getDrawColoredTexturePS(); | 51 | static ShaderPointer getDrawColoredTexturePS(); |
| @@ -51,6 +60,12 @@ protected: · 8 unmodified lines | |||
| 51 | static ShaderPointer _drawTransformUnitQuadVS; | 60 | static ShaderPointer _drawTransformUnitQuadVS; |
| 52 | static ShaderPointer _drawTexcoordRectTransformUnitQuadVS; | 61 | static ShaderPointer _drawTexcoordRectTransformUnitQuadVS; |
| 53 | static ShaderPointer _drawViewportQuadTransformTexcoordVS; | 62 | static ShaderPointer _drawViewportQuadTransformTexcoordVS; |
| 63 | |||
| 64 | static ShaderPointer _drawVertexPositionVS; | ||
| 65 | static ShaderPointer _drawTransformVertexPositionVS; | ||
| 66 | |||
| 67 | static ShaderPointer _drawNadaPS; | ||
| 68 | static ShaderPointer _drawWhitePS; | ||
| 54 | static ShaderPointer _drawTexturePS; | 69 | static ShaderPointer _drawTexturePS; |
| 55 | static ShaderPointer _drawTextureOpaquePS; | 70 | static ShaderPointer _drawTextureOpaquePS; |
| 56 | static ShaderPointer _drawColoredTexturePS; | 71 | static ShaderPointer _drawColoredTexturePS; |
| @@ -70,8 +70,6 @@ public: · 69 unmodified lines | |||
| 70 | bool isSupported() const override { return true; } | 70 | bool isSupported() const override { return true; } |
| 71 | const QString getName() const override { return NAME; } | 71 | const QString getName() const override { return NAME; } |
| 72 | 72 | ||
| 73 | bool isHandController() const override { return false; } | ||
| 74 | |||
| 75 | void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } | 73 | void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } |
| 76 | void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; | 74 | void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; |
| 77 | 75 | ||
| @@ -39,8 +39,6 @@ public: · 38 unmodified lines | |||
| 39 | virtual bool isSupported() const override; | 39 | virtual bool isSupported() const override; |
| 40 | virtual const QString getName() const override { return NAME; } | 40 | virtual const QString getName() const override { return NAME; } |
| 41 | 41 | ||
| 42 | bool isHandController() const override { return false; } | ||
| 43 | |||
| 44 | virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } | 42 | virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } |
| 45 | virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; | 43 | virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; |
| 46 | 44 | ||
| @@ -211,6 +211,8 @@ namespace khronos { · 210 unmodified lines | |||
| 211 | 211 | ||
| 212 | template <uint32_t ALIGNMENT> | 212 | template <uint32_t ALIGNMENT> |
| 213 | inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) { | 213 | inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) { |
| 214 | enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) }; | ||
| 215 | static_assert(val, "template parameter ALIGNMENT must be a power of 2"); | ||
| 214 | // FIXME add static assert that ALIGNMENT is a power of 2 | 216 | // FIXME add static assert that ALIGNMENT is a power of 2 |
| 215 | static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1; | 217 | static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1; |
| 216 | return (value + ALIGNMENT_REMAINDER) / ALIGNMENT; | 218 | return (value + ALIGNMENT_REMAINDER) / ALIGNMENT; |
| @@ -229,7 +229,7 @@ namespace ktx { · 228 unmodified lines | |||
| 229 | } else { | 229 | } else { |
| 230 | Image::FaceBytes faceBytes(NUM_CUBEMAPFACES); | 230 | Image::FaceBytes faceBytes(NUM_CUBEMAPFACES); |
| 231 | auto faceSize = srcImages[l]._faceSize; | 231 | auto faceSize = srcImages[l]._faceSize; |
| 232 | for (int face = 0; face < NUM_CUBEMAPFACES; face++) { | 232 | for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) { |
| 233 | memcpy(currentPtr, srcImages[l]._faceBytes[face], faceSize); | 233 | memcpy(currentPtr, srcImages[l]._faceBytes[face], faceSize); |
| 234 | faceBytes[face] = currentPtr; | 234 | faceBytes[face] = currentPtr; |
| 235 | currentPtr += faceSize; | 235 | currentPtr += faceSize; |
| @@ -11,14 +11,28 @@ · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | #include "KTXCache.h" | 12 | #include "KTXCache.h" |
| 13 | 13 | ||
| 14 | #include <SettingHandle.h> | ||
| 14 | #include <ktx/KTX.h> | 15 | #include <ktx/KTX.h> |
| 15 | 16 | ||
| 16 | using File = cache::File; | 17 | using File = cache::File; |
| 17 | using FilePointer = cache::FilePointer; | 18 | using FilePointer = cache::FilePointer; |
| 18 | 19 | ||
| 20 | // Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible, | ||
| 21 | // this value should be incremented. This will force the KTX cache to be wiped | ||
| 22 | const int KTXCache::CURRENT_VERSION = 0x01; | ||
| 23 | const int KTXCache::INVALID_VERSION = 0x00; | ||
| 24 | const char* KTXCache::SETTING_VERSION_NAME = "hifi.ktx.cache_version"; | ||
| 25 | |||
| 19 | KTXCache::KTXCache(const std::string& dir, const std::string& ext) : | 26 | KTXCache::KTXCache(const std::string& dir, const std::string& ext) : |
| 20 | FileCache(dir, ext) { | 27 | FileCache(dir, ext) { |
| 21 | initialize(); | 28 | initialize(); |
| 29 | |||
| 30 | Setting::Handle<int> cacheVersionHandle(SETTING_VERSION_NAME, INVALID_VERSION); | ||
| 31 | auto cacheVersion = cacheVersionHandle.get(); | ||
| 32 | if (cacheVersion != CURRENT_VERSION) { | ||
| 33 | wipe(); | ||
| 34 | cacheVersionHandle.set(CURRENT_VERSION); | ||
| 35 | } | ||
| 22 | } | 36 | } |
| 23 | 37 | ||
| 24 | KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) { | 38 | KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) { |
| @@ -27,6 +27,12 @@ class KTXCache : public cache::FileCache { · 26 unmodified lines | |||
| 27 | Q_OBJECT | 27 | Q_OBJECT |
| 28 | 28 | ||
| 29 | public: | 29 | public: |
| 30 | // Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible, | ||
| 31 | // this value should be incremented. This will force the KTX cache to be wiped | ||
| 32 | static const int CURRENT_VERSION; | ||
| 33 | static const int INVALID_VERSION; | ||
| 34 | static const char* SETTING_VERSION_NAME; | ||
| 35 | |||
| 30 | KTXCache(const std::string& dir, const std::string& ext); | 36 | KTXCache(const std::string& dir, const std::string& ext); |
| 31 | 37 | ||
| 32 | KTXFilePointer writeFile(const char* data, Metadata&& metadata); | 38 | KTXFilePointer writeFile(const char* data, Metadata&& metadata); |
| @@ -241,6 +241,42 @@ void Mesh::forEach(std::function<void(glm::vec3)> vertexFunc, · 240 unmodified lines | |||
| 241 | } | 241 | } |
| 242 | } | 242 | } |
| 243 | 243 | ||
| 244 | MeshPointer Mesh::createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numIndices, const glm::vec3* vertices, const uint32_t* indices) { | ||
| 245 | MeshPointer mesh; | ||
| 246 | if (numVertices == 0) { return mesh; } | ||
| 247 | if (numIndices < 3) { return mesh; } | ||
| 248 | |||
| 249 | mesh = std::make_shared<Mesh>(); | ||
| 250 | |||
| 251 | // Vertex buffer | ||
| 252 | mesh->setVertexBuffer(gpu::BufferView(new gpu::Buffer(numVertices * sizeof(glm::vec3), (gpu::Byte*) vertices), gpu::Element::VEC3F_XYZ)); | ||
| 253 | |||
| 254 | // trim down the indices to shorts if possible | ||
| 255 | if (numIndices < std::numeric_limits<uint16_t>::max()) { | ||
| 256 | Indices16 shortIndicesVector; | ||
| 257 | int16_t* shortIndices = nullptr; | ||
| 258 | if (indices) { | ||
| 259 | shortIndicesVector.resize(numIndices); | ||
| 260 | for (uint32_t i = 0; i < numIndices; i++) { | ||
| 261 | shortIndicesVector[i] = indices[i]; | ||
| 262 | } | ||
| 263 | shortIndices = shortIndicesVector.data(); | ||
| 264 | } | ||
| 265 | |||
| 266 | mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint16_t), (gpu::Byte*) shortIndices), gpu::Element::INDEX_UINT16)); | ||
| 267 | } else { | ||
| 268 | |||
| 269 | mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint32_t), (gpu::Byte*) indices), gpu::Element::INDEX_INT32)); | ||
| 270 | } | ||
| 271 | |||
| 272 | |||
| 273 | std::vector<model::Mesh::Part> parts; | ||
| 274 | parts.push_back(model::Mesh::Part(0, numIndices, 0, model::Mesh::TRIANGLES)); | ||
| 275 | mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); | ||
| 276 | |||
| 277 | return mesh; | ||
| 278 | } | ||
| 279 | |||
| 244 | 280 | ||
| 245 | Geometry::Geometry() { | 281 | Geometry::Geometry() { |
| 246 | } | 282 | } |
| @@ -256,3 +292,5 @@ Geometry::~Geometry() { · 9 unmodified lines | |||
| 256 | void Geometry::setMesh(const MeshPointer& mesh) { | 292 | void Geometry::setMesh(const MeshPointer& mesh) { |
| 257 | _mesh = mesh; | 293 | _mesh = mesh; |
| 258 | } | 294 | } |
| 295 | |||
| 296 | |||
| @@ -65,6 +65,9 @@ public: · 64 unmodified lines | |||
| 65 | const gpu::BufferStream& getVertexStream() const { return _vertexStream; } | 65 | const gpu::BufferStream& getVertexStream() const { return _vertexStream; } |
| 66 | 66 | ||
| 67 | // Index Buffer | 67 | // Index Buffer |
| 68 | using Indices16 = std::vector<int16_t>; | ||
| 69 | using Indices32 = std::vector<int32_t>; | ||
| 70 | |||
| 68 | void setIndexBuffer(const BufferView& buffer); | 71 | void setIndexBuffer(const BufferView& buffer); |
| 69 | const BufferView& getIndexBuffer() const { return _indexBuffer; } | 72 | const BufferView& getIndexBuffer() const { return _indexBuffer; } |
| 70 | size_t getNumIndices() const { return _indexBuffer.getNumElements(); } | 73 | size_t getNumIndices() const { return _indexBuffer.getNumElements(); } |
| @@ -127,6 +130,9 @@ public: · 56 unmodified lines | |||
| 127 | std::function<void(glm::vec3)> normalFunc, | 130 | std::function<void(glm::vec3)> normalFunc, |
| 128 | std::function<void(uint32_t)> indexFunc); | 131 | std::function<void(uint32_t)> indexFunc); |
| 129 | 132 | ||
| 133 | |||
| 134 | static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr); | ||
| 135 | |||
| 130 | protected: | 136 | protected: |
| 131 | 137 | ||
| 132 | gpu::Stream::FormatPointer _vertexFormat; | 138 | gpu::Stream::FormatPointer _vertexFormat; |
| @@ -145,7 +145,7 @@ void Light::setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset) { · 144 unmodified lines | |||
| 145 | _ambientSchemaBuffer.edit().ambientSphere.assignPreset(preset); | 145 | _ambientSchemaBuffer.edit().ambientSphere.assignPreset(preset); |
| 146 | } | 146 | } |
| 147 | 147 | ||
| 148 | void Light::setAmbientMap(gpu::TexturePointer ambientMap) { | 148 | void Light::setAmbientMap(const gpu::TexturePointer& ambientMap) { |
| 149 | _ambientMap = ambientMap; | 149 | _ambientMap = ambientMap; |
| 150 | if (ambientMap) { | 150 | if (ambientMap) { |
| 151 | setAmbientMapNumMips(_ambientMap->getNumMips()); | 151 | setAmbientMapNumMips(_ambientMap->getNumMips()); |
| @@ -68,7 +68,8 @@ public: · 67 unmodified lines | |||
| 68 | 68 | ||
| 69 | 69 | ||
| 70 | enum Type { | 70 | enum Type { |
| 71 | SUN = 0, | 71 | AMBIENT = 0, |
| 72 | SUN, | ||
| 72 | POINT, | 73 | POINT, |
| 73 | SPOT, | 74 | SPOT, |
| 74 | 75 | ||
| @@ -112,7 +113,6 @@ public: · 37 unmodified lines | |||
| 112 | void setIntensity(float intensity); | 113 | void setIntensity(float intensity); |
| 113 | 114 | ||
| 114 | bool isRanged() const { return (getType() == POINT) || (getType() == SPOT); } | 115 | bool isRanged() const { return (getType() == POINT) || (getType() == SPOT); } |
| 115 | bool hasAmbient() const { return (getType() == SUN); } | ||
| 116 | 116 | ||
| 117 | // FalloffRradius is the physical radius of the light sphere through which energy shines, | 117 | // FalloffRradius is the physical radius of the light sphere through which energy shines, |
| 118 | // expressed in meters. It is used only to calculate the falloff curve of the light. | 118 | // expressed in meters. It is used only to calculate the falloff curve of the light. |
| @@ -143,7 +143,7 @@ public: · 24 unmodified lines | |||
| 143 | const gpu::SphericalHarmonics& getAmbientSphere() const { return _ambientSchemaBuffer->ambientSphere; } | 143 | const gpu::SphericalHarmonics& getAmbientSphere() const { return _ambientSchemaBuffer->ambientSphere; } |
| 144 | void setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset); | 144 | void setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset); |
| 145 | 145 | ||
| 146 | void setAmbientMap(gpu::TexturePointer ambientMap); | 146 | void setAmbientMap(const gpu::TexturePointer& ambientMap); |
| 147 | gpu::TexturePointer getAmbientMap() const { return _ambientMap; } | 147 | gpu::TexturePointer getAmbientMap() const { return _ambientMap; } |
| 148 | 148 | ||
| 149 | void setAmbientMapNumMips(uint16_t numMips); | 149 | void setAmbientMapNumMips(uint16_t numMips); |
| @@ -97,7 +97,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky · 96 unmodified lines | |||
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | auto skyState = std::make_shared<gpu::State>(); | 99 | auto skyState = std::make_shared<gpu::State>(); |
| 100 | skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | 100 | skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); |
| 101 | 101 | ||
| 102 | thePipeline = gpu::Pipeline::create(skyShader, skyState); | 102 | thePipeline = gpu::Pipeline::create(skyShader, skyState); |
| 103 | } | 103 | } |
| @@ -25,6 +25,8 @@ typedef glm::vec3 Color; · 24 unmodified lines | |||
| 25 | 25 | ||
| 26 | class Skybox { | 26 | class Skybox { |
| 27 | public: | 27 | public: |
| 28 | typedef gpu::BufferView UniformBufferView; | ||
| 29 | |||
| 28 | Skybox(); | 30 | Skybox(); |
| 29 | Skybox& operator= (const Skybox& skybox); | 31 | Skybox& operator= (const Skybox& skybox); |
| 30 | virtual ~Skybox() {}; | 32 | virtual ~Skybox() {}; |
| @@ -43,6 +45,8 @@ public: · 12 unmodified lines | |||
| 43 | 45 | ||
| 44 | static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); | 46 | static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); |
| 45 | 47 | ||
| 48 | const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } | ||
| 49 | |||
| 46 | protected: | 50 | protected: |
| 47 | static const int SKYBOX_SKYMAP_SLOT { 0 }; | 51 | static const int SKYBOX_SKYMAP_SLOT { 0 }; |
| 48 | static const int SKYBOX_CONSTANTS_SLOT { 0 }; | 52 | static const int SKYBOX_CONSTANTS_SLOT { 0 }; |
| @@ -1,5 +1,5 @@ | |||
| 1 | set(TARGET_NAME networking) | 1 | set(TARGET_NAME networking) |
| 2 | setup_hifi_library(Network) | 2 | setup_hifi_library(Network WebEngine) |
| 3 | link_hifi_libraries(shared) | 3 | link_hifi_libraries(shared) |
| 4 | 4 | ||
| 5 | target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") | 5 | target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") |
| @@ -45,7 +45,6 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) · 44 unmodified lines | |||
| 45 | Q_DECLARE_METATYPE(JSONCallbackParameters) | 45 | Q_DECLARE_METATYPE(JSONCallbackParameters) |
| 46 | 46 | ||
| 47 | const QString ACCOUNTS_GROUP = "accounts"; | 47 | const QString ACCOUNTS_GROUP = "accounts"; |
| 48 | static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); | ||
| 49 | 48 | ||
| 50 | JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, | 49 | JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, |
| 51 | QObject* errorCallbackReceiver, const QString& errorCallbackMethod, | 50 | QObject* errorCallbackReceiver, const QString& errorCallbackMethod, |
| @@ -201,6 +200,13 @@ void AccountManager::setAuthURL(const QUrl& authURL) { · 149 unmodified lines | |||
| 201 | } | 200 | } |
| 202 | } | 201 | } |
| 203 | 202 | ||
| 203 | void AccountManager::setSessionID(const QUuid& sessionID) { | ||
| 204 | if (_sessionID != sessionID) { | ||
| 205 | qCDebug(networking) << "Metaverse session ID changed to" << uuidStringWithoutCurlyBraces(sessionID); | ||
| 206 | _sessionID = sessionID; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 204 | void AccountManager::sendRequest(const QString& path, | 210 | void AccountManager::sendRequest(const QString& path, |
| 205 | AccountManagerAuth::Type authType, | 211 | AccountManagerAuth::Type authType, |
| 206 | QNetworkAccessManager::Operation operation, | 212 | QNetworkAccessManager::Operation operation, |
| @@ -52,6 +52,7 @@ namespace AccountManagerAuth { · 51 unmodified lines | |||
| 52 | Q_DECLARE_METATYPE(AccountManagerAuth::Type); | 52 | Q_DECLARE_METATYPE(AccountManagerAuth::Type); |
| 53 | 53 | ||
| 54 | const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; | 54 | const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; |
| 55 | const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); | ||
| 55 | 56 | ||
| 56 | using UserAgentGetter = std::function<QString()>; | 57 | using UserAgentGetter = std::function<QString()>; |
| 57 | 58 | ||
| @@ -90,7 +91,7 @@ public: · 32 unmodified lines | |||
| 90 | static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); | 91 | static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); |
| 91 | 92 | ||
| 92 | QUuid getSessionID() const { return _sessionID; } | 93 | QUuid getSessionID() const { return _sessionID; } |
| 93 | void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } | 94 | void setSessionID(const QUuid& sessionID); |
| 94 | 95 | ||
| 95 | void setTemporaryDomain(const QUuid& domainID, const QString& key); | 96 | void setTemporaryDomain(const QUuid& domainID, const QString& key); |
| 96 | const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } | 97 | const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } |
| @@ -15,11 +15,13 @@ · 14 unmodified lines | |||
| 15 | 15 | ||
| 16 | #include <QtCore/QThread> | 16 | #include <QtCore/QThread> |
| 17 | 17 | ||
| 18 | #include <StatTracker.h> | ||
| 19 | #include <Trace.h> | ||
| 20 | |||
| 18 | #include "AssetClient.h" | 21 | #include "AssetClient.h" |
| 19 | #include "NetworkLogging.h" | 22 | #include "NetworkLogging.h" |
| 20 | #include "NodeList.h" | 23 | #include "NodeList.h" |
| 21 | #include "ResourceCache.h" | 24 | #include "ResourceCache.h" |
| 22 | #include <Trace.h> | ||
| 23 | 25 | ||
| 24 | static int requestID = 0; | 26 | static int requestID = 0; |
| 25 | 27 | ||
| @@ -62,9 +64,12 @@ void AssetRequest::start() { · 36 unmodified lines | |||
| 62 | _data = loadFromCache(getUrl()); | 64 | _data = loadFromCache(getUrl()); |
| 63 | if (!_data.isNull()) { | 65 | if (!_data.isNull()) { |
| 64 | _error = NoError; | 66 | _error = NoError; |
| 65 | 67 | ||
| 68 | _loadedFromCache = true; | ||
| 69 | |||
| 66 | _state = Finished; | 70 | _state = Finished; |
| 67 | emit finished(this); | 71 | emit finished(this); |
| 72 | |||
| 68 | return; | 73 | return; |
| 69 | } | 74 | } |
| 70 | 75 | ||
| @@ -52,6 +52,8 @@ public: · 51 unmodified lines | |||
| 52 | QUrl getUrl() const { return ::getATPUrl(_hash); } | 52 | QUrl getUrl() const { return ::getATPUrl(_hash); } |
| 53 | QString getHash() const { return _hash; } | 53 | QString getHash() const { return _hash; } |
| 54 | 54 | ||
| 55 | bool loadedFromCache() const { return _loadedFromCache; } | ||
| 56 | |||
| 55 | signals: | 57 | signals: |
| 56 | void finished(AssetRequest* thisRequest); | 58 | void finished(AssetRequest* thisRequest); |
| 57 | void progress(qint64 totalReceived, qint64 total); | 59 | void progress(qint64 totalReceived, qint64 total); |
| @@ -66,6 +68,7 @@ private: · 8 unmodified lines | |||
| 66 | int _numPendingRequests { 0 }; | 68 | int _numPendingRequests { 0 }; |
| 67 | MessageID _assetRequestID { INVALID_MESSAGE_ID }; | 69 | MessageID _assetRequestID { INVALID_MESSAGE_ID }; |
| 68 | const ByteRange _byteRange; | 70 | const ByteRange _byteRange; |
| 71 | bool _loadedFromCache { false }; | ||
| 69 | }; | 72 | }; |
| 70 | 73 | ||
| 71 | #endif | 74 | #endif |
| @@ -13,12 +13,14 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | #include <QtCore/QLoggingCategory> | 14 | #include <QtCore/QLoggingCategory> |
| 15 | 15 | ||
| 16 | #include <Trace.h> | ||
| 17 | #include <Profile.h> | ||
| 18 | #include <StatTracker.h> | ||
| 19 | |||
| 16 | #include "AssetClient.h" | 20 | #include "AssetClient.h" |
| 17 | #include "AssetUtils.h" | 21 | #include "AssetUtils.h" |
| 18 | #include "MappingRequest.h" | 22 | #include "MappingRequest.h" |
| 19 | #include "NetworkLogging.h" | 23 | #include "NetworkLogging.h" |
| 20 | #include <Trace.h> | ||
| 21 | #include <Profile.h> | ||
| 22 | 24 | ||
| 23 | static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5; | 25 | static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5; |
| 24 | 26 | ||
| @@ -48,6 +50,8 @@ bool AssetResourceRequest::urlIsAssetHash(const QUrl& url) { · 23 unmodified lines | |||
| 48 | } | 50 | } |
| 49 | 51 | ||
| 50 | void AssetResourceRequest::doSend() { | 52 | void AssetResourceRequest::doSend() { |
| 53 | DependencyManager::get<StatTracker>()->incrementStat(STAT_ATP_REQUEST_STARTED); | ||
| 54 | |||
| 51 | // We'll either have a hash or an ATP path to a file (that maps to a hash) | 55 | // We'll either have a hash or an ATP path to a file (that maps to a hash) |
| 52 | if (urlIsAssetHash(_url)) { | 56 | if (urlIsAssetHash(_url)) { |
| 53 | // We've detected that this is a hash - simply use AssetClient to request that asset | 57 | // We've detected that this is a hash - simply use AssetClient to request that asset |
| @@ -65,11 +69,16 @@ void AssetResourceRequest::doSend() { · 11 unmodified lines | |||
| 65 | } | 69 | } |
| 66 | 70 | ||
| 67 | void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { | 71 | void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { |
| 72 | auto statTracker = DependencyManager::get<StatTracker>(); | ||
| 73 | statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_STARTED); | ||
| 74 | |||
| 68 | auto assetClient = DependencyManager::get<AssetClient>(); | 75 | auto assetClient = DependencyManager::get<AssetClient>(); |
| 69 | _assetMappingRequest = assetClient->createGetMappingRequest(path); | 76 | _assetMappingRequest = assetClient->createGetMappingRequest(path); |
| 70 | 77 | ||
| 71 | // make sure we'll hear about the result of the get mapping request | 78 | // make sure we'll hear about the result of the get mapping request |
| 72 | connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){ | 79 | connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){ |
| 80 | auto statTracker = DependencyManager::get<StatTracker>(); | ||
| 81 | |||
| 73 | Q_ASSERT(_state == InProgress); | 82 | Q_ASSERT(_state == InProgress); |
| 74 | Q_ASSERT(request == _assetMappingRequest); | 83 | Q_ASSERT(request == _assetMappingRequest); |
| 75 | 84 | ||
| @@ -80,6 +89,8 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { · 4 unmodified lines | |||
| 80 | 89 | ||
| 81 | requestHash(request->getHash()); | 90 | requestHash(request->getHash()); |
| 82 | 91 | ||
| 92 | statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_SUCCESS); | ||
| 93 | |||
| 83 | break; | 94 | break; |
| 84 | default: { | 95 | default: { |
| 85 | switch (request->getError()) { | 96 | switch (request->getError()) { |
| @@ -100,6 +111,9 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { · 14 unmodified lines | |||
| 100 | _state = Finished; | 111 | _state = Finished; |
| 101 | emit finished(); | 112 | emit finished(); |
| 102 | 113 | ||
| 114 | statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_FAILED); | ||
| 115 | statTracker->incrementStat(STAT_ATP_REQUEST_FAILED); | ||
| 116 | |||
| 103 | break; | 117 | break; |
| 104 | } | 118 | } |
| 105 | } | 119 | } |
| @@ -140,10 +154,26 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) { · 34 unmodified lines | |||
| 140 | _result = Error; | 154 | _result = Error; |
| 141 | break; | 155 | break; |
| 142 | } | 156 | } |
| 143 | 157 | ||
| 158 | auto statTracker = DependencyManager::get<StatTracker>(); | ||
| 159 | |||
| 160 | if (_assetRequest->loadedFromCache()) { | ||
| 161 | _loadedFromCache = true; | ||
| 162 | } | ||
| 163 | |||
| 144 | _state = Finished; | 164 | _state = Finished; |
| 145 | emit finished(); | 165 | emit finished(); |
| 146 | 166 | ||
| 167 | if (_result == Success) { | ||
| 168 | statTracker->incrementStat(STAT_ATP_REQUEST_SUCCESS); | ||
| 169 | |||
| 170 | if (loadedFromCache()) { | ||
| 171 | statTracker->incrementStat(STAT_ATP_REQUEST_CACHE); | ||
| 172 | } | ||
| 173 | } else { | ||
| 174 | statTracker->incrementStat(STAT_ATP_REQUEST_FAILED); | ||
| 175 | } | ||
| 176 | |||
| 147 | _assetRequest->deleteLater(); | 177 | _assetRequest->deleteLater(); |
| 148 | _assetRequest = nullptr; | 178 | _assetRequest = nullptr; |
| 149 | }); | 179 | }); |
| @@ -11,52 +11,81 @@ · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | #include "FileCache.h" | 12 | #include "FileCache.h" |
| 13 | 13 | ||
| 14 | #include <cstdio> | 14 | |
| 15 | #include <cassert> | ||
| 16 | #include <fstream> | ||
| 17 | #include <unordered_set> | 15 | #include <unordered_set> |
| 16 | #include <queue> | ||
| 17 | #include <cassert> | ||
| 18 | 18 | ||
| 19 | #include <QDir> | 19 | #include <QtCore/QDateTime> |
| 20 | #include <QSaveFile> | 20 | #include <QtCore/QDir> |
| 21 | #include <QtCore/QSaveFile> | ||
| 22 | #include <QtCore/QStorageInfo> | ||
| 21 | 23 | ||
| 22 | #include <PathUtils.h> | 24 | #include <PathUtils.h> |
| 25 | #include <NumericalConstants.h> | ||
| 23 | 26 | ||
| 24 | Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg) | 27 | #ifdef Q_OS_WIN |
| 28 | #include <sys/utime.h> | ||
| 29 | #else | ||
| 30 | #include <utime.h> | ||
| 31 | #endif | ||
| 25 | 32 | ||
| 33 | #ifdef NDEBUG | ||
| 34 | Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg) | ||
| 35 | #else | ||
| 36 | Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache") | ||
| 37 | #endif | ||
| 26 | using namespace cache; | 38 | using namespace cache; |
| 27 | 39 | ||
| 28 | static const std::string MANIFEST_NAME = "manifest"; | 40 | static const char DIR_SEP = '/'; |
| 41 | static const char EXT_SEP = '.'; | ||
| 42 | |||
| 43 | const size_t FileCache::DEFAULT_MAX_SIZE { GB_TO_BYTES(5) }; | ||
| 44 | const size_t FileCache::MAX_MAX_SIZE { GB_TO_BYTES(100) }; | ||
| 45 | const size_t FileCache::DEFAULT_MIN_FREE_STORAGE_SPACE { GB_TO_BYTES(1) }; | ||
| 46 | |||
| 29 | 47 | ||
| 30 | static const size_t BYTES_PER_MEGABYTES = 1024 * 1024; | 48 | std::string getCacheName(const std::string& dirname_str) { |
| 31 | static const size_t BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES; | 49 | QString dirname { dirname_str.c_str() }; |
| 32 | const size_t FileCache::DEFAULT_UNUSED_MAX_SIZE = 5 * BYTES_PER_GIGABYTES; // 5GB | 50 | QDir dir(dirname); |
| 33 | const size_t FileCache::MAX_UNUSED_MAX_SIZE = 100 * BYTES_PER_GIGABYTES; // 100GB | 51 | if (!dir.isAbsolute()) { |
| 34 | const size_t FileCache::DEFAULT_OFFLINE_MAX_SIZE = 2 * BYTES_PER_GIGABYTES; // 2GB | 52 | return dirname_str; |
| 53 | } | ||
| 54 | return dir.dirName().toStdString(); | ||
| 55 | } | ||
| 35 | 56 | ||
| 36 | void FileCache::setUnusedFileCacheSize(size_t unusedFilesMaxSize) { | 57 | std::string getCachePath(const std::string& dirname_str) { |
| 37 | _unusedFilesMaxSize = std::min(unusedFilesMaxSize, MAX_UNUSED_MAX_SIZE); | 58 | QString dirname { dirname_str.c_str() }; |
| 38 | reserve(0); | 59 | QDir dir(dirname); |
| 60 | if (dir.isAbsolute()) { | ||
| 61 | return dirname_str; | ||
| 62 | } | ||
| 63 | return PathUtils::getAppLocalDataFilePath(dirname).toStdString(); | ||
| 64 | } | ||
| 65 | |||
| 66 | void FileCache::setMinFreeSize(size_t size) { | ||
| 67 | _minFreeSpaceSize = size; | ||
| 68 | clean(); | ||
| 39 | emit dirty(); | 69 | emit dirty(); |
| 40 | } | 70 | } |
| 41 | 71 | ||
| 42 | void FileCache::setOfflineFileCacheSize(size_t offlineFilesMaxSize) { | 72 | void FileCache::setMaxSize(size_t maxSize) { |
| 43 | _offlineFilesMaxSize = std::min(offlineFilesMaxSize, MAX_UNUSED_MAX_SIZE); | 73 | _maxSize = std::min(maxSize, MAX_MAX_SIZE); |
| 74 | clean(); | ||
| 75 | emit dirty(); | ||
| 44 | } | 76 | } |
| 45 | 77 | ||
| 46 | FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) : | 78 | FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) : |
| 47 | QObject(parent), | 79 | QObject(parent), |
| 48 | _ext(ext), | 80 | _ext(ext), |
| 49 | _dirname(dirname), | 81 | _dirname(getCacheName(dirname)), |
| 50 | _dirpath(PathUtils::getAppLocalDataFilePath(dirname.c_str()).toStdString()) {} | 82 | _dirpath(getCachePath(dirname)) { |
| 83 | } | ||
| 51 | 84 | ||
| 52 | FileCache::~FileCache() { | 85 | FileCache::~FileCache() { |
| 53 | clear(); | 86 | clear(); |
| 54 | } | 87 | } |
| 55 | 88 | ||
| 56 | void fileDeleter(File* file) { | ||
| 57 | file->deleter(); | ||
| 58 | } | ||
| 59 | |||
| 60 | void FileCache::initialize() { | 89 | void FileCache::initialize() { |
| 61 | QDir dir(_dirpath.c_str()); | 90 | QDir dir(_dirpath.c_str()); |
| 62 | 91 | ||
| @@ -84,7 +113,7 @@ void FileCache::initialize() { · 21 unmodified lines | |||
| 84 | } | 113 | } |
| 85 | 114 | ||
| 86 | FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) { | 115 | FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) { |
| 87 | FilePointer file(createFile(std::move(metadata), filepath).release(), &fileDeleter); | 116 | FilePointer file(createFile(std::move(metadata), filepath).release(), std::mem_fn(&File::deleter)); |
| 88 | if (file) { | 117 | if (file) { |
| 89 | _numTotalFiles += 1; | 118 | _numTotalFiles += 1; |
| 90 | _totalFilesSize += file->getLength(); | 119 | _totalFilesSize += file->getLength(); |
| @@ -141,6 +170,7 @@ FilePointer FileCache::getFile(const Key& key) { · 50 unmodified lines | |||
| 141 | if (it != _files.cend()) { | 170 | if (it != _files.cend()) { |
| 142 | file = it->second.lock(); | 171 | file = it->second.lock(); |
| 143 | if (file) { | 172 | if (file) { |
| 173 | file->touch(); | ||
| 144 | // if it exists, it is active - remove it from the cache | 174 | // if it exists, it is active - remove it from the cache |
| 145 | removeUnusedFile(file); | 175 | removeUnusedFile(file); |
| 146 | qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str()); | 176 | qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str()); |
| @@ -155,82 +185,127 @@ FilePointer FileCache::getFile(const Key& key) { · 8 unmodified lines | |||
| 155 | } | 185 | } |
| 156 | 186 | ||
| 157 | std::string FileCache::getFilepath(const Key& key) { | 187 | std::string FileCache::getFilepath(const Key& key) { |
| 158 | return _dirpath + '/' + key + '.' + _ext; | 188 | return _dirpath + DIR_SEP + key + EXT_SEP + _ext; |
| 159 | } | 189 | } |
| 160 | 190 | ||
| 161 | void FileCache::addUnusedFile(const FilePointer file) { | 191 | void FileCache::addUnusedFile(const FilePointer& file) { |
| 162 | { | 192 | { |
| 163 | Lock lock(_filesMutex); | 193 | Lock lock(_filesMutex); |
| 164 | _files[file->getKey()] = file; | 194 | _files[file->getKey()] = file; |
| 165 | } | 195 | } |
| 166 | 196 | ||
| 167 | reserve(file->getLength()); | ||
| 168 | file->_LRUKey = ++_lastLRUKey; | ||
| 169 | |||
| 170 | { | 197 | { |
| 171 | Lock lock(_unusedFilesMutex); | 198 | Lock lock(_unusedFilesMutex); |
| 172 | _unusedFiles.insert({ file->_LRUKey, file }); | 199 | _unusedFiles.insert(file); |
| 173 | _numUnusedFiles += 1; | 200 | _numUnusedFiles += 1; |
| 174 | _unusedFilesSize += file->getLength(); | 201 | _unusedFilesSize += file->getLength(); |
| 175 | } | 202 | } |
| 203 | clean(); | ||
| 176 | 204 | ||
| 177 | emit dirty(); | 205 | emit dirty(); |
| 178 | } | 206 | } |
| 179 | 207 | ||
| 180 | void FileCache::removeUnusedFile(const FilePointer file) { | 208 | void FileCache::removeUnusedFile(const FilePointer& file) { |
| 181 | Lock lock(_unusedFilesMutex); | 209 | Lock lock(_unusedFilesMutex); |
| 182 | const auto it = _unusedFiles.find(file->_LRUKey); | 210 | if (_unusedFiles.erase(file)) { |
| 183 | if (it != _unusedFiles.cend()) { | ||
| 184 | _unusedFiles.erase(it); | ||
| 185 | _numUnusedFiles -= 1; | 211 | _numUnusedFiles -= 1; |
| 186 | _unusedFilesSize -= file->getLength(); | 212 | _unusedFilesSize -= file->getLength(); |
| 187 | } | 213 | } |
| 188 | } | 214 | } |
| 189 | 215 | ||
| 190 | void FileCache::reserve(size_t length) { | 216 | size_t FileCache::getOverbudgetAmount() const { |
| 191 | Lock unusedLock(_unusedFilesMutex); | 217 | size_t result = 0; |
| 192 | while (!_unusedFiles.empty() && | 218 | |
| 193 | _unusedFilesSize + length > _unusedFilesMaxSize) { | 219 | size_t currentFreeSpace = QStorageInfo(_dirpath.c_str()).bytesFree(); |
| 194 | auto it = _unusedFiles.begin(); | 220 | if (_minFreeSpaceSize > currentFreeSpace) { |
| 195 | auto file = it->second; | 221 | result = _minFreeSpaceSize - currentFreeSpace; |
| 196 | auto length = file->getLength(); | 222 | } |
| 197 | 223 | ||
| 198 | unusedLock.unlock(); | 224 | if (_totalFilesSize > _maxSize) { |
| 199 | { | 225 | result = std::max(_totalFilesSize - _maxSize, result); |
| 200 | file->_cache = nullptr; | 226 | } |
| 201 | Lock lock(_filesMutex); | 227 | |
| 202 | _files.erase(file->getKey()); | 228 | return result; |
| 229 | } | ||
| 230 | |||
| 231 | namespace cache { | ||
| 232 | struct FilePointerComparator { | ||
| 233 | bool operator()(const FilePointer& a, const FilePointer& b) { | ||
| 234 | return a->_modified > b->_modified; | ||
| 203 | } | 235 | } |
| 204 | unusedLock.lock(); | 236 | }; |
| 237 | } | ||
| 205 | 238 | ||
| 206 | _unusedFiles.erase(it); | 239 | void FileCache::eject(const FilePointer& file) { |
| 207 | _numTotalFiles -= 1; | 240 | file->_cache = nullptr; |
| 208 | _numUnusedFiles -= 1; | 241 | const auto& length = file->getLength(); |
| 209 | _totalFilesSize -= length; | 242 | const auto& key = file->getKey(); |
| 210 | _unusedFilesSize -= length; | 243 | |
| 244 | { | ||
| 245 | Lock lock(_filesMutex); | ||
| 246 | if (0 != _files.erase(key)) { | ||
| 247 | _numTotalFiles -= 1; | ||
| 248 | _totalFilesSize -= length; | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | { | ||
| 253 | Lock unusedLock(_unusedFilesMutex); | ||
| 254 | if (0 != _unusedFiles.erase(file)) { | ||
| 255 | _numUnusedFiles -= 1; | ||
| 256 | _unusedFilesSize -= length; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | void FileCache::clean() { | ||
| 262 | size_t overbudgetAmount = getOverbudgetAmount(); | ||
| 263 | |||
| 264 | // Avoid sorting the unused files by LRU if we're not over budget / under free space | ||
| 265 | if (0 == overbudgetAmount) { | ||
| 266 | return; | ||
| 267 | } | ||
| 268 | |||
| 269 | Lock unusedLock(_unusedFilesMutex); | ||
| 270 | using Queue = std::priority_queue<FilePointer, std::vector<FilePointer>, FilePointerComparator>; | ||
| 271 | Queue queue; | ||
| 272 | for (const auto& file : _unusedFiles) { | ||
| 273 | queue.push(file); | ||
| 274 | } | ||
| 275 | |||
| 276 | while (!queue.empty() && overbudgetAmount > 0) { | ||
| 277 | auto file = queue.top(); | ||
| 278 | queue.pop(); | ||
| 279 | eject(file); | ||
| 280 | auto length = file->getLength(); | ||
| 281 | overbudgetAmount -= std::min(length, overbudgetAmount); | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | void FileCache::wipe() { | ||
| 286 | Lock unusedFilesLock(_unusedFilesMutex); | ||
| 287 | while (!_unusedFiles.empty()) { | ||
| 288 | eject(*_unusedFiles.begin()); | ||
| 211 | } | 289 | } |
| 212 | } | 290 | } |
| 213 | 291 | ||
| 214 | void FileCache::clear() { | 292 | void FileCache::clear() { |
| 293 | // Eliminate any overbudget files | ||
| 294 | clean(); | ||
| 295 | |||
| 296 | // Mark everything remaining as persisted | ||
| 215 | Lock unusedFilesLock(_unusedFilesMutex); | 297 | Lock unusedFilesLock(_unusedFilesMutex); |
| 216 | for (const auto& pair : _unusedFiles) { | 298 | for (auto& file : _unusedFiles) { |
| 217 | auto& file = pair.second; | 299 | file->_shouldPersist = true; |
| 218 | file->_cache = nullptr; | 300 | file->_cache = nullptr; |
| 219 | 301 | qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str()); | |
| 220 | if (_totalFilesSize > _offlineFilesMaxSize) { | ||
| 221 | _totalFilesSize -= file->getLength(); | ||
| 222 | } else { | ||
| 223 | file->_shouldPersist = true; | ||
| 224 | qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str()); | ||
| 225 | } | ||
| 226 | } | 302 | } |
| 227 | _unusedFiles.clear(); | 303 | _unusedFiles.clear(); |
| 228 | } | 304 | } |
| 229 | 305 | ||
| 230 | void File::deleter() { | 306 | void File::deleter() { |
| 231 | if (_cache) { | 307 | if (_cache) { |
| 232 | FilePointer self(this, &fileDeleter); | 308 | _cache->addUnusedFile(FilePointer(this, std::mem_fn(&File::deleter))); |
| 233 | _cache->addUnusedFile(self); | ||
| 234 | } else { | 309 | } else { |
| 235 | deleteLater(); | 310 | deleteLater(); |
| 236 | } | 311 | } |
| @@ -239,7 +314,9 @@ void File::deleter() { · 2 unmodified lines | |||
| 239 | File::File(Metadata&& metadata, const std::string& filepath) : | 314 | File::File(Metadata&& metadata, const std::string& filepath) : |
| 240 | _key(std::move(metadata.key)), | 315 | _key(std::move(metadata.key)), |
| 241 | _length(metadata.length), | 316 | _length(metadata.length), |
| 242 | _filepath(filepath) {} | 317 | _filepath(filepath), |
| 318 | _modified(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch()) { | ||
| 319 | } | ||
| 243 | 320 | ||
| 244 | File::~File() { | 321 | File::~File() { |
| 245 | QFile file(getFilepath().c_str()); | 322 | QFile file(getFilepath().c_str()); |
| @@ -248,3 +325,8 @@ File::~File() { · 2 unmodified lines | |||
| 248 | file.remove(); | 325 | file.remove(); |
| 249 | } | 326 | } |
| 250 | } | 327 | } |
| 328 | |||
| 329 | void File::touch() { | ||
| 330 | utime(_filepath.c_str(), nullptr); | ||
| 331 | _modified = std::max<int64_t>(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch(), _modified); | ||
| 332 | } | ||
| @@ -14,6 +14,7 @@ · 13 unmodified lines | |||
| 14 | #include <memory> | 14 | #include <memory> |
| 15 | #include <cstddef> | 15 | #include <cstddef> |
| 16 | #include <map> | 16 | #include <map> |
| 17 | #include <unordered_set> | ||
| 17 | #include <mutex> | 18 | #include <mutex> |
| 18 | #include <string> | 19 | #include <string> |
| 19 | #include <unordered_map> | 20 | #include <unordered_map> |
| @@ -35,24 +36,31 @@ class FileCache : public QObject { · 15 unmodified lines | |||
| 35 | Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty) | 36 | Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty) |
| 36 | Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty) | 37 | Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty) |
| 37 | 38 | ||
| 38 | static const size_t DEFAULT_UNUSED_MAX_SIZE; | 39 | static const size_t DEFAULT_MAX_SIZE; |
| 39 | static const size_t MAX_UNUSED_MAX_SIZE; | 40 | static const size_t MAX_MAX_SIZE; |
| 40 | static const size_t DEFAULT_OFFLINE_MAX_SIZE; | 41 | static const size_t DEFAULT_MIN_FREE_STORAGE_SPACE; |
| 41 | 42 | ||
| 42 | public: | 43 | public: |
| 44 | // You can initialize the FileCache with a directory name (ex.: "temp_jpgs") that will be relative to the application local data, OR with a full path | ||
| 45 | // The file cache will ignore any file that doesn't match the extension provided | ||
| 46 | FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr); | ||
| 47 | virtual ~FileCache(); | ||
| 48 | |||
| 49 | // Remove all unlocked items from the cache | ||
| 50 | void wipe(); | ||
| 51 | |||
| 43 | size_t getNumTotalFiles() const { return _numTotalFiles; } | 52 | size_t getNumTotalFiles() const { return _numTotalFiles; } |
| 44 | size_t getNumCachedFiles() const { return _numUnusedFiles; } | 53 | size_t getNumCachedFiles() const { return _numUnusedFiles; } |
| 45 | size_t getSizeTotalFiles() const { return _totalFilesSize; } | 54 | size_t getSizeTotalFiles() const { return _totalFilesSize; } |
| 46 | size_t getSizeCachedFiles() const { return _unusedFilesSize; } | 55 | size_t getSizeCachedFiles() const { return _unusedFilesSize; } |
| 47 | 56 | ||
| 48 | void setUnusedFileCacheSize(size_t unusedFilesMaxSize); | 57 | // Set the maximum amount of disk space to use on disk |
| 49 | size_t getUnusedFileCacheSize() const { return _unusedFilesSize; } | 58 | void setMaxSize(size_t maxCacheSize); |
| 50 | 59 | ||
| 51 | void setOfflineFileCacheSize(size_t offlineFilesMaxSize); | 60 | // Set the minumum amount of free disk space to retain. This supercedes the max size, |
| 52 | 61 | // so if the cache is consuming all but 500 MB of the drive, unused entries will be ejected | |
| 53 | // initialize FileCache with a directory name (not a path, ex.: "temp_jpgs") and an ext (ex.: "jpg") | 62 | // to free up more space, regardless of the cache max size |
| 54 | FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr); | 63 | void setMinFreeSize(size_t size); |
| 55 | virtual ~FileCache(); | ||
| 56 | 64 | ||
| 57 | using Key = std::string; | 65 | using Key = std::string; |
| 58 | struct Metadata { | 66 | struct Metadata { |
| @@ -76,10 +84,11 @@ public: · 17 unmodified lines | |||
| 76 | signals: | 84 | signals: |
| 77 | void dirty(); | 85 | void dirty(); |
| 78 | 86 | ||
| 79 | protected: | 87 | public: |
| 80 | /// must be called after construction to create the cache on the fs and restore persisted files | 88 | /// must be called after construction to create the cache on the fs and restore persisted files |
| 81 | void initialize(); | 89 | void initialize(); |
| 82 | 90 | ||
| 91 | // Add file to the cache and return the cache entry. | ||
| 83 | FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false); | 92 | FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false); |
| 84 | FilePointer getFile(const Key& key); | 93 | FilePointer getFile(const Key& key); |
| 85 | 94 | ||
| @@ -89,17 +98,28 @@ protected: · 3 unmodified lines | |||
| 89 | private: | 98 | private: |
| 90 | using Mutex = std::recursive_mutex; | 99 | using Mutex = std::recursive_mutex; |
| 91 | using Lock = std::unique_lock<Mutex>; | 100 | using Lock = std::unique_lock<Mutex>; |
| 101 | using Map = std::unordered_map<Key, std::weak_ptr<File>>; | ||
| 102 | using Set = std::unordered_set<FilePointer>; | ||
| 103 | using KeySet = std::unordered_set<Key>; | ||
| 92 | 104 | ||
| 93 | friend class File; | 105 | friend class File; |
| 94 | 106 | ||
| 95 | std::string getFilepath(const Key& key); | 107 | std::string getFilepath(const Key& key); |
| 96 | 108 | ||
| 97 | FilePointer addFile(Metadata&& metadata, const std::string& filepath); | 109 | FilePointer addFile(Metadata&& metadata, const std::string& filepath); |
| 98 | void addUnusedFile(const FilePointer file); | 110 | void addUnusedFile(const FilePointer& file); |
| 99 | void removeUnusedFile(const FilePointer file); | 111 | void removeUnusedFile(const FilePointer& file); |
| 100 | void reserve(size_t length); | 112 | void clean(); |
| 101 | void clear(); | 113 | void clear(); |
| 114 | // Remove a file from the cache | ||
| 115 | void eject(const FilePointer& file); | ||
| 102 | 116 | ||
| 117 | size_t getOverbudgetAmount() const; | ||
| 118 | |||
| 119 | // FIXME it might be desirable to have the min free space variable be static so it can be | ||
| 120 | // shared among multiple instances of FileCache | ||
| 121 | std::atomic<size_t> _minFreeSpaceSize { DEFAULT_MIN_FREE_STORAGE_SPACE }; | ||
| 122 | std::atomic<size_t> _maxSize { DEFAULT_MAX_SIZE }; | ||
| 103 | std::atomic<size_t> _numTotalFiles { 0 }; | 123 | std::atomic<size_t> _numTotalFiles { 0 }; |
| 104 | std::atomic<size_t> _numUnusedFiles { 0 }; | 124 | std::atomic<size_t> _numUnusedFiles { 0 }; |
| 105 | std::atomic<size_t> _totalFilesSize { 0 }; | 125 | std::atomic<size_t> _totalFilesSize { 0 }; |
| @@ -110,15 +130,11 @@ private: · 4 unmodified lines | |||
| 110 | std::string _dirpath; | 130 | std::string _dirpath; |
| 111 | bool _initialized { false }; | 131 | bool _initialized { false }; |
| 112 | 132 | ||
| 113 | std::unordered_map<Key, std::weak_ptr<File>> _files; | 133 | Map _files; |
| 114 | Mutex _filesMutex; | 134 | Mutex _filesMutex; |
| 115 | 135 | ||
| 116 | std::map<int, FilePointer> _unusedFiles; | 136 | Set _unusedFiles; |
| 117 | Mutex _unusedFilesMutex; | 137 | Mutex _unusedFilesMutex; |
| 118 | size_t _unusedFilesMaxSize { DEFAULT_UNUSED_MAX_SIZE }; | ||
| 119 | int _lastLRUKey { 0 }; | ||
| 120 | |||
| 121 | size_t _offlineFilesMaxSize { DEFAULT_OFFLINE_MAX_SIZE }; | ||
| 122 | }; | 138 | }; |
| 123 | 139 | ||
| 124 | class File : public QObject { | 140 | class File : public QObject { |
| @@ -128,8 +144,8 @@ public: · 3 unmodified lines | |||
| 128 | using Key = FileCache::Key; | 144 | using Key = FileCache::Key; |
| 129 | using Metadata = FileCache::Metadata; | 145 | using Metadata = FileCache::Metadata; |
| 130 | 146 | ||
| 131 | Key getKey() const { return _key; } | 147 | const Key& getKey() const { return _key; } |
| 132 | size_t getLength() const { return _length; } | 148 | const size_t& getLength() const { return _length; } |
| 133 | std::string getFilepath() const { return _filepath; } | 149 | std::string getFilepath() const { return _filepath; } |
| 134 | 150 | ||
| 135 | virtual ~File(); | 151 | virtual ~File(); |
| @@ -142,13 +158,15 @@ protected: · 6 unmodified lines | |||
| 142 | 158 | ||
| 143 | private: | 159 | private: |
| 144 | friend class FileCache; | 160 | friend class FileCache; |
| 161 | friend struct FilePointerComparator; | ||
| 145 | 162 | ||
| 146 | const Key _key; | 163 | const Key _key; |
| 147 | const size_t _length; | 164 | const size_t _length; |
| 148 | const std::string _filepath; | 165 | const std::string _filepath; |
| 149 | 166 | ||
| 150 | FileCache* _cache; | 167 | void touch(); |
| 151 | int _LRUKey { 0 }; | 168 | FileCache* _cache { nullptr }; |
| 169 | int64_t _modified { 0 }; | ||
| 152 | 170 | ||
| 153 | bool _shouldPersist { false }; | 171 | bool _shouldPersist { false }; |
| 154 | }; | 172 | }; |
| @@ -15,7 +15,12 @@ · 14 unmodified lines | |||
| 15 | 15 | ||
| 16 | #include <QFile> | 16 | #include <QFile> |
| 17 | 17 | ||
| 18 | #include <StatTracker.h> | ||
| 19 | |||
| 18 | void FileResourceRequest::doSend() { | 20 | void FileResourceRequest::doSend() { |
| 21 | auto statTracker = DependencyManager::get<StatTracker>(); | ||
| 22 | statTracker->incrementStat(STAT_FILE_REQUEST_STARTED); | ||
| 23 | |||
| 19 | QString filename = _url.toLocalFile(); | 24 | QString filename = _url.toLocalFile(); |
| 20 | 25 | ||
| 21 | // sometimes on windows, we see the toLocalFile() return null, | 26 | // sometimes on windows, we see the toLocalFile() return null, |
| @@ -60,4 +65,10 @@ void FileResourceRequest::doSend() { · 38 unmodified lines | |||
| 60 | 65 | ||
| 61 | _state = Finished; | 66 | _state = Finished; |
| 62 | emit finished(); | 67 | emit finished(); |
| 68 | |||
| 69 | if (_result == ResourceRequest::Success) { | ||
| 70 | statTracker->incrementStat(STAT_FILE_REQUEST_SUCCESS); | ||
| 71 | } else { | ||
| 72 | statTracker->incrementStat(STAT_FILE_REQUEST_FAILED); | ||
| 73 | } | ||
| 63 | } | 74 | } |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
| @@ -19,6 +19,7 @@ HFTabletWebEngineProfile::HFTabletWebEngineProfile(QObject* parent) : QQuickWebE · 18 unmodified lines | |||
| 19 | static const QString WEB_ENGINE_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"; | 19 | static const QString WEB_ENGINE_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"; |
| 20 | 20 | ||
| 21 | setHttpUserAgent(WEB_ENGINE_USER_AGENT); | 21 | setHttpUserAgent(WEB_ENGINE_USER_AGENT); |
| 22 | setStorageName(QML_WEB_ENGINE_NAME); | ||
| 22 | 23 | ||
| 23 | auto requestInterceptor = new HFTabletWebEngineRequestInterceptor(this); | 24 | auto requestInterceptor = new HFTabletWebEngineRequestInterceptor(this); |
| 24 | setRequestInterceptor(requestInterceptor); | 25 | setRequestInterceptor(requestInterceptor); |
File renamed without changes.
| @@ -11,7 +11,7 @@ · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | #include "HFTabletWebEngineRequestInterceptor.h" | 12 | #include "HFTabletWebEngineRequestInterceptor.h" |
| 13 | #include <QtCore/QDebug> | 13 | #include <QtCore/QDebug> |
| 14 | #include <AccountManager.h> | 14 | #include "AccountManager.h" |
| 15 | 15 | ||
| 16 | bool isTabletAuthableHighFidelityURL(const QUrl& url) { | 16 | bool isTabletAuthableHighFidelityURL(const QUrl& url) { |
| 17 | static const QStringList HF_HOSTS = { | 17 | static const QStringList HF_HOSTS = { |
File renamed without changes.
| @@ -20,6 +20,7 @@ HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : · 19 unmodified lines | |||
| 20 | { | 20 | { |
| 21 | static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; | 21 | static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; |
| 22 | setHttpUserAgent(WEB_ENGINE_USER_AGENT); | 22 | setHttpUserAgent(WEB_ENGINE_USER_AGENT); |
| 23 | setStorageName(QML_WEB_ENGINE_STORAGE_NAME); | ||
| 23 | 24 | ||
| 24 | // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user | 25 | // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user |
| 25 | auto requestInterceptor = new HFWebEngineRequestInterceptor(this); | 26 | auto requestInterceptor = new HFWebEngineRequestInterceptor(this); |
File renamed without changes.
| @@ -13,7 +13,7 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | #include <QtCore/QDebug> | 14 | #include <QtCore/QDebug> |
| 15 | 15 | ||
| 16 | #include <AccountManager.h> | 16 | #include "AccountManager.h" |
| 17 | #include "RequestFilters.h" | 17 | #include "RequestFilters.h" |
| 18 | 18 | ||
| 19 | void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { | 19 | void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { |
File renamed without changes.
| @@ -17,6 +17,7 @@ · 16 unmodified lines | |||
| 17 | #include <QMetaEnum> | 17 | #include <QMetaEnum> |
| 18 | 18 | ||
| 19 | #include <SharedUtil.h> | 19 | #include <SharedUtil.h> |
| 20 | #include <StatTracker.h> | ||
| 20 | 21 | ||
| 21 | #include "NetworkAccessManager.h" | 22 | #include "NetworkAccessManager.h" |
| 22 | #include "NetworkLogging.h" | 23 | #include "NetworkLogging.h" |
| @@ -49,6 +50,8 @@ void HTTPResourceRequest::cleanupTimer() { · 26 unmodified lines | |||
| 49 | } | 50 | } |
| 50 | 51 | ||
| 51 | void HTTPResourceRequest::doSend() { | 52 | void HTTPResourceRequest::doSend() { |
| 53 | DependencyManager::get<StatTracker>()->incrementStat(STAT_HTTP_REQUEST_STARTED); | ||
| 54 | |||
| 52 | QNetworkRequest networkRequest(_url); | 55 | QNetworkRequest networkRequest(_url); |
| 53 | networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); | 56 | networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); |
| 54 | networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); | 57 | networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); |
| @@ -178,6 +181,17 @@ void HTTPResourceRequest::onRequestFinished() { · 123 unmodified lines | |||
| 178 | 181 | ||
| 179 | _state = Finished; | 182 | _state = Finished; |
| 180 | emit finished(); | 183 | emit finished(); |
| 184 | |||
| 185 | auto statTracker = DependencyManager::get<StatTracker>(); | ||
| 186 | if (_result == Success) { | ||
| 187 | statTracker->incrementStat(STAT_HTTP_REQUEST_SUCCESS); | ||
| 188 | |||
| 189 | if (loadedFromCache()) { | ||
| 190 | statTracker->incrementStat(STAT_HTTP_REQUEST_CACHE); | ||
| 191 | } | ||
| 192 | } else { | ||
| 193 | statTracker->incrementStat(STAT_HTTP_REQUEST_FAILED); | ||
| 194 | } | ||
| 181 | } | 195 | } |
| 182 | 196 | ||
| 183 | void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { | 197 | void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { |
| @@ -202,4 +216,6 @@ void HTTPResourceRequest::onTimeout() { · 18 unmodified lines | |||
| 202 | _result = Timeout; | 216 | _result = Timeout; |
| 203 | _state = Finished; | 217 | _state = Finished; |
| 204 | emit finished(); | 218 | emit finished(); |
| 219 | |||
| 220 | DependencyManager::get<StatTracker>()->incrementStat(STAT_HTTP_REQUEST_FAILED); | ||
| 205 | } | 221 | } |
| @@ -22,13 +22,12 @@ · 21 unmodified lines | |||
| 22 | #include <QtCore/QUrl> | 22 | #include <QtCore/QUrl> |
| 23 | #include <QtNetwork/QHostInfo> | 23 | #include <QtNetwork/QHostInfo> |
| 24 | 24 | ||
| 25 | #include <tbb/parallel_for.h> | ||
| 26 | |||
| 27 | #include <LogHandler.h> | 25 | #include <LogHandler.h> |
| 28 | #include <shared/NetworkUtils.h> | 26 | #include <shared/NetworkUtils.h> |
| 29 | #include <NumericalConstants.h> | 27 | #include <NumericalConstants.h> |
| 30 | #include <SettingHandle.h> | 28 | #include <SettingHandle.h> |
| 31 | #include <SharedUtil.h> | 29 | #include <SharedUtil.h> |
| 30 | #include <StatTracker.h> | ||
| 32 | #include <UUID.h> | 31 | #include <UUID.h> |
| 33 | 32 | ||
| 34 | #include "AccountManager.h" | 33 | #include "AccountManager.h" |
| @@ -37,7 +36,6 @@ · 2 unmodified lines | |||
| 37 | #include "HifiSockAddr.h" | 36 | #include "HifiSockAddr.h" |
| 38 | #include "NetworkLogging.h" | 37 | #include "NetworkLogging.h" |
| 39 | #include "udt/Packet.h" | 38 | #include "udt/Packet.h" |
| 40 | #include <Trace.h> | ||
| 41 | 39 | ||
| 42 | static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); | 40 | static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); |
| 43 | 41 | ||
| @@ -1044,10 +1042,10 @@ void LimitedNodeList::setLocalSocket(const HifiSockAddr& sockAddr) { · 1000 unmodified lines | |||
| 1044 | qCInfo(networking) << "Local socket is" << sockAddr; | 1042 | qCInfo(networking) << "Local socket is" << sockAddr; |
| 1045 | } else { | 1043 | } else { |
| 1046 | qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr; | 1044 | qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr; |
| 1045 | DependencyManager::get<StatTracker>()->incrementStat(LOCAL_SOCKET_CHANGE_STAT); | ||
| 1047 | } | 1046 | } |
| 1048 | 1047 | ||
| 1049 | _localSockAddr = sockAddr; | 1048 | _localSockAddr = sockAddr; |
| 1050 | |||
| 1051 | emit localSockAddrChanged(_localSockAddr); | 1049 | emit localSockAddrChanged(_localSockAddr); |
| 1052 | } | 1050 | } |
| 1053 | } | 1051 | } |
| @@ -31,7 +31,7 @@ · 30 unmodified lines | |||
| 31 | #include <QtNetwork/QUdpSocket> | 31 | #include <QtNetwork/QUdpSocket> |
| 32 | #include <QtNetwork/QHostAddress> | 32 | #include <QtNetwork/QHostAddress> |
| 33 | 33 | ||
| 34 | #include <tbb/concurrent_unordered_map.h> | 34 | #include <TBBHelpers.h> |
| 35 | 35 | ||
| 36 | #include <DependencyManager.h> | 36 | #include <DependencyManager.h> |
| 37 | #include <SharedUtil.h> | 37 | #include <SharedUtil.h> |
| @@ -66,9 +66,10 @@ const QHostAddress DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME = QHostAddress::Lo · 28 unmodified lines | |||
| 66 | 66 | ||
| 67 | const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username"; | 67 | const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username"; |
| 68 | 68 | ||
| 69 | using namespace tbb; | 69 | const QString LOCAL_SOCKET_CHANGE_STAT = "LocalSocketChanges"; |
| 70 | |||
| 70 | typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair; | 71 | typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair; |
| 71 | typedef concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash; | 72 | typedef tbb::concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash; |
| 72 | 73 | ||
| 73 | typedef quint8 PingType_t; | 74 | typedef quint8 PingType_t; |
| 74 | namespace PingType { | 75 | namespace PingType { |
| @@ -16,6 +16,8 @@ · 15 unmodified lines | |||
| 16 | #include <QtCore/QBuffer> | 16 | #include <QtCore/QBuffer> |
| 17 | #include <QtCore/QThread> | 17 | #include <QtCore/QThread> |
| 18 | 18 | ||
| 19 | #include <ThreadHelpers.h> | ||
| 20 | |||
| 19 | #include "NetworkLogging.h" | 21 | #include "NetworkLogging.h" |
| 20 | #include "NodeList.h" | 22 | #include "NodeList.h" |
| 21 | #include "PacketReceiver.h" | 23 | #include "PacketReceiver.h" |
| @@ -30,12 +32,6 @@ MessagesClient::MessagesClient() { · 8 unmodified lines | |||
| 30 | connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated); | 32 | connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated); |
| 31 | } | 33 | } |
| 32 | 34 | ||
| 33 | void MessagesClient::init() { | ||
| 34 | if (QThread::currentThread() != thread()) { | ||
| 35 | QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel, | 35 | void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel, |
| 40 | bool& isText, QString& message, QByteArray& data, QUuid& senderID) { | 36 | bool& isText, QString& message, QByteArray& data, QUuid& senderID) { |
| 41 | quint16 channelLength; | 37 | quint16 channelLength; |
| @@ -185,3 +181,7 @@ void MessagesClient::handleNodeActivated(SharedNodePointer node) { · 143 unmodified lines | |||
| 185 | } | 181 | } |
| 186 | } | 182 | } |
| 187 | } | 183 | } |
| 184 | |||
| 185 | void MessagesClient::startThread() { | ||
| 186 | moveToNewNamedThread(this, "Messages Client Thread"); | ||
| 187 | } | ||
| @@ -28,7 +28,7 @@ class MessagesClient : public QObject, public Dependency { · 27 unmodified lines | |||
| 28 | public: | 28 | public: |
| 29 | MessagesClient(); | 29 | MessagesClient(); |
| 30 | 30 | ||
| 31 | Q_INVOKABLE void init(); | 31 | void startThread(); |
| 32 | 32 | ||
| 33 | Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); | 33 | Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); |
| 34 | Q_INVOKABLE void sendLocalMessage(QString channel, QString message); | 34 | Q_INVOKABLE void sendLocalMessage(QString channel, QString message); |
| @@ -24,7 +24,7 @@ · 23 unmodified lines | |||
| 24 | #include <QReadLocker> | 24 | #include <QReadLocker> |
| 25 | #include <UUIDHasher.h> | 25 | #include <UUIDHasher.h> |
| 26 | 26 | ||
| 27 | #include <tbb/concurrent_unordered_set.h> | 27 | #include <TBBHelpers.h> |
| 28 | 28 | ||
| 29 | #include "HifiSockAddr.h" | 29 | #include "HifiSockAddr.h" |
| 30 | #include "NetworkPeer.h" | 30 | #include "NetworkPeer.h" |
| @@ -20,6 +20,7 @@ · 19 unmodified lines | |||
| 20 | #include <QtNetwork/QHostInfo> | 20 | #include <QtNetwork/QHostInfo> |
| 21 | #include <QtNetwork/QNetworkInterface> | 21 | #include <QtNetwork/QNetworkInterface> |
| 22 | 22 | ||
| 23 | #include <ThreadHelpers.h> | ||
| 23 | #include <LogHandler.h> | 24 | #include <LogHandler.h> |
| 24 | #include <UUID.h> | 25 | #include <UUID.h> |
| 25 | 26 | ||
| @@ -1115,3 +1116,8 @@ void NodeList::setRequestsDomainListData(bool isRequesting) { · 1089 unmodified lines | |||
| 1115 | }); | 1116 | }); |
| 1116 | _requestsDomainListData = isRequesting; | 1117 | _requestsDomainListData = isRequesting; |
| 1117 | } | 1118 | } |
| 1119 | |||
| 1120 | |||
| 1121 | void NodeList::startThread() { | ||
| 1122 | moveToNewNamedThread(this, "NodeList Thread", QThread::TimeCriticalPriority); | ||
| 1123 | } | ||
| @@ -20,7 +20,7 @@ · 19 unmodified lines | |||
| 20 | #include <unistd.h> // not on windows, not needed for mac or windows | 20 | #include <unistd.h> // not on windows, not needed for mac or windows |
| 21 | #endif | 21 | #endif |
| 22 | 22 | ||
| 23 | #include <tbb/concurrent_unordered_set.h> | 23 | #include <TBBHelpers.h> |
| 24 | 24 | ||
| 25 | #include <QtCore/QElapsedTimer> | 25 | #include <QtCore/QElapsedTimer> |
| 26 | #include <QtCore/QMutex> | 26 | #include <QtCore/QMutex> |
| @@ -52,6 +52,7 @@ class NodeList : public LimitedNodeList { · 25 unmodified lines | |||
| 52 | SINGLETON_DEPENDENCY | 52 | SINGLETON_DEPENDENCY |
| 53 | 53 | ||
| 54 | public: | 54 | public: |
| 55 | void startThread(); | ||
| 55 | NodeType_t getOwnerType() const { return _ownerType.load(); } | 56 | NodeType_t getOwnerType() const { return _ownerType.load(); } |
| 56 | void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); } | 57 | void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); } |
| 57 | 58 | ||
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | #include <QtCore/QDebug> | 15 | #include <QtCore/QDebug> |
| 16 | 16 | ||
| 17 | #include <AccountManager.h> | 17 | #include "AccountManager.h" |
| 18 | 18 | ||
| 19 | namespace { | 19 | namespace { |
| 20 | 20 | ||
File renamed without changes.
| @@ -19,6 +19,21 @@ · 18 unmodified lines | |||
| 19 | 19 | ||
| 20 | #include "ByteRange.h" | 20 | #include "ByteRange.h" |
| 21 | 21 | ||
| 22 | const QString STAT_ATP_REQUEST_STARTED = "StartedATPRequest"; | ||
| 23 | const QString STAT_HTTP_REQUEST_STARTED = "StartedHTTPRequest"; | ||
| 24 | const QString STAT_FILE_REQUEST_STARTED = "StartedFileRequest"; | ||
| 25 | const QString STAT_ATP_REQUEST_SUCCESS = "SuccessfulATPRequest"; | ||
| 26 | const QString STAT_HTTP_REQUEST_SUCCESS = "SuccessfulHTTPRequest"; | ||
| 27 | const QString STAT_FILE_REQUEST_SUCCESS = "SuccessfulFileRequest"; | ||
| 28 | const QString STAT_ATP_REQUEST_FAILED = "FailedATPRequest"; | ||
| 29 | const QString STAT_HTTP_REQUEST_FAILED = "FailedHTTPRequest"; | ||
| 30 | const QString STAT_FILE_REQUEST_FAILED = "FailedFileRequest"; | ||
| 31 | const QString STAT_ATP_REQUEST_CACHE = "CacheATPRequest"; | ||
| 32 | const QString STAT_HTTP_REQUEST_CACHE = "CacheHTTPRequest"; | ||
| 33 | const QString STAT_ATP_MAPPING_REQUEST_STARTED = "StartedATPMappingRequest"; | ||
| 34 | const QString STAT_ATP_MAPPING_REQUEST_FAILED = "FailedATPMappingRequest"; | ||
| 35 | const QString STAT_ATP_MAPPING_REQUEST_SUCCESS = "SuccessfulATPMappingRequest"; | ||
| 36 | |||
| 22 | class ResourceRequest : public QObject { | 37 | class ResourceRequest : public QObject { |
| 23 | Q_OBJECT | 38 | Q_OBJECT |
| 24 | public: | 39 | public: |
| @@ -52,9 +52,8 @@ bool readStatus(QByteArray statusData) { · 51 unmodified lines | |||
| 52 | return false; | 52 | return false; |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) { | 55 | void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) { |
| 56 | QString serverPath = "./server-console/server-console.exe"; | 56 | QString serverPath = "./server-console/server-console.exe"; |
| 57 | qCDebug(networking) << "Running marker path is: " << runningMarkerName; | ||
| 58 | qCDebug(networking) << "Server path is: " << serverPath; | 57 | qCDebug(networking) << "Server path is: " << serverPath; |
| 59 | qCDebug(networking) << "autoShutdown: " << autoShutdown; | 58 | qCDebug(networking) << "autoShutdown: " << autoShutdown; |
| 60 | qCDebug(networking) << "noUpdater: " << noUpdater; | 59 | qCDebug(networking) << "noUpdater: " << noUpdater; |
| @@ -74,8 +73,8 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMark · 13 unmodified lines | |||
| 74 | } | 73 | } |
| 75 | 74 | ||
| 76 | if (autoShutdown) { | 75 | if (autoShutdown) { |
| 77 | QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName); | 76 | auto pid = QCoreApplication::applicationPid(); |
| 78 | args << "--shutdownWatcher" << interfaceRunningStateFile; | 77 | args << "--shutdownWith" << QString::number(pid); |
| 79 | } | 78 | } |
| 80 | 79 | ||
| 81 | if (noUpdater) { | 80 | if (noUpdater) { |
| @@ -21,7 +21,7 @@ namespace SandboxUtils { · 20 unmodified lines | |||
| 21 | 21 | ||
| 22 | QNetworkReply* getStatus(); | 22 | QNetworkReply* getStatus(); |
| 23 | bool readStatus(QByteArray statusData); | 23 | bool readStatus(QByteArray statusData); |
| 24 | void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater); | 24 | void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater); |
| 25 | }; | 25 | }; |
| 26 | 26 | ||
| 27 | #endif // hifi_SandboxUtils_h | 27 | #endif // hifi_SandboxUtils_h |
| @@ -20,7 +20,9 @@ · 19 unmodified lines | |||
| 20 | #include <DependencyManager.h> | 20 | #include <DependencyManager.h> |
| 21 | #include "AddressManager.h" | 21 | #include "AddressManager.h" |
| 22 | 22 | ||
| 23 | static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; | 23 | UserActivityLogger::UserActivityLogger() { |
| 24 | _timer.start(); | ||
| 25 | } | ||
| 24 | 26 | ||
| 25 | UserActivityLogger& UserActivityLogger::getInstance() { | 27 | UserActivityLogger& UserActivityLogger::getInstance() { |
| 26 | static UserActivityLogger sharedInstance; | 28 | static UserActivityLogger sharedInstance; |
| @@ -44,6 +46,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall · 17 unmodified lines | |||
| 44 | actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); | 46 | actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); |
| 45 | actionPart.setBody(QByteArray().append(action)); | 47 | actionPart.setBody(QByteArray().append(action)); |
| 46 | multipart->append(actionPart); | 48 | multipart->append(actionPart); |
| 49 | |||
| 50 | // Log the local-time that this event was logged | ||
| 51 | QHttpPart elapsedPart; | ||
| 52 | elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\""); | ||
| 53 | elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit()); | ||
| 54 | multipart->append(elapsedPart); | ||
| 47 | 55 | ||
| 48 | // If there are action details, add them to the multipart | 56 | // If there are action details, add them to the multipart |
| 49 | if (!details.isEmpty()) { | 57 | if (!details.isEmpty()) { |
| @@ -18,10 +18,13 @@ · 17 unmodified lines | |||
| 18 | #include <QString> | 18 | #include <QString> |
| 19 | #include <QJsonObject> | 19 | #include <QJsonObject> |
| 20 | #include <QNetworkReply> | 20 | #include <QNetworkReply> |
| 21 | #include <QElapsedTimer> | ||
| 21 | 22 | ||
| 22 | #include <SettingHandle.h> | 23 | #include <SettingHandle.h> |
| 23 | #include "AddressManager.h" | 24 | #include "AddressManager.h" |
| 24 | 25 | ||
| 26 | const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; | ||
| 27 | |||
| 25 | class UserActivityLogger : public QObject { | 28 | class UserActivityLogger : public QObject { |
| 26 | Q_OBJECT | 29 | Q_OBJECT |
| 27 | 30 | ||
| @@ -49,8 +52,10 @@ private slots: · 21 unmodified lines | |||
| 49 | void requestError(QNetworkReply& errorReply); | 52 | void requestError(QNetworkReply& errorReply); |
| 50 | 53 | ||
| 51 | private: | 54 | private: |
| 52 | UserActivityLogger() {}; | 55 | UserActivityLogger(); |
| 53 | Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false }; | 56 | Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false }; |
| 57 | |||
| 58 | QElapsedTimer _timer; | ||
| 54 | }; | 59 | }; |
| 55 | 60 | ||
| 56 | #endif // hifi_UserActivityLogger_h | 61 | #endif // hifi_UserActivityLogger_h |
| @@ -56,7 +56,7 @@ void UserActivityLoggerScriptingInterface::palAction(QString action, QString tar · 55 unmodified lines | |||
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) { | 58 | void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) { |
| 59 | doLogAction("pal_opened", { | 59 | doLogAction("pal_opened", { |
| 60 | { "seconds_opened", secondsOpened } | 60 | { "seconds_opened", secondsOpened } |
| 61 | }); | 61 | }); |
| 62 | } | 62 | } |
| @@ -71,6 +71,14 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b · 8 unmodified lines | |||
| 71 | doLogAction("makeUserConnection", payload); | 71 | doLogAction("makeUserConnection", payload); |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) { | ||
| 75 | doLogAction(newValue ? "bubbleOn" : "bubbleOff"); | ||
| 76 | } | ||
| 77 | |||
| 78 | void UserActivityLoggerScriptingInterface::bubbleActivated() { | ||
| 79 | doLogAction("bubbleActivated"); | ||
| 80 | } | ||
| 81 | |||
| 74 | void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) { | 82 | void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) { |
| 75 | doLogAction(action, QJsonObject::fromVariantMap(details)); | 83 | doLogAction(action, QJsonObject::fromVariantMap(details)); |
| 76 | } | 84 | } |
| @@ -30,6 +30,8 @@ public: · 29 unmodified lines | |||
| 30 | Q_INVOKABLE void palAction(QString action, QString target); | 30 | Q_INVOKABLE void palAction(QString action, QString target); |
| 31 | Q_INVOKABLE void palOpened(float secondsOpen); | 31 | Q_INVOKABLE void palOpened(float secondsOpen); |
| 32 | Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = ""); | 32 | Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = ""); |
| 33 | Q_INVOKABLE void bubbleToggled(bool newValue); | ||
| 34 | Q_INVOKABLE void bubbleActivated(); | ||
| 33 | Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); | 35 | Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); |
| 34 | private: | 36 | private: |
| 35 | void doLogAction(QString action, QJsonObject details = {}); | 37 | void doLogAction(QString action, QJsonObject details = {}); |
| @@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) { · 55 unmodified lines | |||
| 56 | case PacketType::AvatarData: | 56 | case PacketType::AvatarData: |
| 57 | case PacketType::BulkAvatarData: | 57 | case PacketType::BulkAvatarData: |
| 58 | case PacketType::KillAvatar: | 58 | case PacketType::KillAvatar: |
| 59 | return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarIdentitySequenceId); | 59 | return static_cast<PacketVersion>(AvatarMixerPacketVersion::MannequinDefaultAvatar); |
| 60 | case PacketType::MessagesData: | 60 | case PacketType::MessagesData: |
| 61 | return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData); | 61 | return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData); |
| 62 | case PacketType::ICEServerHeartbeat: | 62 | case PacketType::ICEServerHeartbeat: |
| @@ -236,7 +236,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { · 235 unmodified lines | |||
| 236 | AvatarAsChildFixes, | 236 | AvatarAsChildFixes, |
| 237 | StickAndBallDefaultAvatar, | 237 | StickAndBallDefaultAvatar, |
| 238 | IdentityPacketsIncludeUpdateTime, | 238 | IdentityPacketsIncludeUpdateTime, |
| 239 | AvatarIdentitySequenceId | 239 | AvatarIdentitySequenceId, |
| 240 | MannequinDefaultAvatar | ||
| 240 | }; | 241 | }; |
| 241 | 242 | ||
| 242 | enum class DomainConnectRequestVersion : PacketVersion { | 243 | enum class DomainConnectRequestVersion : PacketVersion { |
| @@ -241,6 +241,9 @@ void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { · 240 unmodified lines | |||
| 241 | std::lock_guard<std::mutex> locker { _handshakeMutex }; | 241 | std::lock_guard<std::mutex> locker { _handshakeMutex }; |
| 242 | _hasReceivedHandshakeACK = true; | 242 | _hasReceivedHandshakeACK = true; |
| 243 | } | 243 | } |
| 244 | |||
| 245 | _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); | ||
| 246 | |||
| 244 | // Notify on the handshake ACK condition | 247 | // Notify on the handshake ACK condition |
| 245 | _handshakeACKCondition.notify_one(); | 248 | _handshakeACKCondition.notify_one(); |
| 246 | } | 249 | } |
| @@ -152,6 +152,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 151 unmodified lines | |||
| 152 | btDispatcher* dispatcher = collisionWorld->getDispatcher(); | 152 | btDispatcher* dispatcher = collisionWorld->getDispatcher(); |
| 153 | int numManifolds = dispatcher->getNumManifolds(); | 153 | int numManifolds = dispatcher->getNumManifolds(); |
| 154 | bool hasFloor = false; | 154 | bool hasFloor = false; |
| 155 | bool isStuck = false; | ||
| 155 | 156 | ||
| 156 | btTransform rotation = _rigidBody->getWorldTransform(); | 157 | btTransform rotation = _rigidBody->getWorldTransform(); |
| 157 | rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part | 158 | rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part |
| @@ -169,10 +170,18 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 11 unmodified lines | |||
| 169 | btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame | 170 | btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame |
| 170 | btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character | 171 | btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character |
| 171 | btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp); | 172 | btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp); |
| 173 | // If there's non-trivial penetration with a big impulse for several steps, we're probably stuck. | ||
| 174 | // Note it here in the controller, and let MyAvatar figure out what to do about it. | ||
| 175 | const float STUCK_PENETRATION = -0.05f; // always negative into the object. | ||
| 176 | const float STUCK_IMPULSE = 500.0f; | ||
| 177 | const int STUCK_LIFETIME = 3; | ||
| 178 | if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) { | ||
| 179 | isStuck = true; // latch on | ||
| 180 | } | ||
| 172 | if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { | 181 | if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { |
| 173 | hasFloor = true; | 182 | hasFloor = true; |
| 174 | if (!pushing) { | 183 | if (!pushing && isStuck) { |
| 175 | // we're not pushing against anything so we can early exit | 184 | // we're not pushing against anything and we're stuck so we can early exit |
| 176 | // (all we need to know is that there is a floor) | 185 | // (all we need to know is that there is a floor) |
| 177 | break; | 186 | break; |
| 178 | } | 187 | } |
| @@ -198,12 +207,13 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 19 unmodified lines | |||
| 198 | _stepHeight = highestStep; | 207 | _stepHeight = highestStep; |
| 199 | _stepPoint = rotation * pointOnCharacter; // rotate into world-frame | 208 | _stepPoint = rotation * pointOnCharacter; // rotate into world-frame |
| 200 | } | 209 | } |
| 201 | if (hasFloor && !(pushing && _stepUpEnabled)) { | 210 | if (hasFloor && isStuck && !(pushing && _stepUpEnabled)) { |
| 202 | // early exit since all we need to know is that we're on a floor | 211 | // early exit since all we need to know is that we're on a floor |
| 203 | break; | 212 | break; |
| 204 | } | 213 | } |
| 205 | } | 214 | } |
| 206 | } | 215 | } |
| 216 | _isStuck = isStuck; | ||
| 207 | return hasFloor; | 217 | return hasFloor; |
| 208 | } | 218 | } |
| 209 | 219 | ||
| @@ -110,6 +110,7 @@ public: · 109 unmodified lines | |||
| 110 | void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale); | 110 | void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale); |
| 111 | 111 | ||
| 112 | bool isEnabledAndReady() const { return _dynamicsWorld; } | 112 | bool isEnabledAndReady() const { return _dynamicsWorld; } |
| 113 | bool isStuck() const { return _isStuck; } | ||
| 113 | 114 | ||
| 114 | void setCollisionless(bool collisionless); | 115 | void setCollisionless(bool collisionless); |
| 115 | int16_t computeCollisionGroup() const; | 116 | int16_t computeCollisionGroup() const; |
| @@ -192,6 +193,7 @@ protected: · 76 unmodified lines | |||
| 192 | 193 | ||
| 193 | State _state; | 194 | State _state; |
| 194 | bool _isPushingUp; | 195 | bool _isPushingUp; |
| 196 | bool _isStuck { false }; | ||
| 195 | 197 | ||
| 196 | btDynamicsWorld* _dynamicsWorld { nullptr }; | 198 | btDynamicsWorld* _dynamicsWorld { nullptr }; |
| 197 | btRigidBody* _rigidBody { nullptr }; | 199 | btRigidBody* _rigidBody { nullptr }; |
| @@ -65,8 +65,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer · 64 unmodified lines | |||
| 65 | _lastStep(0), | 65 | _lastStep(0), |
| 66 | _loopsWithoutOwner(0), | 66 | _loopsWithoutOwner(0), |
| 67 | _accelerationNearlyGravityCount(0), | 67 | _accelerationNearlyGravityCount(0), |
| 68 | _numInactiveUpdates(1), | 68 | _numInactiveUpdates(1) |
| 69 | _outgoingPriority(0) | ||
| 70 | { | 69 | { |
| 71 | _type = MOTIONSTATE_TYPE_ENTITY; | 70 | _type = MOTIONSTATE_TYPE_ENTITY; |
| 72 | assert(_entity); | 71 | assert(_entity); |
| @@ -75,6 +74,8 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer · 2 unmodified lines | |||
| 75 | // we need the side-effects of EntityMotionState::setShape() so we call it explicitly here | 74 | // we need the side-effects of EntityMotionState::setShape() so we call it explicitly here |
| 76 | // rather than pass the legit shape pointer to the ObjectMotionState ctor above. | 75 | // rather than pass the legit shape pointer to the ObjectMotionState ctor above. |
| 77 | setShape(shape); | 76 | setShape(shape); |
| 77 | |||
| 78 | _outgoingPriority = _entity->getPendingOwnershipPriority(); | ||
| 78 | } | 79 | } |
| 79 | 80 | ||
| 80 | EntityMotionState::~EntityMotionState() { | 81 | EntityMotionState::~EntityMotionState() { |
| @@ -84,7 +85,7 @@ EntityMotionState::~EntityMotionState() { · 3 unmodified lines | |||
| 84 | 85 | ||
| 85 | void EntityMotionState::updateServerPhysicsVariables() { | 86 | void EntityMotionState::updateServerPhysicsVariables() { |
| 86 | assert(entityTreeIsLocked()); | 87 | assert(entityTreeIsLocked()); |
| 87 | if (_entity->getSimulatorID() == Physics::getSessionUUID()) { | 88 | if (isLocallyOwned()) { |
| 88 | // don't slam these values if we are the simulation owner | 89 | // don't slam these values if we are the simulation owner |
| 89 | return; | 90 | return; |
| 90 | } | 91 | } |
| @@ -114,6 +115,7 @@ void EntityMotionState::handleDeactivation() { · 23 unmodified lines | |||
| 114 | 115 | ||
| 115 | // virtual | 116 | // virtual |
| 116 | void EntityMotionState::handleEasyChanges(uint32_t& flags) { | 117 | void EntityMotionState::handleEasyChanges(uint32_t& flags) { |
| 118 | assert(_entity); | ||
| 117 | assert(entityTreeIsLocked()); | 119 | assert(entityTreeIsLocked()); |
| 118 | updateServerPhysicsVariables(); | 120 | updateServerPhysicsVariables(); |
| 119 | ObjectMotionState::handleEasyChanges(flags); | 121 | ObjectMotionState::handleEasyChanges(flags); |
| @@ -135,23 +137,23 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { · 15 unmodified lines | |||
| 135 | _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; | 137 | _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; |
| 136 | } | 138 | } |
| 137 | _loopsWithoutOwner = 0; | 139 | _loopsWithoutOwner = 0; |
| 138 | } else if (_entity->getSimulatorID() == Physics::getSessionUUID()) { | 140 | _numInactiveUpdates = 0; |
| 141 | } else if (isLocallyOwned()) { | ||
| 139 | // we just inherited ownership, make sure our desired priority matches what we have | 142 | // we just inherited ownership, make sure our desired priority matches what we have |
| 140 | upgradeOutgoingPriority(_entity->getSimulationPriority()); | 143 | upgradeOutgoingPriority(_entity->getSimulationPriority()); |
| 141 | } else { | 144 | } else { |
| 142 | _outgoingPriority = 0; | 145 | _outgoingPriority = 0; |
| 143 | _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; | 146 | _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; |
| 147 | _numInactiveUpdates = 0; | ||
| 144 | } | 148 | } |
| 145 | } | 149 | } |
| 146 | if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) { | 150 | if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) { |
| 147 | // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bits really mean "we should bid for ownership because | 151 | // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following: |
| 148 | // a local script has been changing physics properties, or we should adjust our own ownership priority". | 152 | // (1) we own it but may need to change the priority OR... |
| 149 | // The desired priority is determined by which bits were set. | 153 | // (2) we don't own it but should bid (because a local script has been changing physics properties) |
| 150 | if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB) { | 154 | uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); |
| 151 | _outgoingPriority = SCRIPT_GRAB_SIMULATION_PRIORITY; | 155 | _outgoingPriority = glm::max(_outgoingPriority, newPriority); |
| 152 | } else { | 156 | |
| 153 | _outgoingPriority = SCRIPT_POKE_SIMULATION_PRIORITY; | ||
| 154 | } | ||
| 155 | // reset bid expiry so that we bid ASAP | 157 | // reset bid expiry so that we bid ASAP |
| 156 | _nextOwnershipBid = 0; | 158 | _nextOwnershipBid = 0; |
| 157 | } | 159 | } |
| @@ -170,6 +172,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { · 12 unmodified lines | |||
| 170 | 172 | ||
| 171 | // virtual | 173 | // virtual |
| 172 | bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { | 174 | bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { |
| 175 | assert(_entity); | ||
| 173 | updateServerPhysicsVariables(); | 176 | updateServerPhysicsVariables(); |
| 174 | return ObjectMotionState::handleHardAndEasyChanges(flags, engine); | 177 | return ObjectMotionState::handleHardAndEasyChanges(flags, engine); |
| 175 | } | 178 | } |
| @@ -315,7 +318,7 @@ bool EntityMotionState::isCandidateForOwnership() const { · 139 unmodified lines | |||
| 315 | assert(_entity); | 318 | assert(_entity); |
| 316 | assert(entityTreeIsLocked()); | 319 | assert(entityTreeIsLocked()); |
| 317 | return _outgoingPriority != 0 | 320 | return _outgoingPriority != 0 |
| 318 | || Physics::getSessionUUID() == _entity->getSimulatorID() | 321 | || isLocallyOwned() |
| 319 | || _entity->dynamicDataNeedsTransmit(); | 322 | || _entity->dynamicDataNeedsTransmit(); |
| 320 | } | 323 | } |
| 321 | 324 | ||
| @@ -489,7 +492,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { · 167 unmodified lines | |||
| 489 | return true; | 492 | return true; |
| 490 | } | 493 | } |
| 491 | 494 | ||
| 492 | if (_entity->getSimulatorID() != Physics::getSessionUUID()) { | 495 | if (!isLocallyOwned()) { |
| 493 | // we don't own the simulation | 496 | // we don't own the simulation |
| 494 | 497 | ||
| 495 | // NOTE: we do not volunteer to own kinematic or static objects | 498 | // NOTE: we do not volunteer to own kinematic or static objects |
| @@ -597,7 +600,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ · 101 unmodified lines | |||
| 597 | properties.clearSimulationOwner(); | 600 | properties.clearSimulationOwner(); |
| 598 | _outgoingPriority = 0; | 601 | _outgoingPriority = 0; |
| 599 | _entity->setPendingOwnershipPriority(_outgoingPriority, now); | 602 | _entity->setPendingOwnershipPriority(_outgoingPriority, now); |
| 600 | } else if (Physics::getSessionUUID() != _entity->getSimulatorID()) { | 603 | } else if (!isLocallyOwned()) { |
| 601 | // we don't own the simulation for this entity yet, but we're sending a bid for it | 604 | // we don't own the simulation for this entity yet, but we're sending a bid for it |
| 602 | quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); | 605 | quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); |
| 603 | properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); | 606 | properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); |
| @@ -786,6 +789,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma · 182 unmodified lines | |||
| 786 | _entity->computeCollisionGroupAndFinalMask(group, mask); | 789 | _entity->computeCollisionGroupAndFinalMask(group, mask); |
| 787 | } | 790 | } |
| 788 | 791 | ||
| 792 | bool EntityMotionState::isLocallyOwned() const { | ||
| 793 | return _entity->getSimulatorID() == Physics::getSessionUUID(); | ||
| 794 | } | ||
| 795 | |||
| 789 | bool EntityMotionState::shouldBeLocallyOwned() const { | 796 | bool EntityMotionState::shouldBeLocallyOwned() const { |
| 790 | return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || | 797 | return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || |
| 791 | _entity->getSimulatorID() == Physics::getSessionUUID(); | 798 | _entity->getSimulatorID() == Physics::getSessionUUID(); |
| @@ -79,6 +79,7 @@ public: · 78 unmodified lines | |||
| 79 | 79 | ||
| 80 | virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; | 80 | virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; |
| 81 | 81 | ||
| 82 | bool isLocallyOwned() const override; | ||
| 82 | bool shouldBeLocallyOwned() const override; | 83 | bool shouldBeLocallyOwned() const override; |
| 83 | 84 | ||
| 84 | friend class PhysicalEntitySimulation; | 85 | friend class PhysicalEntitySimulation; |
| @@ -202,6 +202,7 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) { · 201 unmodified lines | |||
| 202 | } | 202 | } |
| 203 | 203 | ||
| 204 | void ObjectMotionState::handleEasyChanges(uint32_t& flags) { | 204 | void ObjectMotionState::handleEasyChanges(uint32_t& flags) { |
| 205 | assert(_body && _shape); | ||
| 205 | if (flags & Simulation::DIRTY_POSITION) { | 206 | if (flags & Simulation::DIRTY_POSITION) { |
| 206 | btTransform worldTrans = _body->getWorldTransform(); | 207 | btTransform worldTrans = _body->getWorldTransform(); |
| 207 | btVector3 newPosition = glmToBullet(getObjectPosition()); | 208 | btVector3 newPosition = glmToBullet(getObjectPosition()); |
| @@ -282,6 +283,7 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) { · 74 unmodified lines | |||
| 282 | } | 283 | } |
| 283 | 284 | ||
| 284 | bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { | 285 | bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { |
| 286 | assert(_body && _shape); | ||
| 285 | if (flags & Simulation::DIRTY_SHAPE) { | 287 | if (flags & Simulation::DIRTY_SHAPE) { |
| 286 | // make sure the new shape is valid | 288 | // make sure the new shape is valid |
| 287 | if (!isReadyToComputeShape()) { | 289 | if (!isReadyToComputeShape()) { |
| @@ -79,7 +79,7 @@ public: · 78 unmodified lines | |||
| 79 | static ShapeManager* getShapeManager(); | 79 | static ShapeManager* getShapeManager(); |
| 80 | 80 | ||
| 81 | ObjectMotionState(const btCollisionShape* shape); | 81 | ObjectMotionState(const btCollisionShape* shape); |
| 82 | ~ObjectMotionState(); | 82 | virtual ~ObjectMotionState(); |
| 83 | 83 | ||
| 84 | virtual void handleEasyChanges(uint32_t& flags); | 84 | virtual void handleEasyChanges(uint32_t& flags); |
| 85 | virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); | 85 | virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); |
| @@ -146,6 +146,7 @@ public: · 60 unmodified lines | |||
| 146 | void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; } | 146 | void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; } |
| 147 | void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } | 147 | void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } |
| 148 | 148 | ||
| 149 | virtual bool isLocallyOwned() const { return false; } | ||
| 149 | virtual bool shouldBeLocallyOwned() const { return false; } | 150 | virtual bool shouldBeLocallyOwned() const { return false; } |
| 150 | 151 | ||
| 151 | friend class PhysicsEngine; | 152 | friend class PhysicsEngine; |
| @@ -130,7 +130,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { · 129 unmodified lines | |||
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | // then remove the objects (aka MotionStates) from physics | 132 | // then remove the objects (aka MotionStates) from physics |
| 133 | _physicsEngine->removeObjects(_physicalObjects); | 133 | _physicsEngine->removeSetOfObjects(_physicalObjects); |
| 134 | 134 | ||
| 135 | // delete the MotionStates | 135 | // delete the MotionStates |
| 136 | // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete | 136 | // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete |
| @@ -129,6 +129,9 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { · 128 unmodified lines | |||
| 129 | } | 129 | } |
| 130 | body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); | 130 | body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); |
| 131 | body->updateInertiaTensor(); | 131 | body->updateInertiaTensor(); |
| 132 | if (motionState->isLocallyOwned()) { | ||
| 133 | _activeStaticBodies.insert(body); | ||
| 134 | } | ||
| 132 | break; | 135 | break; |
| 133 | } | 136 | } |
| 134 | } | 137 | } |
| @@ -174,19 +177,9 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { · 39 unmodified lines | |||
| 174 | // frame (because the framerate is faster than our physics simulation rate). When this happens we must scan | 177 | // frame (because the framerate is faster than our physics simulation rate). When this happens we must scan |
| 175 | // _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer. | 178 | // _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer. |
| 176 | for (auto object : objects) { | 179 | for (auto object : objects) { |
| 177 | btRigidBody* body = object->getRigidBody(); | 180 | std::set<btRigidBody*>::iterator itr = _activeStaticBodies.find(object->getRigidBody()); |
| 178 | 181 | if (itr != _activeStaticBodies.end()) { | |
| 179 | std::vector<btRigidBody*>::reverse_iterator itr = _activeStaticBodies.rbegin(); | 182 | _activeStaticBodies.erase(itr); |
| 180 | while (itr != _activeStaticBodies.rend()) { | ||
| 181 | if (body == *itr) { | ||
| 182 | if (*itr != *(_activeStaticBodies.rbegin())) { | ||
| 183 | // swap with rbegin | ||
| 184 | *itr = *(_activeStaticBodies.rbegin()); | ||
| 185 | } | ||
| 186 | _activeStaticBodies.pop_back(); | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | ++itr; | ||
| 190 | } | 183 | } |
| 191 | } | 184 | } |
| 192 | } | 185 | } |
| @@ -207,7 +200,7 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { · 14 unmodified lines | |||
| 207 | } | 200 | } |
| 208 | 201 | ||
| 209 | // Same as above, but takes a Set instead of a Vector. Should only be called during teardown. | 202 | // Same as above, but takes a Set instead of a Vector. Should only be called during teardown. |
| 210 | void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) { | 203 | void PhysicsEngine::removeSetOfObjects(const SetOfMotionStates& objects) { |
| 211 | _contactMap.clear(); | 204 | _contactMap.clear(); |
| 212 | for (auto object : objects) { | 205 | for (auto object : objects) { |
| 213 | btRigidBody* body = object->getRigidBody(); | 206 | btRigidBody* body = object->getRigidBody(); |
| @@ -245,14 +238,16 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob · 31 unmodified lines | |||
| 245 | object->clearIncomingDirtyFlags(); | 238 | object->clearIncomingDirtyFlags(); |
| 246 | } | 239 | } |
| 247 | if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { | 240 | if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { |
| 248 | _activeStaticBodies.push_back(object->getRigidBody()); | 241 | _activeStaticBodies.insert(object->getRigidBody()); |
| 249 | } | 242 | } |
| 250 | } | 243 | } |
| 251 | // active static bodies have changed (in an Easy way) and need their Aabbs updated | 244 | // active static bodies have changed (in an Easy way) and need their Aabbs updated |
| 252 | // but we've configured Bullet to NOT update them automatically (for improved performance) | 245 | // but we've configured Bullet to NOT update them automatically (for improved performance) |
| 253 | // so we must do it ourselves | 246 | // so we must do it ourselves |
| 254 | for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { | 247 | std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin(); |
| 255 | _dynamicsWorld->updateSingleAabb(_activeStaticBodies[i]); | 248 | while (itr != _activeStaticBodies.end()) { |
| 249 | _dynamicsWorld->updateSingleAabb(*itr); | ||
| 250 | ++itr; | ||
| 256 | } | 251 | } |
| 257 | return stillNeedChange; | 252 | return stillNeedChange; |
| 258 | } | 253 | } |
| @@ -496,13 +491,23 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { · 237 unmodified lines | |||
| 496 | 491 | ||
| 497 | const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { | 492 | const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { |
| 498 | BT_PROFILE("copyOutgoingChanges"); | 493 | BT_PROFILE("copyOutgoingChanges"); |
| 494 | |||
| 495 | _dynamicsWorld->synchronizeMotionStates(); | ||
| 496 | |||
| 499 | // Bullet will not deactivate static objects (it doesn't expect them to be active) | 497 | // Bullet will not deactivate static objects (it doesn't expect them to be active) |
| 500 | // so we must deactivate them ourselves | 498 | // so we must deactivate them ourselves |
| 501 | for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { | 499 | std::set<btRigidBody*>::const_iterator itr = _activeStaticBodies.begin(); |
| 502 | _activeStaticBodies[i]->forceActivationState(ISLAND_SLEEPING); | 500 | while (itr != _activeStaticBodies.end()) { |
| 501 | btRigidBody* body = *itr; | ||
| 502 | body->forceActivationState(ISLAND_SLEEPING); | ||
| 503 | ObjectMotionState* motionState = static_cast<ObjectMotionState*>(body->getUserPointer()); | ||
| 504 | if (motionState) { | ||
| 505 | _dynamicsWorld->addChangedMotionState(motionState); | ||
| 506 | } | ||
| 507 | ++itr; | ||
| 503 | } | 508 | } |
| 504 | _activeStaticBodies.clear(); | 509 | _activeStaticBodies.clear(); |
| 505 | _dynamicsWorld->synchronizeMotionStates(); | 510 | |
| 506 | _hasOutgoingChanges = false; | 511 | _hasOutgoingChanges = false; |
| 507 | return _dynamicsWorld->getChangedMotionStates(); | 512 | return _dynamicsWorld->getChangedMotionStates(); |
| 508 | } | 513 | } |
| @@ -13,6 +13,7 @@ · 12 unmodified lines | |||
| 13 | #define hifi_PhysicsEngine_h | 13 | #define hifi_PhysicsEngine_h |
| 14 | 14 | ||
| 15 | #include <stdint.h> | 15 | #include <stdint.h> |
| 16 | #include <set> | ||
| 16 | #include <vector> | 17 | #include <vector> |
| 17 | 18 | ||
| 18 | #include <QUuid> | 19 | #include <QUuid> |
| @@ -53,7 +54,7 @@ public: · 34 unmodified lines | |||
| 53 | uint32_t getNumSubsteps(); | 54 | uint32_t getNumSubsteps(); |
| 54 | 55 | ||
| 55 | void removeObjects(const VectorOfMotionStates& objects); | 56 | void removeObjects(const VectorOfMotionStates& objects); |
| 56 | void removeObjects(const SetOfMotionStates& objects); // only called during teardown | 57 | void removeSetOfObjects(const SetOfMotionStates& objects); // only called during teardown |
| 57 | 58 | ||
| 58 | void addObjects(const VectorOfMotionStates& objects); | 59 | void addObjects(const VectorOfMotionStates& objects); |
| 59 | VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); | 60 | VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); |
| @@ -114,7 +115,7 @@ private: · 54 unmodified lines | |||
| 114 | CollisionEvents _collisionEvents; | 115 | CollisionEvents _collisionEvents; |
| 115 | QHash<QUuid, EntityDynamicPointer> _objectDynamics; | 116 | QHash<QUuid, EntityDynamicPointer> _objectDynamics; |
| 116 | QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody; | 117 | QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody; |
| 117 | std::vector<btRigidBody*> _activeStaticBodies; | 118 | std::set<btRigidBody*> _activeStaticBodies; |
| 118 | 119 | ||
| 119 | glm::vec3 _originOffset; | 120 | glm::vec3 _originOffset; |
| 120 | 121 | ||
| @@ -51,6 +51,8 @@ public: · 50 unmodified lines | |||
| 51 | const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; } | 51 | const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; } |
| 52 | const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; } | 52 | const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; } |
| 53 | 53 | ||
| 54 | void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); } | ||
| 55 | |||
| 54 | private: | 56 | private: |
| 55 | // call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState() | 57 | // call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState() |
| 56 | void synchronizeMotionState(btRigidBody* body); | 58 | void synchronizeMotionState(btRigidBody* body); |
| @@ -200,6 +200,7 @@ public: · 199 unmodified lines | |||
| 200 | virtual float newFramePresentRate() const { return -1.0f; } | 200 | virtual float newFramePresentRate() const { return -1.0f; } |
| 201 | // Rate at which rendered frames are being skipped | 201 | // Rate at which rendered frames are being skipped |
| 202 | virtual float droppedFrameRate() const { return -1.0f; } | 202 | virtual float droppedFrameRate() const { return -1.0f; } |
| 203 | virtual bool getSupportsAutoSwitch() { return false; } | ||
| 203 | 204 | ||
| 204 | // Hardware specific stats | 205 | // Hardware specific stats |
| 205 | virtual QJsonObject getHardwareStats() const { return QJsonObject(); } | 206 | virtual QJsonObject getHardwareStats() const { return QJsonObject(); } |
| @@ -24,6 +24,7 @@ public: · 23 unmodified lines | |||
| 24 | // Some input plugins are comprised of multiple subdevices (SDL2, for instance). | 24 | // Some input plugins are comprised of multiple subdevices (SDL2, for instance). |
| 25 | // If an input plugin is only a single device, it will only return it's primary name. | 25 | // If an input plugin is only a single device, it will only return it's primary name. |
| 26 | virtual QStringList getSubdeviceNames() { return { getName() }; }; | 26 | virtual QStringList getSubdeviceNames() { return { getName() }; }; |
| 27 | virtual bool isHandController() const = 0; | 27 | virtual bool isHandController() const { return false; } |
| 28 | virtual bool isHeadController() const { return false; } | ||
| 28 | }; | 29 | }; |
| 29 | 30 | ||
| @@ -53,6 +53,18 @@ public: · 52 unmodified lines | |||
| 53 | virtual bool isActive() { | 53 | virtual bool isActive() { |
| 54 | return _active; | 54 | return _active; |
| 55 | } | 55 | } |
| 56 | virtual bool startStandBySession() { | ||
| 57 | _sessionStatus = true; | ||
| 58 | return _sessionStatus; | ||
| 59 | } | ||
| 60 | |||
| 61 | virtual void endSession() { | ||
| 62 | _sessionStatus = false; | ||
| 63 | } | ||
| 64 | |||
| 65 | virtual bool isSessionActive() { | ||
| 66 | return _sessionStatus; | ||
| 67 | } | ||
| 56 | 68 | ||
| 57 | /** | 69 | /** |
| 58 | * Called by the application during it's idle phase. If the plugin needs to do | 70 | * Called by the application during it's idle phase. If the plugin needs to do |
| @@ -73,6 +85,7 @@ signals: · 14 unmodified lines | |||
| 73 | 85 | ||
| 74 | protected: | 86 | protected: |
| 75 | bool _active { false }; | 87 | bool _active { false }; |
| 88 | bool _sessionStatus { false }; | ||
| 76 | PluginContainer* _container { nullptr }; | 89 | PluginContainer* _container { nullptr }; |
| 77 | static const char* UNKNOWN_PLUGIN_ID; | 90 | static const char* UNKNOWN_PLUGIN_ID; |
| 78 | 91 | ||
| @@ -24,6 +24,15 @@ bool PluginUtils::isHMDAvailable(const QString& pluginName) { · 23 unmodified lines | |||
| 24 | return false; | 24 | return false; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | bool PluginUtils::isHeadControllerAvailable(const QString& pluginName) { | ||
| 28 | for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) { | ||
| 29 | if (inputPlugin->isHeadController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) { | ||
| 30 | return true; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | return false; | ||
| 34 | }; | ||
| 35 | |||
| 27 | bool PluginUtils::isHandControllerAvailable(const QString& pluginName) { | 36 | bool PluginUtils::isHandControllerAvailable(const QString& pluginName) { |
| 28 | for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) { | 37 | for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) { |
| 29 | if (inputPlugin->isHandController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) { | 38 | if (inputPlugin->isHandController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) { |
| @@ -16,6 +16,7 @@ class PluginUtils { · 15 unmodified lines | |||
| 16 | public: | 16 | public: |
| 17 | static bool isHMDAvailable(const QString& pluginName = ""); | 17 | static bool isHMDAvailable(const QString& pluginName = ""); |
| 18 | static bool isHandControllerAvailable(const QString& pluginName = ""); | 18 | static bool isHandControllerAvailable(const QString& pluginName = ""); |
| 19 | static bool isHeadControllerAvailable(const QString& pluginName = ""); | ||
| 19 | static bool isSubdeviceContainingNameAvailable(QString name); | 20 | static bool isSubdeviceContainingNameAvailable(QString name); |
| 20 | static bool isViveControllerAvailable(); | 21 | static bool isViveControllerAvailable(); |
| 21 | static bool isOculusTouchControllerAvailable(); | 22 | static bool isOculusTouchControllerAvailable(); |
| @@ -23,7 +23,7 @@ ProceduralSkybox::ProceduralSkybox() : model::Skybox() { · 22 unmodified lines | |||
| 23 | _procedural._fragmentSource = skybox_frag; | 23 | _procedural._fragmentSource = skybox_frag; |
| 24 | // Adjust the pipeline state for background using the stencil test | 24 | // Adjust the pipeline state for background using the stencil test |
| 25 | _procedural.setDoesFade(false); | 25 | _procedural.setDoesFade(false); |
| 26 | _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | 26 | _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | bool ProceduralSkybox::empty() { | 29 | bool ProceduralSkybox::empty() { |
| @@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render) · 2 unmodified lines | |||
| 3 | # pull in the resources.qrc file | 3 | # pull in the resources.qrc file |
| 4 | qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") | 4 | qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") |
| 5 | setup_hifi_library(Widgets OpenGL Network Qml Quick Script) | 5 | setup_hifi_library(Widgets OpenGL Network Qml Quick Script) |
| 6 | link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities image) | 6 | link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities image procedural) |
| 7 | 7 | ||
| 8 | if (NOT ANDROID) | 8 | if (NOT ANDROID) |
| 9 | target_nsight() | 9 | target_nsight() |
| @@ -260,7 +260,7 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, const AnimPo · 259 unmodified lines | |||
| 260 | // there is room, so lets draw a nice bone | 260 | // there is room, so lets draw a nice bone |
| 261 | 261 | ||
| 262 | glm::vec3 uAxis, vAxis, wAxis; | 262 | glm::vec3 uAxis, vAxis, wAxis; |
| 263 | generateBasisVectors(boneAxis0, glm::vec3(1, 0, 0), uAxis, vAxis, wAxis); | 263 | generateBasisVectors(boneAxis0, glm::vec3(1.0f, 0.0f, 0.0f), uAxis, vAxis, wAxis); |
| 264 | 264 | ||
| 265 | glm::vec3 boneBaseCorners[NUM_BASE_CORNERS]; | 265 | glm::vec3 boneBaseCorners[NUM_BASE_CORNERS]; |
| 266 | boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius)); | 266 | boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius)); |
| @@ -17,6 +17,7 @@ · 16 unmodified lines | |||
| 17 | #include <gpu/Context.h> | 17 | #include <gpu/Context.h> |
| 18 | 18 | ||
| 19 | #include "AntialiasingEffect.h" | 19 | #include "AntialiasingEffect.h" |
| 20 | #include "StencilMaskPass.h" | ||
| 20 | #include "TextureCache.h" | 21 | #include "TextureCache.h" |
| 21 | #include "FramebufferCache.h" | 22 | #include "FramebufferCache.h" |
| 22 | #include "DependencyManager.h" | 23 | #include "DependencyManager.h" |
| @@ -70,6 +71,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { · 47 unmodified lines | |||
| 70 | 71 | ||
| 71 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | 72 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); |
| 72 | 73 | ||
| 74 | PrepareStencil::testMask(*state); | ||
| 75 | |||
| 73 | state->setDepthTest(false, false, gpu::LESS_EQUAL); | 76 | state->setDepthTest(false, false, gpu::LESS_EQUAL); |
| 74 | 77 | ||
| 75 | // Good to go add the brand new pipeline | 78 | // Good to go add the brand new pipeline |
| @@ -93,6 +96,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { · 17 unmodified lines | |||
| 93 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | 96 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); |
| 94 | 97 | ||
| 95 | state->setDepthTest(false, false, gpu::LESS_EQUAL); | 98 | state->setDepthTest(false, false, gpu::LESS_EQUAL); |
| 99 | PrepareStencil::testMask(*state); | ||
| 96 | 100 | ||
| 97 | // Good to go add the brand new pipeline | 101 | // Good to go add the brand new pipeline |
| 98 | _blendPipeline = gpu::Pipeline::create(program, state); | 102 | _blendPipeline = gpu::Pipeline::create(program, state); |
| @@ -106,10 +110,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const · 7 unmodified lines | |||
| 106 | 110 | ||
| 107 | RenderArgs* args = renderContext->args; | 111 | RenderArgs* args = renderContext->args; |
| 108 | 112 | ||
| 109 | if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { | ||
| 110 | return; | ||
| 111 | } | ||
| 112 | |||
| 113 | gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { | 113 | gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { |
| 114 | batch.enableStereo(false); | 114 | batch.enableStereo(false); |
| 115 | batch.setViewportTransform(args->_viewport); | 115 | batch.setViewportTransform(args->_viewport); |
| @@ -0,0 +1,140 @@ | |||
| 1 | // | ||
| 2 | // BackgroundStage.cpp | ||
| 3 | // | ||
| 4 | // Created by Sam Gateau on 5/9/2017. | ||
| 5 | // Copyright 2015 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | // | ||
| 10 | #include "BackgroundStage.h" | ||
| 11 | |||
| 12 | #include "DeferredLightingEffect.h" | ||
| 13 | |||
| 14 | #include <gpu/Context.h> | ||
| 15 | |||
| 16 | BackgroundStage::Index BackgroundStage::findBackground(const BackgroundPointer& background) const { | ||
| 17 | auto found = _backgroundMap.find(background); | ||
| 18 | if (found != _backgroundMap.end()) { | ||
| 19 | return INVALID_INDEX; | ||
| 20 | } else { | ||
| 21 | return (*found).second; | ||
| 22 | } | ||
| 23 | |||
| 24 | } | ||
| 25 | |||
| 26 | BackgroundStage::Index BackgroundStage::addBackground(const BackgroundPointer& background) { | ||
| 27 | |||
| 28 | auto found = _backgroundMap.find(background); | ||
| 29 | if (found == _backgroundMap.end()) { | ||
| 30 | auto backgroundId = _backgrounds.newElement(background); | ||
| 31 | // Avoid failing to allocate a background, just pass | ||
| 32 | if (backgroundId != INVALID_INDEX) { | ||
| 33 | |||
| 34 | // Insert the background and its index in the reverse map | ||
| 35 | _backgroundMap.insert(BackgroundMap::value_type(background, backgroundId)); | ||
| 36 | } | ||
| 37 | return backgroundId; | ||
| 38 | } else { | ||
| 39 | return (*found).second; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | |||
| 44 | BackgroundStage::BackgroundPointer BackgroundStage::removeBackground(Index index) { | ||
| 45 | BackgroundPointer removed = _backgrounds.freeElement(index); | ||
| 46 | |||
| 47 | if (removed) { | ||
| 48 | _backgroundMap.erase(removed); | ||
| 49 | } | ||
| 50 | return removed; | ||
| 51 | } | ||
| 52 | |||
| 53 | |||
| 54 | void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { | ||
| 55 | |||
| 56 | const auto& lightingModel = inputs; | ||
| 57 | if (!lightingModel->isBackgroundEnabled()) { | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | |||
| 61 | |||
| 62 | // Background rendering decision | ||
| 63 | auto backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage(); | ||
| 64 | model::SunSkyStagePointer background; | ||
| 65 | model::SkyboxPointer skybox; | ||
| 66 | if (backgroundStage->_currentFrame._backgrounds.size()) { | ||
| 67 | auto backgroundId = backgroundStage->_currentFrame._backgrounds.front(); | ||
| 68 | auto background = backgroundStage->getBackground(backgroundId); | ||
| 69 | if (background) { | ||
| 70 | skybox = background->getSkybox(); | ||
| 71 | } | ||
| 72 | } else { | ||
| 73 | skybox = DependencyManager::get<DeferredLightingEffect>()->getDefaultSkybox(); | ||
| 74 | } | ||
| 75 | |||
| 76 | /* auto backgroundMode = skyStage->getBackgroundMode(); | ||
| 77 | |||
| 78 | switch (backgroundMode) { | ||
| 79 | case model::SunSkyStage::SKY_DEFAULT: { | ||
| 80 | auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage(); | ||
| 81 | auto sceneKeyLight = scene->getKeyLight(); | ||
| 82 | |||
| 83 | scene->setSunModelEnable(false); | ||
| 84 | sceneKeyLight->setColor(ColorUtils::toVec3(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR)); | ||
| 85 | sceneKeyLight->setIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY); | ||
| 86 | sceneKeyLight->setAmbientIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY); | ||
| 87 | sceneKeyLight->setDirection(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION); | ||
| 88 | // fall through: render a skybox (if available), or the defaults (if requested) | ||
| 89 | } | ||
| 90 | |||
| 91 | case model::SunSkyStage::SKY_BOX: {*/ | ||
| 92 | if (skybox && !skybox->empty()) { | ||
| 93 | PerformanceTimer perfTimer("skybox"); | ||
| 94 | auto args = renderContext->args; | ||
| 95 | |||
| 96 | gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { | ||
| 97 | args->_batch = &batch; | ||
| 98 | |||
| 99 | batch.enableSkybox(true); | ||
| 100 | |||
| 101 | batch.setViewportTransform(args->_viewport); | ||
| 102 | batch.setStateScissorRect(args->_viewport); | ||
| 103 | |||
| 104 | glm::mat4 projMat; | ||
| 105 | Transform viewMat; | ||
| 106 | args->getViewFrustum().evalProjectionMatrix(projMat); | ||
| 107 | args->getViewFrustum().evalViewTransform(viewMat); | ||
| 108 | |||
| 109 | batch.setProjectionTransform(projMat); | ||
| 110 | batch.setViewTransform(viewMat); | ||
| 111 | |||
| 112 | skybox->render(batch, args->getViewFrustum()); | ||
| 113 | }); | ||
| 114 | args->_batch = nullptr; | ||
| 115 | |||
| 116 | // break; | ||
| 117 | } | ||
| 118 | // fall through: render defaults (if requested) | ||
| 119 | // } | ||
| 120 | /* | ||
| 121 | case model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE: { | ||
| 122 | if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) { | ||
| 123 | auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage(); | ||
| 124 | auto sceneKeyLight = scene->getKeyLight(); | ||
| 125 | auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture(); | ||
| 126 | if (defaultSkyboxAmbientTexture) { | ||
| 127 | sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance()); | ||
| 128 | sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture); | ||
| 129 | } else { | ||
| 130 | static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( | ||
| 131 | "Failed to get a valid Default Skybox Ambient Texture ? probably because it couldn't be find during initialization step"); | ||
| 132 | } | ||
| 133 | // fall through: render defaults skybox | ||
| 134 | } else { | ||
| 135 | break; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | */ | ||
| 139 | |||
| 140 | } | ||
| @@ -0,0 +1,80 @@ | |||
| 1 | // | ||
| 2 | // BackgroundStage.h | ||
| 3 | |||
| 4 | // Created by Sam Gateau on 5/9/2017. | ||
| 5 | // Copyright 2015 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | // | ||
| 10 | |||
| 11 | #ifndef hifi_render_utils_BackgroundStage_h | ||
| 12 | #define hifi_render_utils_BackgroundStage_h | ||
| 13 | |||
| 14 | #include <model/Stage.h> | ||
| 15 | #include <set> | ||
| 16 | #include <unordered_map> | ||
| 17 | #include <render/IndexedContainer.h> | ||
| 18 | |||
| 19 | #include "LightingModel.h" | ||
| 20 | |||
| 21 | |||
| 22 | // Background stage to set up background-related rendering tasks | ||
| 23 | class BackgroundStage { | ||
| 24 | public: | ||
| 25 | using Index = render::indexed_container::Index; | ||
| 26 | static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; | ||
| 27 | static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } | ||
| 28 | |||
| 29 | using BackgroundPointer = model::SunSkyStagePointer; | ||
| 30 | using Backgrounds = render::indexed_container::IndexedPointerVector<model::SunSkyStage>; | ||
| 31 | using BackgroundMap = std::unordered_map<BackgroundPointer, Index>; | ||
| 32 | |||
| 33 | using BackgroundIndices = std::vector<Index>; | ||
| 34 | |||
| 35 | |||
| 36 | Index findBackground(const BackgroundPointer& background) const; | ||
| 37 | Index addBackground(const BackgroundPointer& background); | ||
| 38 | |||
| 39 | BackgroundPointer removeBackground(Index index); | ||
| 40 | |||
| 41 | bool checkBackgroundId(Index index) const { return _backgrounds.checkIndex(index); } | ||
| 42 | |||
| 43 | Index getNumBackgrounds() const { return _backgrounds.getNumElements(); } | ||
| 44 | Index getNumFreeBackgrounds() const { return _backgrounds.getNumFreeIndices(); } | ||
| 45 | Index getNumAllocatedBackgrounds() const { return _backgrounds.getNumAllocatedIndices(); } | ||
| 46 | |||
| 47 | BackgroundPointer getBackground(Index backgroundId) const { | ||
| 48 | return _backgrounds.get(backgroundId); | ||
| 49 | } | ||
| 50 | |||
| 51 | Backgrounds _backgrounds; | ||
| 52 | BackgroundMap _backgroundMap; | ||
| 53 | |||
| 54 | class Frame { | ||
| 55 | public: | ||
| 56 | Frame() {} | ||
| 57 | |||
| 58 | void clear() { _backgrounds.clear(); } | ||
| 59 | |||
| 60 | void pushBackground(BackgroundStage::Index index) { _backgrounds.emplace_back(index); } | ||
| 61 | |||
| 62 | BackgroundStage::BackgroundIndices _backgrounds; | ||
| 63 | }; | ||
| 64 | |||
| 65 | Frame _currentFrame; | ||
| 66 | }; | ||
| 67 | using BackgroundStagePointer = std::shared_ptr<BackgroundStage>; | ||
| 68 | |||
| 69 | |||
| 70 | class DrawBackgroundStage { | ||
| 71 | public: | ||
| 72 | using Inputs = LightingModelPointer; | ||
| 73 | using JobModel = render::Job::ModelI<DrawBackgroundStage, Inputs>; | ||
| 74 | |||
| 75 | void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); | ||
| 76 | |||
| 77 | protected: | ||
| 78 | }; | ||
| 79 | |||
| 80 | #endif | ||
| @@ -18,6 +18,7 @@ · 17 unmodified lines | |||
| 18 | #include <gpu/Batch.h> | 18 | #include <gpu/Batch.h> |
| 19 | #include <gpu/Context.h> | 19 | #include <gpu/Context.h> |
| 20 | 20 | ||
| 21 | #include "StencilMaskPass.h" | ||
| 21 | #include "AbstractViewStateInterface.h" | 22 | #include "AbstractViewStateInterface.h" |
| 22 | #include "GeometryCache.h" | 23 | #include "GeometryCache.h" |
| 23 | #include "TextureCache.h" | 24 | #include "TextureCache.h" |
| @@ -27,18 +28,15 @@ · 3 unmodified lines | |||
| 27 | #include "deferred_light_point_vert.h" | 28 | #include "deferred_light_point_vert.h" |
| 28 | #include "deferred_light_spot_vert.h" | 29 | #include "deferred_light_spot_vert.h" |
| 29 | 30 | ||
| 30 | #include "directional_light_frag.h" | ||
| 31 | #include "directional_ambient_light_frag.h" | 31 | #include "directional_ambient_light_frag.h" |
| 32 | #include "directional_skybox_light_frag.h" | 32 | #include "directional_skybox_light_frag.h" |
| 33 | 33 | ||
| 34 | #include "directional_light_shadow_frag.h" | ||
| 35 | #include "directional_ambient_light_shadow_frag.h" | 34 | #include "directional_ambient_light_shadow_frag.h" |
| 36 | #include "directional_skybox_light_shadow_frag.h" | 35 | #include "directional_skybox_light_shadow_frag.h" |
| 37 | 36 | ||
| 38 | #include "local_lights_shading_frag.h" | 37 | #include "local_lights_shading_frag.h" |
| 39 | #include "local_lights_drawOutline_frag.h" | 38 | #include "local_lights_drawOutline_frag.h" |
| 40 | #include "point_light_frag.h" | 39 | |
| 41 | #include "spot_light_frag.h" | ||
| 42 | 40 | ||
| 43 | using namespace render; | 41 | using namespace render; |
| 44 | 42 | ||
| @@ -82,48 +80,26 @@ enum DeferredShader_BufferSlot { · 37 unmodified lines | |||
| 82 | }; | 80 | }; |
| 83 | 81 | ||
| 84 | static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); | 82 | static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); |
| 85 | static void loadLightVolumeProgram(const char* vertSource, const char* fragSource, bool front, gpu::PipelinePointer& program, LightLocationsPtr& locations); | ||
| 86 | |||
| 87 | const char no_light_frag[] = | ||
| 88 | R"SCRIBE( | ||
| 89 | out vec4 _fragColor; | ||
| 90 | |||
| 91 | void main(void) { | ||
| 92 | _fragColor = vec4(1.0, 1.0, 1.0, 1.0); | ||
| 93 | } | ||
| 94 | )SCRIBE" | ||
| 95 | ; | ||
| 96 | 83 | ||
| 97 | void DeferredLightingEffect::init() { | 84 | void DeferredLightingEffect::init() { |
| 98 | _directionalLightLocations = std::make_shared<LightLocations>(); | ||
| 99 | _directionalAmbientSphereLightLocations = std::make_shared<LightLocations>(); | 85 | _directionalAmbientSphereLightLocations = std::make_shared<LightLocations>(); |
| 100 | _directionalSkyboxLightLocations = std::make_shared<LightLocations>(); | 86 | _directionalSkyboxLightLocations = std::make_shared<LightLocations>(); |
| 101 | 87 | ||
| 102 | _directionalLightShadowLocations = std::make_shared<LightLocations>(); | ||
| 103 | _directionalAmbientSphereLightShadowLocations = std::make_shared<LightLocations>(); | 88 | _directionalAmbientSphereLightShadowLocations = std::make_shared<LightLocations>(); |
| 104 | _directionalSkyboxLightShadowLocations = std::make_shared<LightLocations>(); | 89 | _directionalSkyboxLightShadowLocations = std::make_shared<LightLocations>(); |
| 105 | 90 | ||
| 106 | _localLightLocations = std::make_shared<LightLocations>(); | 91 | _localLightLocations = std::make_shared<LightLocations>(); |
| 107 | _localLightOutlineLocations = std::make_shared<LightLocations>(); | 92 | _localLightOutlineLocations = std::make_shared<LightLocations>(); |
| 108 | _pointLightLocations = std::make_shared<LightLocations>(); | ||
| 109 | _spotLightLocations = std::make_shared<LightLocations>(); | ||
| 110 | 93 | ||
| 111 | loadLightProgram(deferred_light_vert, directional_light_frag, false, _directionalLight, _directionalLightLocations); | ||
| 112 | loadLightProgram(deferred_light_vert, directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); | 94 | loadLightProgram(deferred_light_vert, directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); |
| 113 | loadLightProgram(deferred_light_vert, directional_skybox_light_frag, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); | 95 | loadLightProgram(deferred_light_vert, directional_skybox_light_frag, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); |
| 114 | 96 | ||
| 115 | loadLightProgram(deferred_light_vert, directional_light_shadow_frag, false, _directionalLightShadow, _directionalLightShadowLocations); | ||
| 116 | loadLightProgram(deferred_light_vert, directional_ambient_light_shadow_frag, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); | 97 | loadLightProgram(deferred_light_vert, directional_ambient_light_shadow_frag, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); |
| 117 | loadLightProgram(deferred_light_vert, directional_skybox_light_shadow_frag, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); | 98 | loadLightProgram(deferred_light_vert, directional_skybox_light_shadow_frag, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); |
| 118 | 99 | ||
| 119 | loadLightProgram(deferred_light_vert, local_lights_shading_frag, true, _localLight, _localLightLocations); | 100 | loadLightProgram(deferred_light_vert, local_lights_shading_frag, true, _localLight, _localLightLocations); |
| 120 | loadLightProgram(deferred_light_vert, local_lights_drawOutline_frag, true, _localLightOutline, _localLightOutlineLocations); | 101 | loadLightProgram(deferred_light_vert, local_lights_drawOutline_frag, true, _localLightOutline, _localLightOutlineLocations); |
| 121 | 102 | ||
| 122 | loadLightVolumeProgram(deferred_light_point_vert, no_light_frag, false, _pointLightBack, _pointLightLocations); | ||
| 123 | loadLightVolumeProgram(deferred_light_point_vert, no_light_frag, true, _pointLightFront, _pointLightLocations); | ||
| 124 | loadLightVolumeProgram(deferred_light_spot_vert, no_light_frag, false, _spotLightBack, _spotLightLocations); | ||
| 125 | loadLightVolumeProgram(deferred_light_spot_vert, no_light_frag, true, _spotLightFront, _spotLightLocations); | ||
| 126 | |||
| 127 | // Light Stage and clusters | 103 | // Light Stage and clusters |
| 128 | _lightStage = std::make_shared<LightStage>(); | 104 | _lightStage = std::make_shared<LightStage>(); |
| 129 | 105 | ||
| @@ -141,79 +117,69 @@ void DeferredLightingEffect::init() { · 11 unmodified lines | |||
| 141 | _globalLights.push_back(_lightStage->addLight(lp)); | 117 | _globalLights.push_back(_lightStage->addLight(lp)); |
| 142 | _lightStage->addShadow(_globalLights[0]); | 118 | _lightStage->addShadow(_globalLights[0]); |
| 143 | 119 | ||
| 144 | } | ||
| 145 | 120 | ||
| 146 | void DeferredLightingEffect::addLight(const model::LightPointer& light) { | 121 | _backgroundStage = std::make_shared<BackgroundStage>(); |
| 147 | assert(light); | ||
| 148 | auto lightID = _lightStage->addLight(light); | ||
| 149 | if (light->getType() == model::Light::POINT) { | ||
| 150 | _pointLights.push_back(lightID); | ||
| 151 | } else { | ||
| 152 | _spotLights.push_back(lightID); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | 122 | ||
| 123 | auto textureCache = DependencyManager::get<TextureCache>(); | ||
| 156 | 124 | ||
| 157 | void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color, | 125 | { |
| 158 | float intensity, float falloffRadius) { | 126 | PROFILE_RANGE(render, "Process Default Skybox"); |
| 159 | addSpotLight(position, radius, color, intensity, falloffRadius); | 127 | auto textureCache = DependencyManager::get<TextureCache>(); |
| 160 | } | ||
| 161 | 128 | ||
| 162 | void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color, | 129 | auto skyboxUrl = PathUtils::resourcesPath().toStdString() + "images/Default-Sky-9-cubemap.ktx"; |
| 163 | float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) { | 130 | |
| 164 | 131 | _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl); | |
| 165 | unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size()); | 132 | _defaultSkyboxAmbientTexture = _defaultSkyboxTexture; |
| 166 | if (lightID >= _allocatedLights.size()) { | 133 | |
| 167 | _allocatedLights.push_back(std::make_shared<model::Light>()); | 134 | _defaultSkybox->setCubemap(_defaultSkyboxTexture); |
| 168 | } | 135 | } |
| 169 | model::LightPointer lp = _allocatedLights[lightID]; | ||
| 170 | 136 | ||
| 171 | lp->setPosition(position); | ||
| 172 | lp->setMaximumRadius(radius); | ||
| 173 | lp->setColor(color); | ||
| 174 | lp->setIntensity(intensity); | ||
| 175 | lp->setFalloffRadius(falloffRadius); | ||
| 176 | 137 | ||
| 177 | if (exponent == 0.0f && cutoff == PI) { | 138 | lp->setAmbientIntensity(0.5f); |
| 178 | lp->setType(model::Light::POINT); | 139 | lp->setAmbientMap(_defaultSkyboxAmbientTexture); |
| 179 | _pointLights.push_back(lightID); | 140 | auto irradianceSH = _defaultSkyboxAmbientTexture->getIrradiance(); |
| 180 | 141 | if (irradianceSH) { | |
| 181 | } else { | 142 | lp->setAmbientSphere((*irradianceSH)); |
| 182 | lp->setOrientation(orientation); | ||
| 183 | lp->setSpotAngle(cutoff); | ||
| 184 | lp->setSpotExponent(exponent); | ||
| 185 | lp->setType(model::Light::SPOT); | ||
| 186 | _spotLights.push_back(lightID); | ||
| 187 | } | 143 | } |
| 188 | } | 144 | } |
| 189 | 145 | ||
| 190 | void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { | 146 | void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { |
| 191 | PerformanceTimer perfTimer("DLE->setupBatch()"); | 147 | PerformanceTimer perfTimer("DLE->setupBatch()"); |
| 192 | auto keyLight = _allocatedLights[_globalLights.front()]; | 148 | model::LightPointer keySunLight; |
| 149 | if (_lightStage && _lightStage->_currentFrame._sunLights.size()) { | ||
| 150 | keySunLight = _lightStage->getLight(_lightStage->_currentFrame._sunLights.front()); | ||
| 151 | } else { | ||
| 152 | keySunLight = _allocatedLights[_globalLights.front()]; | ||
| 153 | } | ||
| 154 | |||
| 155 | model::LightPointer keyAmbiLight; | ||
| 156 | if (_lightStage && _lightStage->_currentFrame._ambientLights.size()) { | ||
| 157 | keyAmbiLight = _lightStage->getLight(_lightStage->_currentFrame._ambientLights.front()); | ||
| 158 | } else { | ||
| 159 | keyAmbiLight = _allocatedLights[_globalLights.front()]; | ||
| 160 | } | ||
| 193 | 161 | ||
| 194 | if (lightBufferUnit >= 0) { | 162 | if (lightBufferUnit >= 0) { |
| 195 | batch.setUniformBuffer(lightBufferUnit, keyLight->getLightSchemaBuffer()); | 163 | batch.setUniformBuffer(lightBufferUnit, keySunLight->getLightSchemaBuffer()); |
| 196 | } | 164 | } |
| 197 | if (keyLight->hasAmbient() && (ambientBufferUnit >= 0)) { | 165 | if (ambientBufferUnit >= 0) { |
| 198 | batch.setUniformBuffer(ambientBufferUnit, keyLight->getAmbientSchemaBuffer()); | 166 | batch.setUniformBuffer(ambientBufferUnit, keyAmbiLight->getAmbientSchemaBuffer()); |
| 199 | } | 167 | } |
| 200 | 168 | ||
| 201 | if (keyLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) { | 169 | if (keyAmbiLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) { |
| 202 | batch.setResourceTexture(skyboxCubemapUnit, keyLight->getAmbientMap()); | 170 | batch.setResourceTexture(skyboxCubemapUnit, keyAmbiLight->getAmbientMap()); |
| 203 | } | 171 | } |
| 204 | } | 172 | } |
| 205 | 173 | ||
| 206 | void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { | 174 | void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { |
| 207 | auto keyLight = _allocatedLights[_globalLights.front()]; | ||
| 208 | |||
| 209 | if (lightBufferUnit >= 0) { | 175 | if (lightBufferUnit >= 0) { |
| 210 | batch.setUniformBuffer(lightBufferUnit, nullptr); | 176 | batch.setUniformBuffer(lightBufferUnit, nullptr); |
| 211 | } | 177 | } |
| 212 | if (keyLight->hasAmbient() && (ambientBufferUnit >= 0)) { | 178 | if ((ambientBufferUnit >= 0)) { |
| 213 | batch.setUniformBuffer(ambientBufferUnit, nullptr); | 179 | batch.setUniformBuffer(ambientBufferUnit, nullptr); |
| 214 | } | 180 | } |
| 215 | 181 | ||
| 216 | if (keyLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) { | 182 | if ((skyboxCubemapUnit >= 0)) { |
| 217 | batch.setResourceTexture(skyboxCubemapUnit, nullptr); | 183 | batch.setResourceTexture(skyboxCubemapUnit, nullptr); |
| 218 | } | 184 | } |
| 219 | } | 185 | } |
| @@ -277,7 +243,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo · 57 unmodified lines | |||
| 277 | state->setColorWriteMask(true, true, true, false); | 243 | state->setColorWriteMask(true, true, true, false); |
| 278 | 244 | ||
| 279 | if (lightVolume) { | 245 | if (lightVolume) { |
| 280 | state->setStencilTest(true, 0x00, gpu::State::StencilTest(1, 0xFF, gpu::LESS_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | 246 | PrepareStencil::testShape(*state); |
| 281 | 247 | ||
| 282 | state->setCullMode(gpu::State::CULL_BACK); | 248 | state->setCullMode(gpu::State::CULL_BACK); |
| 283 | // state->setCullMode(gpu::State::CULL_FRONT); | 249 | // state->setCullMode(gpu::State::CULL_FRONT); |
| @@ -290,7 +256,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo · 6 unmodified lines | |||
| 290 | 256 | ||
| 291 | } else { | 257 | } else { |
| 292 | // Stencil test all the light passes for objects pixels only, not the background | 258 | // Stencil test all the light passes for objects pixels only, not the background |
| 293 | state->setStencilTest(true, 0x00, gpu::State::StencilTest(0, 0x01, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | 259 | PrepareStencil::testShape(*state); |
| 294 | 260 | ||
| 295 | state->setCullMode(gpu::State::CULL_BACK); | 261 | state->setCullMode(gpu::State::CULL_BACK); |
| 296 | // additive blending | 262 | // additive blending |
| @@ -300,49 +266,21 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo · 3 unmodified lines | |||
| 300 | 266 | ||
| 301 | } | 267 | } |
| 302 | 268 | ||
| 303 | |||
| 304 | static void loadLightVolumeProgram(const char* vertSource, const char* fragSource, bool front, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { | ||
| 305 | gpu::ShaderPointer program = makeLightProgram(vertSource, fragSource, locations); | ||
| 306 | |||
| 307 | auto state = std::make_shared<gpu::State>(); | ||
| 308 | |||
| 309 | // Stencil test all the light passes for objects pixels only, not the background | ||
| 310 | |||
| 311 | if (front) { | ||
| 312 | state->setCullMode(gpu::State::CULL_BACK); | ||
| 313 | state->setDepthTest(true, false, gpu::LESS_EQUAL); | ||
| 314 | state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_DECR, gpu::State::STENCIL_OP_KEEP)); | ||
| 315 | |||
| 316 | // state->setDepthClampEnable(true); | ||
| 317 | // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases | ||
| 318 | // additive blending | ||
| 319 | // state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | ||
| 320 | |||
| 321 | //state->setColorWriteMask(true, true, true, false); | ||
| 322 | state->setColorWriteMask(false, false, false, false); | ||
| 323 | } else { | ||
| 324 | state->setCullMode(gpu::State::CULL_FRONT); | ||
| 325 | state->setDepthTest(true, false, gpu::LESS_EQUAL); | ||
| 326 | state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_INCR, gpu::State::STENCIL_OP_KEEP)); | ||
| 327 | // additive blending | ||
| 328 | // state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | ||
| 329 | // state->setColorWriteMask(true, true, true, false); | ||
| 330 | state->setColorWriteMask(false, false, false, false); | ||
| 331 | } | ||
| 332 | pipeline = gpu::Pipeline::create(program, state); | ||
| 333 | |||
| 334 | } | ||
| 335 | |||
| 336 | void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light) { | 269 | void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light) { |
| 337 | auto globalLight = _allocatedLights.front(); | 270 | /* auto globalLight = _allocatedLights.front(); |
| 338 | globalLight->setDirection(light->getDirection()); | 271 | globalLight->setDirection(light->getDirection()); |
| 339 | globalLight->setColor(light->getColor()); | 272 | globalLight->setColor(light->getColor()); |
| 340 | globalLight->setIntensity(light->getIntensity()); | 273 | globalLight->setIntensity(light->getIntensity()); |
| 341 | globalLight->setAmbientIntensity(light->getAmbientIntensity()); | 274 | globalLight->setAmbientIntensity(light->getAmbientIntensity()); |
| 342 | globalLight->setAmbientSphere(light->getAmbientSphere()); | 275 | globalLight->setAmbientSphere(light->getAmbientSphere()); |
| 343 | globalLight->setAmbientMap(light->getAmbientMap()); | 276 | globalLight->setAmbientMap(light->getAmbientMap());*/ |
| 277 | } | ||
| 278 | |||
| 279 | const model::LightPointer& DeferredLightingEffect::getGlobalLight() const { | ||
| 280 | return _allocatedLights.front(); | ||
| 344 | } | 281 | } |
| 345 | 282 | ||
| 283 | |||
| 346 | #include <shared/Shapes.h> | 284 | #include <shared/Shapes.h> |
| 347 | 285 | ||
| 348 | model::MeshPointer DeferredLightingEffect::getPointLightMesh() { | 286 | model::MeshPointer DeferredLightingEffect::getPointLightMesh() { |
| @@ -540,7 +478,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input · 191 unmodified lines | |||
| 540 | gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 | | 478 | gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 | |
| 541 | gpu::Framebuffer::BUFFER_DEPTH | | 479 | gpu::Framebuffer::BUFFER_DEPTH | |
| 542 | gpu::Framebuffer::BUFFER_STENCIL, | 480 | gpu::Framebuffer::BUFFER_STENCIL, |
| 543 | vec4(vec3(0), 0), 1.0, 0.0, true); | 481 | vec4(vec3(0), 0), 1.0, 1, true); |
| 544 | 482 | ||
| 545 | // For the rest of the rendering, bind the lighting model | 483 | // For the rest of the rendering, bind the lighting model |
| 546 | batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); | 484 | batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); |
| @@ -566,10 +504,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, · 19 unmodified lines | |||
| 566 | { | 504 | { |
| 567 | // Framebuffer copy operations cannot function as multipass stereo operations. | 505 | // Framebuffer copy operations cannot function as multipass stereo operations. |
| 568 | batch.enableStereo(false); | 506 | batch.enableStereo(false); |
| 569 | 507 | ||
| 570 | // perform deferred lighting, rendering to free fbo | ||
| 571 | auto framebufferCache = DependencyManager::get<FramebufferCache>(); | ||
| 572 | |||
| 573 | auto textureCache = DependencyManager::get<TextureCache>(); | 508 | auto textureCache = DependencyManager::get<TextureCache>(); |
| 574 | auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>(); | 509 | auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>(); |
| 575 | 510 | ||
| @@ -624,8 +559,8 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, · 48 unmodified lines | |||
| 624 | batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map); | 559 | batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map); |
| 625 | } | 560 | } |
| 626 | 561 | ||
| 627 | auto& program = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadow : deferredLightingEffect->_directionalLight; | 562 | auto& program = deferredLightingEffect->_directionalSkyboxLight; |
| 628 | LightLocationsPtr locations = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadowLocations : deferredLightingEffect->_directionalLightLocations; | 563 | LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; |
| 629 | const auto& keyLight = deferredLightingEffect->_allocatedLights[deferredLightingEffect->_globalLights.front()]; | 564 | const auto& keyLight = deferredLightingEffect->_allocatedLights[deferredLightingEffect->_globalLights.front()]; |
| 630 | 565 | ||
| 631 | // Setup the global directional pass pipeline | 566 | // Setup the global directional pass pipeline |
| @@ -772,16 +707,6 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex · 140 unmodified lines | |||
| 772 | batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, nullptr); | 707 | batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, nullptr); |
| 773 | 708 | ||
| 774 | } | 709 | } |
| 775 | |||
| 776 | auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>(); | ||
| 777 | |||
| 778 | // End of the Lighting pass | ||
| 779 | if (!deferredLightingEffect->_pointLights.empty()) { | ||
| 780 | deferredLightingEffect->_pointLights.clear(); | ||
| 781 | } | ||
| 782 | if (!deferredLightingEffect->_spotLights.empty()) { | ||
| 783 | deferredLightingEffect->_spotLights.clear(); | ||
| 784 | } | ||
| 785 | } | 710 | } |
| 786 | 711 | ||
| 787 | RenderDeferred::RenderDeferred() { | 712 | RenderDeferred::RenderDeferred() { |
| @@ -20,6 +20,8 @@ · 19 unmodified lines | |||
| 20 | #include "model/Light.h" | 20 | #include "model/Light.h" |
| 21 | #include "model/Geometry.h" | 21 | #include "model/Geometry.h" |
| 22 | 22 | ||
| 23 | #include <procedural/ProceduralSkybox.h> | ||
| 24 | |||
| 23 | #include <render/CullTask.h> | 25 | #include <render/CullTask.h> |
| 24 | 26 | ||
| 25 | #include "DeferredFrameTransform.h" | 27 | #include "DeferredFrameTransform.h" |
| @@ -28,11 +30,13 @@ · 2 unmodified lines | |||
| 28 | 30 | ||
| 29 | #include "LightStage.h" | 31 | #include "LightStage.h" |
| 30 | #include "LightClusters.h" | 32 | #include "LightClusters.h" |
| 33 | #include "BackgroundStage.h" | ||
| 31 | 34 | ||
| 32 | #include "SurfaceGeometryPass.h" | 35 | #include "SurfaceGeometryPass.h" |
| 33 | #include "SubsurfaceScattering.h" | 36 | #include "SubsurfaceScattering.h" |
| 34 | #include "AmbientOcclusionEffect.h" | 37 | #include "AmbientOcclusionEffect.h" |
| 35 | 38 | ||
| 39 | |||
| 36 | class RenderArgs; | 40 | class RenderArgs; |
| 37 | struct LightLocations; | 41 | struct LightLocations; |
| 38 | using LightLocationsPtr = std::shared_ptr<LightLocations>; | 42 | using LightLocationsPtr = std::shared_ptr<LightLocations>; |
| @@ -43,34 +47,30 @@ class DeferredLightingEffect : public Dependency { · 4 unmodified lines | |||
| 43 | 47 | ||
| 44 | public: | 48 | public: |
| 45 | void init(); | 49 | void init(); |
| 46 | 50 | ||
| 47 | void addLight(const model::LightPointer& light); | ||
| 48 | |||
| 49 | /// Adds a point light to render for the current frame. | ||
| 50 | void addPointLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(0.0f, 0.0f, 0.0f), | ||
| 51 | float intensity = 0.5f, float falloffRadius = 0.01f); | ||
| 52 | |||
| 53 | /// Adds a spot light to render for the current frame. | ||
| 54 | void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f), | ||
| 55 | float intensity = 0.5f, float falloffRadius = 0.01f, | ||
| 56 | const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI); | ||
| 57 | |||
| 58 | void setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); | 51 | void setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); |
| 59 | void unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); | 52 | void unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); |
| 60 | 53 | ||
| 61 | // update global lighting | 54 | // update global lighting |
| 62 | void setGlobalLight(const model::LightPointer& light); | 55 | void setGlobalLight(const model::LightPointer& light); |
| 56 | const model::LightPointer& getGlobalLight() const; | ||
| 63 | 57 | ||
| 64 | const LightStagePointer getLightStage() { return _lightStage; } | 58 | const LightStagePointer& getLightStage() { return _lightStage; } |
| 59 | const BackgroundStagePointer& getBackgroundStage() { return _backgroundStage; } | ||
| 65 | 60 | ||
| 66 | void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; }; | 61 | void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; }; |
| 67 | void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; } | 62 | void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; } |
| 68 | bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; } | 63 | bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; } |
| 69 | 64 | ||
| 65 | model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; } | ||
| 66 | gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; } | ||
| 67 | gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; } | ||
| 68 | |||
| 70 | private: | 69 | private: |
| 71 | DeferredLightingEffect() = default; | 70 | DeferredLightingEffect() = default; |
| 72 | 71 | ||
| 73 | LightStagePointer _lightStage; | 72 | LightStagePointer _lightStage; |
| 73 | BackgroundStagePointer _backgroundStage; | ||
| 74 | 74 | ||
| 75 | bool _shadowMapEnabled{ false }; | 75 | bool _shadowMapEnabled{ false }; |
| 76 | bool _ambientOcclusionEnabled{ false }; | 76 | bool _ambientOcclusionEnabled{ false }; |
| @@ -82,39 +82,30 @@ private: · 5 unmodified lines | |||
| 82 | 82 | ||
| 83 | gpu::PipelinePointer _directionalSkyboxLight; | 83 | gpu::PipelinePointer _directionalSkyboxLight; |
| 84 | gpu::PipelinePointer _directionalAmbientSphereLight; | 84 | gpu::PipelinePointer _directionalAmbientSphereLight; |
| 85 | gpu::PipelinePointer _directionalLight; | ||
| 86 | 85 | ||
| 87 | gpu::PipelinePointer _directionalSkyboxLightShadow; | 86 | gpu::PipelinePointer _directionalSkyboxLightShadow; |
| 88 | gpu::PipelinePointer _directionalAmbientSphereLightShadow; | 87 | gpu::PipelinePointer _directionalAmbientSphereLightShadow; |
| 89 | gpu::PipelinePointer _directionalLightShadow; | ||
| 90 | 88 | ||
| 91 | gpu::PipelinePointer _localLight; | 89 | gpu::PipelinePointer _localLight; |
| 92 | gpu::PipelinePointer _localLightOutline; | 90 | gpu::PipelinePointer _localLightOutline; |
| 93 | 91 | ||
| 94 | gpu::PipelinePointer _pointLightBack; | ||
| 95 | gpu::PipelinePointer _pointLightFront; | ||
| 96 | gpu::PipelinePointer _spotLightBack; | ||
| 97 | gpu::PipelinePointer _spotLightFront; | ||
| 98 | |||
| 99 | LightLocationsPtr _directionalSkyboxLightLocations; | 92 | LightLocationsPtr _directionalSkyboxLightLocations; |
| 100 | LightLocationsPtr _directionalAmbientSphereLightLocations; | 93 | LightLocationsPtr _directionalAmbientSphereLightLocations; |
| 101 | LightLocationsPtr _directionalLightLocations; | ||
| 102 | 94 | ||
| 103 | LightLocationsPtr _directionalSkyboxLightShadowLocations; | 95 | LightLocationsPtr _directionalSkyboxLightShadowLocations; |
| 104 | LightLocationsPtr _directionalAmbientSphereLightShadowLocations; | 96 | LightLocationsPtr _directionalAmbientSphereLightShadowLocations; |
| 105 | LightLocationsPtr _directionalLightShadowLocations; | ||
| 106 | 97 | ||
| 107 | LightLocationsPtr _localLightLocations; | 98 | LightLocationsPtr _localLightLocations; |
| 108 | LightLocationsPtr _localLightOutlineLocations; | 99 | LightLocationsPtr _localLightOutlineLocations; |
| 109 | LightLocationsPtr _pointLightLocations; | ||
| 110 | LightLocationsPtr _spotLightLocations; | ||
| 111 | 100 | ||
| 112 | using Lights = std::vector<model::LightPointer>; | 101 | using Lights = std::vector<model::LightPointer>; |
| 113 | 102 | ||
| 114 | Lights _allocatedLights; | 103 | Lights _allocatedLights; |
| 115 | std::vector<int> _globalLights; | 104 | std::vector<int> _globalLights; |
| 116 | std::vector<int> _pointLights; | 105 | |
| 117 | std::vector<int> _spotLights; | 106 | model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() }; |
| 107 | gpu::TexturePointer _defaultSkyboxTexture; | ||
| 108 | gpu::TexturePointer _defaultSkyboxAmbientTexture; | ||
| 118 | 109 | ||
| 119 | friend class LightClusteringPass; | 110 | friend class LightClusteringPass; |
| 120 | friend class RenderDeferredSetup; | 111 | friend class RenderDeferredSetup; |
| @@ -24,6 +24,7 @@ · 23 unmodified lines | |||
| 24 | 24 | ||
| 25 | #include "TextureCache.h" | 25 | #include "TextureCache.h" |
| 26 | #include "RenderUtilsLogging.h" | 26 | #include "RenderUtilsLogging.h" |
| 27 | #include "StencilMaskPass.h" | ||
| 27 | 28 | ||
| 28 | #include "gpu/StandardShaderLib.h" | 29 | #include "gpu/StandardShaderLib.h" |
| 29 | 30 | ||
| @@ -68,7 +69,7 @@ std::vector<vec3> polygon() { · 38 unmodified lines | |||
| 68 | std::vector<vec3> result; | 69 | std::vector<vec3> result; |
| 69 | result.reserve(SIDES); | 70 | result.reserve(SIDES); |
| 70 | double angleIncrement = 2.0 * M_PI / SIDES; | 71 | double angleIncrement = 2.0 * M_PI / SIDES; |
| 71 | for (size_t i = 0; i < SIDES; ++i) { | 72 | for (size_t i = 0; i < SIDES; i++) { |
| 72 | double angle = (double)i * angleIncrement; | 73 | double angle = (double)i * angleIncrement; |
| 73 | result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 }); | 74 | result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 }); |
| 74 | } | 75 | } |
| @@ -171,20 +172,20 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid<N · 96 unmodified lines | |||
| 171 | vertices.reserve(N * faceCount * 2); | 172 | vertices.reserve(N * faceCount * 2); |
| 172 | solidIndices.reserve(faceIndexCount * faceCount); | 173 | solidIndices.reserve(faceIndexCount * faceCount); |
| 173 | 174 | ||
| 174 | for (size_t f = 0; f < faceCount; ++f) { | 175 | for (size_t f = 0; f < faceCount; f++) { |
| 175 | const Face<N>& face = shape.faces[f]; | 176 | const Face<N>& face = shape.faces[f]; |
| 176 | // Compute the face normal | 177 | // Compute the face normal |
| 177 | vec3 faceNormal = shape.getFaceNormal(f); | 178 | vec3 faceNormal = shape.getFaceNormal(f); |
| 178 | 179 | ||
| 179 | // Create the vertices for the face | 180 | // Create the vertices for the face |
| 180 | for (Index i = 0; i < N; ++i) { | 181 | for (Index i = 0; i < N; i++) { |
| 181 | Index originalIndex = face[i]; | 182 | Index originalIndex = face[i]; |
| 182 | vertices.push_back(shape.vertices[originalIndex]); | 183 | vertices.push_back(shape.vertices[originalIndex]); |
| 183 | vertices.push_back(faceNormal); | 184 | vertices.push_back(faceNormal); |
| 184 | } | 185 | } |
| 185 | 186 | ||
| 186 | // Create the wire indices for unseen edges | 187 | // Create the wire indices for unseen edges |
| 187 | for (Index i = 0; i < N; ++i) { | 188 | for (Index i = 0; i < N; i++) { |
| 188 | Index a = i; | 189 | Index a = i; |
| 189 | Index b = (i + 1) % N; | 190 | Index b = (i + 1) % N; |
| 190 | auto token = indexToken(face[a], face[b]); | 191 | auto token = indexToken(face[a], face[b]); |
| @@ -196,7 +197,7 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid<N · 5 unmodified lines | |||
| 196 | } | 197 | } |
| 197 | 198 | ||
| 198 | // Create the solid face indices | 199 | // Create the solid face indices |
| 199 | for (Index i = 0; i < N - 2; ++i) { | 200 | for (Index i = 0; i < N - 2; i++) { |
| 200 | solidIndices.push_back(0 + baseVertex); | 201 | solidIndices.push_back(0 + baseVertex); |
| 201 | solidIndices.push_back(i + 1 + baseVertex); | 202 | solidIndices.push_back(i + 1 + baseVertex); |
| 202 | solidIndices.push_back(i + 2 + baseVertex); | 203 | solidIndices.push_back(i + 2 + baseVertex); |
| @@ -228,10 +229,10 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid · 25 unmodified lines | |||
| 228 | 229 | ||
| 229 | solidIndices.reserve(faceIndexCount * faceCount); | 230 | solidIndices.reserve(faceIndexCount * faceCount); |
| 230 | 231 | ||
| 231 | for (size_t f = 0; f < faceCount; ++f) { | 232 | for (size_t f = 0; f < faceCount; f++) { |
| 232 | const Face<N>& face = shape.faces[f]; | 233 | const Face<N>& face = shape.faces[f]; |
| 233 | // Create the wire indices for unseen edges | 234 | // Create the wire indices for unseen edges |
| 234 | for (Index i = 0; i < N; ++i) { | 235 | for (Index i = 0; i < N; i++) { |
| 235 | Index a = face[i]; | 236 | Index a = face[i]; |
| 236 | Index b = face[(i + 1) % N]; | 237 | Index b = face[(i + 1) % N]; |
| 237 | auto token = indexToken(a, b); | 238 | auto token = indexToken(a, b); |
| @@ -243,7 +244,7 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid · 5 unmodified lines | |||
| 243 | } | 244 | } |
| 244 | 245 | ||
| 245 | // Create the solid face indices | 246 | // Create the solid face indices |
| 246 | for (Index i = 0; i < N - 2; ++i) { | 247 | for (Index i = 0; i < N - 2; i++) { |
| 247 | solidIndices.push_back(face[i] + baseVertex); | 248 | solidIndices.push_back(face[i] + baseVertex); |
| 248 | solidIndices.push_back(face[i + 1] + baseVertex); | 249 | solidIndices.push_back(face[i + 1] + baseVertex); |
| 249 | solidIndices.push_back(face[i + 2] + baseVertex); | 250 | solidIndices.push_back(face[i + 2] + baseVertex); |
| @@ -255,23 +256,30 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid · 5 unmodified lines | |||
| 255 | } | 256 | } |
| 256 | 257 | ||
| 257 | template <uint32_t N> | 258 | template <uint32_t N> |
| 258 | void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { | 259 | void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer, bool isConical = false) { |
| 259 | using namespace geometry; | 260 | using namespace geometry; |
| 260 | Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); | 261 | Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); |
| 261 | VertexVector vertices; | 262 | VertexVector vertices; |
| 262 | IndexVector solidIndices, wireIndices; | 263 | IndexVector solidIndices, wireIndices; |
| 263 | 264 | ||
| 264 | // Top and bottom faces | 265 | // Top (if not conical) and bottom faces |
| 265 | std::vector<vec3> shape = polygon<N>(); | 266 | std::vector<vec3> shape = polygon<N>(); |
| 266 | for (const vec3& v : shape) { | 267 | if (isConical) { |
| 267 | vertices.push_back(vec3(v.x, 0.5f, v.z)); | 268 | for (uint32_t i = 0; i < N; i++) { |
| 268 | vertices.push_back(vec3(0, 1, 0)); | 269 | vertices.push_back(vec3(0.0f, 0.5f, 0.0f)); |
| 270 | vertices.push_back(vec3(0.0f, 1.0f, 0.0f)); | ||
| 271 | } | ||
| 272 | } else { | ||
| 273 | for (const vec3& v : shape) { | ||
| 274 | vertices.push_back(vec3(v.x, 0.5f, v.z)); | ||
| 275 | vertices.push_back(vec3(0.0f, 1.0f, 0.0f)); | ||
| 276 | } | ||
| 269 | } | 277 | } |
| 270 | for (const vec3& v : shape) { | 278 | for (const vec3& v : shape) { |
| 271 | vertices.push_back(vec3(v.x, -0.5f, v.z)); | 279 | vertices.push_back(vec3(v.x, -0.5f, v.z)); |
| 272 | vertices.push_back(vec3(0, -1, 0)); | 280 | vertices.push_back(vec3(0.0f, -1.0f, 0.0f)); |
| 273 | } | 281 | } |
| 274 | for (uint32_t i = 2; i < N; ++i) { | 282 | for (uint32_t i = 2; i < N; i++) { |
| 275 | solidIndices.push_back(baseVertex + 0); | 283 | solidIndices.push_back(baseVertex + 0); |
| 276 | solidIndices.push_back(baseVertex + i); | 284 | solidIndices.push_back(baseVertex + i); |
| 277 | solidIndices.push_back(baseVertex + i - 1); | 285 | solidIndices.push_back(baseVertex + i - 1); |
| @@ -279,7 +287,7 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver · 1 unmodified line | |||
| 279 | solidIndices.push_back(baseVertex + i + N - 1); | 287 | solidIndices.push_back(baseVertex + i + N - 1); |
| 280 | solidIndices.push_back(baseVertex + i + N); | 288 | solidIndices.push_back(baseVertex + i + N); |
| 281 | } | 289 | } |
| 282 | for (uint32_t i = 1; i <= N; ++i) { | 290 | for (uint32_t i = 1; i <= N; i++) { |
| 283 | wireIndices.push_back(baseVertex + (i % N)); | 291 | wireIndices.push_back(baseVertex + (i % N)); |
| 284 | wireIndices.push_back(baseVertex + i - 1); | 292 | wireIndices.push_back(baseVertex + i - 1); |
| 285 | wireIndices.push_back(baseVertex + (i % N) + N); | 293 | wireIndices.push_back(baseVertex + (i % N) + N); |
| @@ -289,12 +297,12 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver · 3 unmodified lines | |||
| 289 | // Now do the sides | 297 | // Now do the sides |
| 290 | baseVertex += 2 * N; | 298 | baseVertex += 2 * N; |
| 291 | 299 | ||
| 292 | for (uint32_t i = 0; i < N; ++i) { | 300 | for (uint32_t i = 0; i < N; i++) { |
| 293 | vec3 left = shape[i]; | 301 | vec3 left = shape[i]; |
| 294 | vec3 right = shape[(i + 1) % N]; | 302 | vec3 right = shape[(i + 1) % N]; |
| 295 | vec3 normal = glm::normalize(left + right); | 303 | vec3 normal = glm::normalize(left + right); |
| 296 | vec3 topLeft = vec3(left.x, 0.5f, left.z); | 304 | vec3 topLeft = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(left.x, 0.5f, left.z)); |
| 297 | vec3 topRight = vec3(right.x, 0.5f, right.z); | 305 | vec3 topRight = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(right.x, 0.5f, right.z)); |
| 298 | vec3 bottomLeft = vec3(left.x, -0.5f, left.z); | 306 | vec3 bottomLeft = vec3(left.x, -0.5f, left.z); |
| 299 | vec3 bottomRight = vec3(right.x, -0.5f, right.z); | 307 | vec3 bottomRight = vec3(right.x, -0.5f, right.z); |
| 300 | 308 | ||
| @@ -324,6 +332,41 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver · 23 unmodified lines | |||
| 324 | shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); | 332 | shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); |
| 325 | } | 333 | } |
| 326 | 334 | ||
| 335 | void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { | ||
| 336 | // Draw a circle with radius 1/4th the size of the bounding box | ||
| 337 | using namespace geometry; | ||
| 338 | |||
| 339 | Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); | ||
| 340 | VertexVector vertices; | ||
| 341 | IndexVector solidIndices, wireIndices; | ||
| 342 | const int NUM_CIRCLE_VERTICES = 64; | ||
| 343 | |||
| 344 | std::vector<vec3> shape = polygon<NUM_CIRCLE_VERTICES>(); | ||
| 345 | for (const vec3& v : shape) { | ||
| 346 | vertices.push_back(vec3(v.x, 0.0f, v.z)); | ||
| 347 | vertices.push_back(vec3(0.0f, 0.0f, 0.0f)); | ||
| 348 | } | ||
| 349 | |||
| 350 | for (uint32_t i = 2; i < NUM_CIRCLE_VERTICES; i++) { | ||
| 351 | solidIndices.push_back(baseVertex + 0); | ||
| 352 | solidIndices.push_back(baseVertex + i); | ||
| 353 | solidIndices.push_back(baseVertex + i - 1); | ||
| 354 | solidIndices.push_back(baseVertex + NUM_CIRCLE_VERTICES); | ||
| 355 | solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES - 1); | ||
| 356 | solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES); | ||
| 357 | } | ||
| 358 | |||
| 359 | for (uint32_t i = 1; i <= NUM_CIRCLE_VERTICES; i++) { | ||
| 360 | wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES)); | ||
| 361 | wireIndices.push_back(baseVertex + i - 1); | ||
| 362 | wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES) + NUM_CIRCLE_VERTICES); | ||
| 363 | wireIndices.push_back(baseVertex + (i - 1) + NUM_CIRCLE_VERTICES); | ||
| 364 | } | ||
| 365 | |||
| 366 | shapeData.setupVertices(vertexBuffer, vertices); | ||
| 367 | shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); | ||
| 368 | } | ||
| 369 | |||
| 327 | // FIXME solids need per-face vertices, but smooth shaded | 370 | // FIXME solids need per-face vertices, but smooth shaded |
| 328 | // components do not. Find a way to support using draw elements | 371 | // components do not. Find a way to support using draw elements |
| 329 | // or draw arrays as appropriate | 372 | // or draw arrays as appropriate |
| @@ -356,8 +399,8 @@ void GeometryCache::buildShapes() { · 26 unmodified lines | |||
| 356 | Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); | 399 | Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); |
| 357 | ShapeData& shapeData = _shapes[Line]; | 400 | ShapeData& shapeData = _shapes[Line]; |
| 358 | shapeData.setupVertices(_shapeVertices, VertexVector { | 401 | shapeData.setupVertices(_shapeVertices, VertexVector { |
| 359 | vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0), | 402 | vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f), |
| 360 | vec3(0.5f, 0, 0), vec3(0.5f, 0, 0) | 403 | vec3(0.5f, 0.0f, 0.0f), vec3(0.5f, 0.0f, 0.0f) |
| 361 | }); | 404 | }); |
| 362 | IndexVector wireIndices; | 405 | IndexVector wireIndices; |
| 363 | // Only two indices | 406 | // Only two indices |
| @@ -366,20 +409,22 @@ void GeometryCache::buildShapes() { · 2 unmodified lines | |||
| 366 | shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); | 409 | shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); |
| 367 | } | 410 | } |
| 368 | 411 | ||
| 369 | // Not implememented yet: | ||
| 370 | |||
| 371 | //Triangle, | 412 | //Triangle, |
| 372 | extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices); | 413 | extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices); |
| 373 | //Hexagon, | 414 | //Hexagon, |
| 374 | extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices); | 415 | extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices); |
| 375 | //Octagon, | 416 | //Octagon, |
| 376 | extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices); | 417 | extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices); |
| 377 | |||
| 378 | //Quad, | ||
| 379 | //Circle, | ||
| 380 | //Torus, | ||
| 381 | //Cone, | ||
| 382 | //Cylinder, | 418 | //Cylinder, |
| 419 | extrudePolygon<64>(_shapes[Cylinder], _shapeVertices, _shapeIndices); | ||
| 420 | //Cone, | ||
| 421 | extrudePolygon<64>(_shapes[Cone], _shapeVertices, _shapeIndices, true); | ||
| 422 | //Circle | ||
| 423 | drawCircle(_shapes[Circle], _shapeVertices, _shapeIndices); | ||
| 424 | // Not implememented yet: | ||
| 425 | //Quad, | ||
| 426 | //Torus, | ||
| 427 | |||
| 383 | } | 428 | } |
| 384 | 429 | ||
| 385 | gpu::Stream::FormatPointer& getSolidStreamFormat() { | 430 | gpu::Stream::FormatPointer& getSolidStreamFormat() { |
| @@ -596,7 +641,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec2>& points, con · 210 unmodified lines | |||
| 596 | auto pointCount = points.size(); | 641 | auto pointCount = points.size(); |
| 597 | auto colorCount = colors.size(); | 642 | auto colorCount = colors.size(); |
| 598 | int compactColor = 0; | 643 | int compactColor = 0; |
| 599 | for (auto i = 0; i < pointCount; ++i) { | 644 | for (auto i = 0; i < pointCount; i++) { |
| 600 | const auto& point = points[i]; | 645 | const auto& point = points[i]; |
| 601 | *(vertex++) = point.x; | 646 | *(vertex++) = point.x; |
| 602 | *(vertex++) = point.y; | 647 | *(vertex++) = point.y; |
| @@ -673,7 +718,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, con · 70 unmodified lines | |||
| 673 | const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); | 718 | const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); |
| 674 | auto pointCount = points.size(); | 719 | auto pointCount = points.size(); |
| 675 | auto colorCount = colors.size(); | 720 | auto colorCount = colors.size(); |
| 676 | for (auto i = 0; i < pointCount; ++i) { | 721 | for (auto i = 0; i < pointCount; i++) { |
| 677 | const glm::vec3& point = points[i]; | 722 | const glm::vec3& point = points[i]; |
| 678 | if (i < colorCount) { | 723 | if (i < colorCount) { |
| 679 | const glm::vec4& color = colors[i]; | 724 | const glm::vec4& color = colors[i]; |
| @@ -1610,6 +1655,9 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const · 930 unmodified lines | |||
| 1610 | state->setBlendFunction(true, | 1655 | state->setBlendFunction(true, |
| 1611 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, | 1656 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, |
| 1612 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 1657 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| 1658 | |||
| 1659 | PrepareStencil::testMask(*state); | ||
| 1660 | |||
| 1613 | gpu::Shader::BindingSet slotBindings; | 1661 | gpu::Shader::BindingSet slotBindings; |
| 1614 | slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT)); | 1662 | slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT)); |
| 1615 | gpu::Shader::makeProgram(*program, slotBindings); | 1663 | gpu::Shader::makeProgram(*program, slotBindings); |
| @@ -1663,11 +1711,14 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { · 47 unmodified lines | |||
| 1663 | 1711 | ||
| 1664 | // enable decal blend | 1712 | // enable decal blend |
| 1665 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); | 1713 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); |
| 1714 | PrepareStencil::testMask(*state); | ||
| 1666 | 1715 | ||
| 1667 | _standardDrawPipeline = gpu::Pipeline::create(program, state); | 1716 | _standardDrawPipeline = gpu::Pipeline::create(program, state); |
| 1668 | 1717 | ||
| 1669 | 1718 | ||
| 1670 | auto stateNoBlend = std::make_shared<gpu::State>(); | 1719 | auto stateNoBlend = std::make_shared<gpu::State>(); |
| 1720 | PrepareStencil::testMaskDrawShape(*state); | ||
| 1721 | |||
| 1671 | auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); | 1722 | auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); |
| 1672 | auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); | 1723 | auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); |
| 1673 | gpu::Shader::makeProgram((*programNoBlend)); | 1724 | gpu::Shader::makeProgram((*programNoBlend)); |
| @@ -1690,12 +1741,14 @@ void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bo · 16 unmodified lines | |||
| 1690 | 1741 | ||
| 1691 | auto stateLayered = std::make_shared<gpu::State>(); | 1742 | auto stateLayered = std::make_shared<gpu::State>(); |
| 1692 | stateLayered->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); | 1743 | stateLayered->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); |
| 1744 | PrepareStencil::testMask(*stateLayered); | ||
| 1693 | _gridPipelineLayered = gpu::Pipeline::create(program, stateLayered); | 1745 | _gridPipelineLayered = gpu::Pipeline::create(program, stateLayered); |
| 1694 | 1746 | ||
| 1695 | auto state = std::make_shared<gpu::State>(stateLayered->getValues()); | 1747 | auto state = std::make_shared<gpu::State>(stateLayered->getValues()); |
| 1696 | const float DEPTH_BIAS = 0.001f; | 1748 | const float DEPTH_BIAS = 0.001f; |
| 1697 | state->setDepthBias(DEPTH_BIAS); | 1749 | state->setDepthBias(DEPTH_BIAS); |
| 1698 | state->setDepthTest(true, false, gpu::LESS_EQUAL); | 1750 | state->setDepthTest(true, false, gpu::LESS_EQUAL); |
| 1751 | PrepareStencil::testMaskDrawShape(*state); | ||
| 1699 | _gridPipeline = gpu::Pipeline::create(program, state); | 1752 | _gridPipeline = gpu::Pipeline::create(program, state); |
| 1700 | } | 1753 | } |
| 1701 | 1754 | ||
| @@ -1773,6 +1826,11 @@ static void buildWebShader(const std::string& vertShaderText, const std::string& · 71 unmodified lines | |||
| 1773 | state->setBlendFunction(blendEnable, | 1826 | state->setBlendFunction(blendEnable, |
| 1774 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, | 1827 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, |
| 1775 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 1828 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| 1829 | if (blendEnable) { | ||
| 1830 | PrepareStencil::testMask(*state); | ||
| 1831 | } else { | ||
| 1832 | PrepareStencil::testMaskDrawShape(*state); | ||
| 1833 | } | ||
| 1776 | 1834 | ||
| 1777 | pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); | 1835 | pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); |
| 1778 | } | 1836 | } |
| @@ -1858,6 +1916,12 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp · 79 unmodified lines | |||
| 1858 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, | 1916 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, |
| 1859 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); | 1917 | gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); |
| 1860 | 1918 | ||
| 1919 | if (config.isTransparent()) { | ||
| 1920 | PrepareStencil::testMask(*state); | ||
| 1921 | } else { | ||
| 1922 | PrepareStencil::testMaskDrawShape(*state); | ||
| 1923 | } | ||
| 1924 | |||
| 1861 | gpu::ShaderPointer program = (config.isUnlit()) ? _unlitShader : _simpleShader; | 1925 | gpu::ShaderPointer program = (config.isUnlit()) ? _unlitShader : _simpleShader; |
| 1862 | gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); | 1926 | gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); |
| 1863 | _simplePrograms.insert(config, pipeline); | 1927 | _simplePrograms.insert(config, pipeline); |
| @@ -142,8 +142,8 @@ public: · 141 unmodified lines | |||
| 142 | Dodecahedron, | 142 | Dodecahedron, |
| 143 | Icosahedron, | 143 | Icosahedron, |
| 144 | Torus, // not yet implemented | 144 | Torus, // not yet implemented |
| 145 | Cone, // not yet implemented | 145 | Cone, |
| 146 | Cylinder, // not yet implemented | 146 | Cylinder, |
| 147 | NUM_SHAPES, | 147 | NUM_SHAPES, |
| 148 | }; | 148 | }; |
| 149 | 149 | ||
| @@ -1,94 +0,0 @@ | |||
| 1 | // | ||
| 2 | // HitEffect.cpp | ||
| 3 | // interface/src/renderer | ||
| 4 | // | ||
| 5 | // Created by Andrzej Kapolka on 7/14/13. | ||
| 6 | // Copyright 2013 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | // include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL | ||
| 13 | |||
| 14 | |||
| 15 | #include <glm/gtc/random.hpp> | ||
| 16 | |||
| 17 | #include <DependencyManager.h> | ||
| 18 | #include <PathUtils.h> | ||
| 19 | #include <SharedUtil.h> | ||
| 20 | |||
| 21 | #include "AbstractViewStateInterface.h" | ||
| 22 | #include "HitEffect.h" | ||
| 23 | |||
| 24 | #include "TextureCache.h" | ||
| 25 | #include "DependencyManager.h" | ||
| 26 | #include "ViewFrustum.h" | ||
| 27 | #include "GeometryCache.h" | ||
| 28 | |||
| 29 | #include <gpu/Context.h> | ||
| 30 | |||
| 31 | #include "hit_effect_vert.h" | ||
| 32 | #include "hit_effect_frag.h" | ||
| 33 | |||
| 34 | |||
| 35 | HitEffect::HitEffect() { | ||
| 36 | _geometryId = DependencyManager::get<GeometryCache>()->allocateID(); | ||
| 37 | } | ||
| 38 | |||
| 39 | HitEffect::~HitEffect() { | ||
| 40 | auto geometryCache = DependencyManager::get<GeometryCache>(); | ||
| 41 | if (_geometryId && geometryCache) { | ||
| 42 | geometryCache->releaseID(_geometryId); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | const gpu::PipelinePointer& HitEffect::getHitEffectPipeline() { | ||
| 47 | if (!_hitEffectPipeline) { | ||
| 48 | auto vs = gpu::Shader::createVertex(std::string(hit_effect_vert)); | ||
| 49 | auto ps = gpu::Shader::createPixel(std::string(hit_effect_frag)); | ||
| 50 | gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); | ||
| 51 | |||
| 52 | |||
| 53 | gpu::Shader::BindingSet slotBindings; | ||
| 54 | gpu::Shader::makeProgram(*program, slotBindings); | ||
| 55 | |||
| 56 | |||
| 57 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | ||
| 58 | |||
| 59 | state->setDepthTest(false, false, gpu::LESS_EQUAL); | ||
| 60 | |||
| 61 | // Blend on transparent | ||
| 62 | state->setBlendFunction(true, | ||
| 63 | gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); | ||
| 64 | |||
| 65 | // Good to go add the brand new pipeline | ||
| 66 | _hitEffectPipeline = gpu::Pipeline::create(program, state); | ||
| 67 | } | ||
| 68 | return _hitEffectPipeline; | ||
| 69 | } | ||
| 70 | |||
| 71 | void HitEffect::run(const render::RenderContextPointer& renderContext) { | ||
| 72 | assert(renderContext->args); | ||
| 73 | assert(renderContext->args->hasViewFrustum()); | ||
| 74 | RenderArgs* args = renderContext->args; | ||
| 75 | |||
| 76 | gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { | ||
| 77 | |||
| 78 | glm::mat4 projMat; | ||
| 79 | Transform viewMat; | ||
| 80 | args->getViewFrustum().evalProjectionMatrix(projMat); | ||
| 81 | args->getViewFrustum().evalViewTransform(viewMat); | ||
| 82 | batch.setProjectionTransform(projMat); | ||
| 83 | batch.setViewTransform(viewMat); | ||
| 84 | batch.setModelTransform(Transform()); | ||
| 85 | |||
| 86 | batch.setPipeline(getHitEffectPipeline()); | ||
| 87 | |||
| 88 | static const glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); | ||
| 89 | static const glm::vec2 bottomLeft(-1.0f, -1.0f); | ||
| 90 | static const glm::vec2 topRight(1.0f, 1.0f); | ||
| 91 | DependencyManager::get<GeometryCache>()->renderQuad(batch, bottomLeft, topRight, color, _geometryId); | ||
| 92 | }); | ||
| 93 | } | ||
| 94 | |||
| @@ -1,38 +0,0 @@ | |||
| 1 | // | ||
| 2 | // hitEffect.h | ||
| 3 | // hifi | ||
| 4 | // | ||
| 5 | // Created by eric levin on 7/17/15. | ||
| 6 | // | ||
| 7 | // | ||
| 8 | |||
| 9 | #ifndef hifi_hitEffect_h | ||
| 10 | #define hifi_hitEffect_h | ||
| 11 | |||
| 12 | #include <render/DrawTask.h> | ||
| 13 | |||
| 14 | class HitEffectConfig : public render::Job::Config { | ||
| 15 | Q_OBJECT | ||
| 16 | Q_PROPERTY(bool enabled MEMBER enabled) | ||
| 17 | public: | ||
| 18 | HitEffectConfig() : render::Job::Config(false) {} | ||
| 19 | }; | ||
| 20 | |||
| 21 | class HitEffect { | ||
| 22 | public: | ||
| 23 | using Config = HitEffectConfig; | ||
| 24 | using JobModel = render::Job::Model<HitEffect, Config>; | ||
| 25 | |||
| 26 | HitEffect(); | ||
| 27 | ~HitEffect(); | ||
| 28 | void configure(const Config& config) {} | ||
| 29 | void run(const render::RenderContextPointer& renderContext); | ||
| 30 | |||
| 31 | const gpu::PipelinePointer& getHitEffectPipeline(); | ||
| 32 | |||
| 33 | private: | ||
| 34 | int _geometryId { 0 }; | ||
| 35 | gpu::PipelinePointer _hitEffectPipeline; | ||
| 36 | }; | ||
| 37 | |||
| 38 | #endif | ||
| @@ -16,6 +16,8 @@ · 15 unmodified lines | |||
| 16 | 16 | ||
| 17 | #include <gpu/StandardShaderLib.h> | 17 | #include <gpu/StandardShaderLib.h> |
| 18 | 18 | ||
| 19 | #include "StencilMaskPass.h" | ||
| 20 | |||
| 19 | #include "lightClusters_drawGrid_vert.h" | 21 | #include "lightClusters_drawGrid_vert.h" |
| 20 | #include "lightClusters_drawGrid_frag.h" | 22 | #include "lightClusters_drawGrid_frag.h" |
| 21 | 23 | ||
| @@ -152,9 +152,9 @@ public: · 151 unmodified lines | |||
| 152 | int numInputLights { 0 }; | 152 | int numInputLights { 0 }; |
| 153 | int numClusteredLights { 0 }; | 153 | int numClusteredLights { 0 }; |
| 154 | 154 | ||
| 155 | void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; emit dirty(); } | 155 | void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; } |
| 156 | void setNumInputLights(int numLights) { numInputLights = numLights; emit dirty(); } | 156 | void setNumInputLights(int numLights) { numInputLights = numLights; } |
| 157 | void setNumClusteredLights(int numLights) { numClusteredLights = numLights; emit dirty(); } | 157 | void setNumClusteredLights(int numLights) { numClusteredLights = numLights; } |
| 158 | 158 | ||
| 159 | int numSceneLights { 0 }; | 159 | int numSceneLights { 0 }; |
| 160 | int numFreeSceneLights { 0 }; | 160 | int numFreeSceneLights { 0 }; |
| @@ -81,3 +81,71 @@ void LightPayload::render(RenderArgs* args) { · 80 unmodified lines | |||
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | |||
| 85 | namespace render { | ||
| 86 | template <> const ItemKey payloadGetKey(const KeyLightPayload::Pointer& payload) { | ||
| 87 | ItemKey::Builder builder; | ||
| 88 | builder.withTypeLight(); | ||
| 89 | if (!payload || !payload->isVisible()) { | ||
| 90 | builder.withInvisible(); | ||
| 91 | } | ||
| 92 | return builder.build(); | ||
| 93 | } | ||
| 94 | |||
| 95 | template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload) { | ||
| 96 | if (payload) { | ||
| 97 | return payload->editBound(); | ||
| 98 | } | ||
| 99 | return render::Item::Bound(); | ||
| 100 | } | ||
| 101 | template <> void payloadRender(const KeyLightPayload::Pointer& payload, RenderArgs* args) { | ||
| 102 | if (args) { | ||
| 103 | if (payload) { | ||
| 104 | payload->render(args); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | KeyLightPayload::KeyLightPayload() : | ||
| 111 | _light(std::make_shared<model::Light>()) | ||
| 112 | { | ||
| 113 | } | ||
| 114 | |||
| 115 | |||
| 116 | KeyLightPayload::~KeyLightPayload() { | ||
| 117 | if (!LightStage::isIndexInvalid(_index)) { | ||
| 118 | if (_stage) { | ||
| 119 | _stage->removeLight(_index); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | void KeyLightPayload::render(RenderArgs* args) { | ||
| 125 | if (!_stage) { | ||
| 126 | _stage = DependencyManager::get<DeferredLightingEffect>()->getLightStage(); | ||
| 127 | } | ||
| 128 | // Do we need to allocate the light in the stage ? | ||
| 129 | if (LightStage::isIndexInvalid(_index)) { | ||
| 130 | _index = _stage->addLight(_light); | ||
| 131 | _needUpdate = false; | ||
| 132 | } | ||
| 133 | // Need an update ? | ||
| 134 | if (_needUpdate) { | ||
| 135 | _stage->updateLightArrayBuffer(_index); | ||
| 136 | _needUpdate = false; | ||
| 137 | } | ||
| 138 | |||
| 139 | if (isVisible()) { | ||
| 140 | // FInally, push the light visible in the frame | ||
| 141 | _stage->_currentFrame.pushLight(_index, _light->getType()); | ||
| 142 | |||
| 143 | #ifdef WANT_DEBUG | ||
| 144 | Q_ASSERT(args->_batch); | ||
| 145 | gpu::Batch& batch = *args->_batch; | ||
| 146 | batch.setModelTransform(getTransformToCenter()); | ||
| 147 | DependencyManager::get<GeometryCache>()->renderWireSphere(batch, 0.5f, 15, 15, glm::vec4(color, 1.0f)); | ||
| 148 | #endif | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| @@ -15,6 +15,7 @@ · 14 unmodified lines | |||
| 15 | #include <model/Light.h> | 15 | #include <model/Light.h> |
| 16 | #include <render/Item.h> | 16 | #include <render/Item.h> |
| 17 | #include "LightStage.h" | 17 | #include "LightStage.h" |
| 18 | #include "TextureCache.h" | ||
| 18 | 19 | ||
| 19 | class LightPayload { | 20 | class LightPayload { |
| 20 | public: | 21 | public: |
| @@ -46,4 +47,41 @@ namespace render { · 25 unmodified lines | |||
| 46 | template <> void payloadRender(const LightPayload::Pointer& payload, RenderArgs* args); | 47 | template <> void payloadRender(const LightPayload::Pointer& payload, RenderArgs* args); |
| 47 | } | 48 | } |
| 48 | 49 | ||
| 50 | class KeyLightPayload { | ||
| 51 | public: | ||
| 52 | using Payload = render::Payload<KeyLightPayload>; | ||
| 53 | using Pointer = Payload::DataPointer; | ||
| 54 | |||
| 55 | KeyLightPayload(); | ||
| 56 | ~KeyLightPayload(); | ||
| 57 | void render(RenderArgs* args); | ||
| 58 | |||
| 59 | model::LightPointer editLight() { _needUpdate = true; return _light; } | ||
| 60 | render::Item::Bound& editBound() { _needUpdate = true; return _bound; } | ||
| 61 | |||
| 62 | void setVisible(bool visible) { _isVisible = visible; } | ||
| 63 | bool isVisible() const { return _isVisible; } | ||
| 64 | |||
| 65 | |||
| 66 | // More attributes used for rendering: | ||
| 67 | NetworkTexturePointer _ambientTexture; | ||
| 68 | QString _ambientTextureURL; | ||
| 69 | bool _pendingAmbientTexture { false }; | ||
| 70 | bool _validAmbientTextureURL { false }; | ||
| 71 | |||
| 72 | protected: | ||
| 73 | model::LightPointer _light; | ||
| 74 | render::Item::Bound _bound; | ||
| 75 | LightStagePointer _stage; | ||
| 76 | LightStage::Index _index { LightStage::INVALID_INDEX }; | ||
| 77 | bool _needUpdate { true }; | ||
| 78 | bool _isVisible { true }; | ||
| 79 | }; | ||
| 80 | |||
| 81 | namespace render { | ||
| 82 | template <> const ItemKey payloadGetKey(const KeyLightPayload::Pointer& payload); | ||
| 83 | template <> const Item::Bound payloadGetBound(const KeyLightPayload::Pointer& payload); | ||
| 84 | template <> void payloadRender(const KeyLightPayload::Pointer& payload, RenderArgs* args); | ||
| 85 | } | ||
| 86 | |||
| 49 | #endif | 87 | #endif |
| @@ -77,7 +77,6 @@ public: · 76 unmodified lines | |||
| 77 | }; | 77 | }; |
| 78 | using Descs = std::vector<Desc>; | 78 | using Descs = std::vector<Desc>; |
| 79 | 79 | ||
| 80 | |||
| 81 | Index findLight(const LightPointer& light) const; | 80 | Index findLight(const LightPointer& light) const; |
| 82 | Index addLight(const LightPointer& light); | 81 | Index addLight(const LightPointer& light); |
| 83 | 82 | ||
| @@ -118,19 +117,25 @@ public: · 34 unmodified lines | |||
| 118 | public: | 117 | public: |
| 119 | Frame() {} | 118 | Frame() {} |
| 120 | 119 | ||
| 121 | void clear() { _pointLights.clear(); _spotLights.clear(); } | 120 | void clear() { _pointLights.clear(); _spotLights.clear(); _sunLights.clear(); _ambientLights.clear(); } |
| 122 | void pushLight(LightStage::Index index, model::Light::Type type) { | 121 | void pushLight(LightStage::Index index, model::Light::Type type) { |
| 123 | switch (type) { | 122 | switch (type) { |
| 124 | case model::Light::POINT: { pushPointLight(index); break; } | 123 | case model::Light::POINT: { pushPointLight(index); break; } |
| 125 | case model::Light::SPOT: { pushSpotLight(index); break; } | 124 | case model::Light::SPOT: { pushSpotLight(index); break; } |
| 125 | case model::Light::SUN: { pushSunLight(index); break; } | ||
| 126 | case model::Light::AMBIENT: { pushAmbientLight(index); break; } | ||
| 126 | default: { break; } | 127 | default: { break; } |
| 127 | } | 128 | } |
| 128 | } | 129 | } |
| 129 | void pushPointLight(LightStage::Index index) { _pointLights.emplace_back(index); } | 130 | void pushPointLight(LightStage::Index index) { _pointLights.emplace_back(index); } |
| 130 | void pushSpotLight(LightStage::Index index) { _spotLights.emplace_back(index); } | 131 | void pushSpotLight(LightStage::Index index) { _spotLights.emplace_back(index); } |
| 131 | 132 | void pushSunLight(LightStage::Index index) { _sunLights.emplace_back(index); } | |
| 133 | void pushAmbientLight(LightStage::Index index) { _ambientLights.emplace_back(index); } | ||
| 134 | |||
| 132 | LightStage::LightIndices _pointLights; | 135 | LightStage::LightIndices _pointLights; |
| 133 | LightStage::LightIndices _spotLights; | 136 | LightStage::LightIndices _spotLights; |
| 137 | LightStage::LightIndices _sunLights; | ||
| 138 | LightStage::LightIndices _ambientLights; | ||
| 134 | }; | 139 | }; |
| 135 | 140 | ||
| 136 | Frame _currentFrame; | 141 | Frame _currentFrame; |
| @@ -332,7 +332,7 @@ void Model::initJointStates() { · 331 unmodified lines | |||
| 332 | 332 | ||
| 333 | bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, | 333 | bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, |
| 334 | BoxFace& face, glm::vec3& surfaceNormal, | 334 | BoxFace& face, glm::vec3& surfaceNormal, |
| 335 | QString& extraInfo, bool pickAgainstTriangles) { | 335 | QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) { |
| 336 | 336 | ||
| 337 | bool intersectedSomething = false; | 337 | bool intersectedSomething = false; |
| 338 | 338 | ||
| @@ -381,7 +381,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g · 42 unmodified lines | |||
| 381 | float triangleSetDistance = 0.0f; | 381 | float triangleSetDistance = 0.0f; |
| 382 | BoxFace triangleSetFace; | 382 | BoxFace triangleSetFace; |
| 383 | glm::vec3 triangleSetNormal; | 383 | glm::vec3 triangleSetNormal; |
| 384 | if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles)) { | 384 | if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) { |
| 385 | 385 | ||
| 386 | glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); | 386 | glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); |
| 387 | glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); | 387 | glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); |
| @@ -156,7 +156,7 @@ public: · 155 unmodified lines | |||
| 156 | 156 | ||
| 157 | bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, | 157 | bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, |
| 158 | BoxFace& face, glm::vec3& surfaceNormal, | 158 | BoxFace& face, glm::vec3& surfaceNormal, |
| 159 | QString& extraInfo, bool pickAgainstTriangles = false); | 159 | QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); |
| 160 | 160 | ||
| 161 | void setOffset(const glm::vec3& offset); | 161 | void setOffset(const glm::vec3& offset); |
| 162 | const glm::vec3& getOffset() const { return _offset; } | 162 | const glm::vec3& getOffset() const { return _offset; } |
| @@ -27,12 +27,12 @@ · 26 unmodified lines | |||
| 27 | #include <render/BlurTask.h> | 27 | #include <render/BlurTask.h> |
| 28 | 28 | ||
| 29 | #include "LightingModel.h" | 29 | #include "LightingModel.h" |
| 30 | #include "StencilMaskPass.h" | ||
| 30 | #include "DebugDeferredBuffer.h" | 31 | #include "DebugDeferredBuffer.h" |
| 31 | #include "DeferredFramebuffer.h" | 32 | #include "DeferredFramebuffer.h" |
| 32 | #include "DeferredLightingEffect.h" | 33 | #include "DeferredLightingEffect.h" |
| 33 | #include "SurfaceGeometryPass.h" | 34 | #include "SurfaceGeometryPass.h" |
| 34 | #include "FramebufferCache.h" | 35 | #include "FramebufferCache.h" |
| 35 | #include "HitEffect.h" | ||
| 36 | #include "TextureCache.h" | 36 | #include "TextureCache.h" |
| 37 | #include "ZoneRenderer.h" | 37 | #include "ZoneRenderer.h" |
| 38 | 38 | ||
| @@ -43,8 +43,6 @@ · 4 unmodified lines | |||
| 43 | 43 | ||
| 44 | #include <gpu/StandardShaderLib.h> | 44 | #include <gpu/StandardShaderLib.h> |
| 45 | 45 | ||
| 46 | #include "drawOpaqueStencil_frag.h" | ||
| 47 | |||
| 48 | 46 | ||
| 49 | using namespace render; | 47 | using namespace render; |
| 50 | extern void initOverlay3DPipelines(render::ShapePlumber& plumber); | 48 | extern void initOverlay3DPipelines(render::ShapePlumber& plumber); |
| @@ -75,7 +73,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 24 unmodified lines | |||
| 75 | const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform"); | 73 | const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform"); |
| 76 | const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel"); | 74 | const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel"); |
| 77 | 75 | ||
| 78 | |||
| 79 | // GPU jobs: Start preparing the primary, deferred and lighting buffer | 76 | // GPU jobs: Start preparing the primary, deferred and lighting buffer |
| 80 | const auto primaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer"); | 77 | const auto primaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer"); |
| 81 | 78 | ||
| @@ -86,13 +83,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 4 unmodified lines | |||
| 86 | const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0); | 83 | const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0); |
| 87 | const auto lightingFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(1); | 84 | const auto lightingFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(1); |
| 88 | 85 | ||
| 86 | // draw a stencil mask in hidden regions of the framebuffer. | ||
| 87 | task.addJob<PrepareStencil>("PrepareStencil", primaryFramebuffer); | ||
| 88 | |||
| 89 | // Render opaque objects in DeferredBuffer | 89 | // Render opaque objects in DeferredBuffer |
| 90 | const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).hasVarying(); | 90 | const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).hasVarying(); |
| 91 | task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber); | 91 | task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber); |
| 92 | 92 | ||
| 93 | // Once opaque is all rendered create stencil background | ||
| 94 | task.addJob<DrawStencilDeferred>("DrawOpaqueStencil", deferredFramebuffer); | ||
| 95 | |||
| 96 | task.addJob<EndGPURangeTimer>("OpaqueRangeTimer", opaqueRangeTimer); | 93 | task.addJob<EndGPURangeTimer>("OpaqueRangeTimer", opaqueRangeTimer); |
| 97 | 94 | ||
| 98 | 95 | ||
| @@ -124,6 +121,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 25 unmodified lines | |||
| 124 | // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. | 121 | // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. |
| 125 | task.addJob<DrawLight>("DrawLight", lights); | 122 | task.addJob<DrawLight>("DrawLight", lights); |
| 126 | 123 | ||
| 124 | // Filter zones from the general metas bucket | ||
| 125 | const auto zones = task.addJob<ZoneRendererTask>("ZoneRenderer", metas); | ||
| 126 | |||
| 127 | // Light Clustering | 127 | // Light Clustering |
| 128 | // Create the cluster grid of lights, cpu job for now | 128 | // Create the cluster grid of lights, cpu job for now |
| 129 | const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget).hasVarying(); | 129 | const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget).hasVarying(); |
| @@ -136,11 +136,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 6 unmodified lines | |||
| 136 | 136 | ||
| 137 | task.addJob<RenderDeferred>("RenderDeferred", deferredLightingInputs); | 137 | task.addJob<RenderDeferred>("RenderDeferred", deferredLightingInputs); |
| 138 | 138 | ||
| 139 | // Use Stencil and draw background in Lighting buffer to complete filling in the opaque | 139 | // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job |
| 140 | const auto backgroundInputs = DrawBackgroundDeferred::Inputs(background, lightingModel).hasVarying(); | 140 | task.addJob<DrawBackgroundStage>("DrawBackgroundDeferred", lightingModel); |
| 141 | task.addJob<DrawBackgroundDeferred>("DrawBackgroundDeferred", backgroundInputs); | 141 | |
| 142 | |||
| 143 | |||
| 144 | // Render transparent objects forward in LightingBuffer | 142 | // Render transparent objects forward in LightingBuffer |
| 145 | const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).hasVarying(); | 143 | const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).hasVarying(); |
| 146 | task.addJob<DrawDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber); | 144 | task.addJob<DrawDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber); |
| @@ -162,7 +160,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 15 unmodified lines | |||
| 162 | task.addJob<DrawBounds>("DrawOpaqueBounds", opaques); | 160 | task.addJob<DrawBounds>("DrawOpaqueBounds", opaques); |
| 163 | task.addJob<DrawBounds>("DrawTransparentBounds", transparents); | 161 | task.addJob<DrawBounds>("DrawTransparentBounds", transparents); |
| 164 | 162 | ||
| 165 | task.addJob<ZoneRendererTask>("ZoneRenderer", opaques); | 163 | task.addJob<DrawBounds>("DrawLightBounds", lights); |
| 164 | task.addJob<DrawBounds>("DrawZones", zones); | ||
| 166 | } | 165 | } |
| 167 | 166 | ||
| 168 | // Overlays | 167 | // Overlays |
| @@ -202,6 +201,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren · 33 unmodified lines | |||
| 202 | auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE); | 201 | auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE); |
| 203 | task.addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap)); | 202 | task.addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap)); |
| 204 | } | 203 | } |
| 204 | |||
| 205 | task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform); | ||
| 205 | } | 206 | } |
| 206 | 207 | ||
| 207 | 208 | ||
| @@ -384,88 +385,6 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& · 176 unmodified lines | |||
| 384 | } | 385 | } |
| 385 | } | 386 | } |
| 386 | 387 | ||
| 387 | |||
| 388 | gpu::PipelinePointer DrawStencilDeferred::getOpaquePipeline() { | ||
| 389 | if (!_opaquePipeline) { | ||
| 390 | const gpu::int8 STENCIL_OPAQUE = 1; | ||
| 391 | auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); | ||
| 392 | auto ps = gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag)); | ||
| 393 | auto program = gpu::Shader::createProgram(vs, ps); | ||
| 394 | gpu::Shader::makeProgram((*program)); | ||
| 395 | |||
| 396 | auto state = std::make_shared<gpu::State>(); | ||
| 397 | state->setDepthTest(true, false, gpu::LESS_EQUAL); | ||
| 398 | state->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_OPAQUE, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP)); | ||
| 399 | state->setColorWriteMask(0); | ||
| 400 | |||
| 401 | _opaquePipeline = gpu::Pipeline::create(program, state); | ||
| 402 | } | ||
| 403 | return _opaquePipeline; | ||
| 404 | } | ||
| 405 | |||
| 406 | void DrawStencilDeferred::run(const RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer) { | ||
| 407 | assert(renderContext->args); | ||
| 408 | assert(renderContext->args->hasViewFrustum()); | ||
| 409 | |||
| 410 | // from the touched pixel generate the stencil buffer | ||
| 411 | RenderArgs* args = renderContext->args; | ||
| 412 | doInBatch(args->_context, [&](gpu::Batch& batch) { | ||
| 413 | args->_batch = &batch; | ||
| 414 | |||
| 415 | auto deferredFboColorDepthStencil = deferredFramebuffer->getDeferredFramebufferDepthColor(); | ||
| 416 | |||
| 417 | |||
| 418 | batch.enableStereo(false); | ||
| 419 | |||
| 420 | batch.setFramebuffer(deferredFboColorDepthStencil); | ||
| 421 | batch.setViewportTransform(args->_viewport); | ||
| 422 | batch.setStateScissorRect(args->_viewport); | ||
| 423 | |||
| 424 | batch.setPipeline(getOpaquePipeline()); | ||
| 425 | |||
| 426 | batch.draw(gpu::TRIANGLE_STRIP, 4); | ||
| 427 | batch.setResourceTexture(0, nullptr); | ||
| 428 | |||
| 429 | }); | ||
| 430 | args->_batch = nullptr; | ||
| 431 | } | ||
| 432 | |||
| 433 | void DrawBackgroundDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) { | ||
| 434 | assert(renderContext->args); | ||
| 435 | assert(renderContext->args->hasViewFrustum()); | ||
| 436 | |||
| 437 | const auto& inItems = inputs.get0(); | ||
| 438 | const auto& lightingModel = inputs.get1(); | ||
| 439 | if (!lightingModel->isBackgroundEnabled()) { | ||
| 440 | return; | ||
| 441 | } | ||
| 442 | |||
| 443 | RenderArgs* args = renderContext->args; | ||
| 444 | doInBatch(args->_context, [&](gpu::Batch& batch) { | ||
| 445 | args->_batch = &batch; | ||
| 446 | // _gpuTimer.begin(batch); | ||
| 447 | |||
| 448 | batch.enableSkybox(true); | ||
| 449 | |||
| 450 | batch.setViewportTransform(args->_viewport); | ||
| 451 | batch.setStateScissorRect(args->_viewport); | ||
| 452 | |||
| 453 | glm::mat4 projMat; | ||
| 454 | Transform viewMat; | ||
| 455 | args->getViewFrustum().evalProjectionMatrix(projMat); | ||
| 456 | args->getViewFrustum().evalViewTransform(viewMat); | ||
| 457 | |||
| 458 | batch.setProjectionTransform(projMat); | ||
| 459 | batch.setViewTransform(viewMat); | ||
| 460 | |||
| 461 | renderItems(renderContext, inItems); | ||
| 462 | // _gpuTimer.end(batch); | ||
| 463 | }); | ||
| 464 | args->_batch = nullptr; | ||
| 465 | |||
| 466 | // std::static_pointer_cast<Config>(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage(); | ||
| 467 | } | ||
| 468 | |||
| 469 | void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { | 388 | void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { |
| 470 | assert(renderContext->args); | 389 | assert(renderContext->args); |
| 471 | assert(renderContext->args->_context); | 390 | assert(renderContext->args->_context); |
| @@ -535,3 +454,4 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer · 63 unmodified lines | |||
| 535 | } | 454 | } |
| 536 | }); | 455 | }); |
| 537 | } | 456 | } |
| 457 | |||
| @@ -120,35 +120,6 @@ protected: · 119 unmodified lines | |||
| 120 | bool _stateSort; | 120 | bool _stateSort; |
| 121 | }; | 121 | }; |
| 122 | 122 | ||
| 123 | class DeferredFramebuffer; | ||
| 124 | class DrawStencilDeferred { | ||
| 125 | public: | ||
| 126 | using JobModel = render::Job::ModelI<DrawStencilDeferred, std::shared_ptr<DeferredFramebuffer>>; | ||
| 127 | |||
| 128 | void run(const render::RenderContextPointer& renderContext, const std::shared_ptr<DeferredFramebuffer>& deferredFramebuffer); | ||
| 129 | |||
| 130 | protected: | ||
| 131 | gpu::PipelinePointer _opaquePipeline; | ||
| 132 | |||
| 133 | gpu::PipelinePointer getOpaquePipeline(); | ||
| 134 | }; | ||
| 135 | |||
| 136 | using DrawBackgroundDeferredConfig = render::GPUJobConfig; | ||
| 137 | |||
| 138 | class DrawBackgroundDeferred { | ||
| 139 | public: | ||
| 140 | using Inputs = render::VaryingSet2 <render::ItemBounds, LightingModelPointer>; | ||
| 141 | |||
| 142 | using Config = DrawBackgroundDeferredConfig; | ||
| 143 | using JobModel = render::Job::ModelI<DrawBackgroundDeferred, Inputs, Config>; | ||
| 144 | |||
| 145 | void configure(const Config& config) {} | ||
| 146 | void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); | ||
| 147 | |||
| 148 | protected: | ||
| 149 | gpu::RangeTimerPointer _gpuTimer; | ||
| 150 | }; | ||
| 151 | |||
| 152 | class DrawOverlay3DConfig : public render::Job::Config { | 123 | class DrawOverlay3DConfig : public render::Job::Config { |
| 153 | Q_OBJECT | 124 | Q_OBJECT |
| 154 | Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) | 125 | Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) |
| @@ -15,6 +15,7 @@ · 14 unmodified lines | |||
| 15 | #include <gpu/Context.h> | 15 | #include <gpu/Context.h> |
| 16 | #include <gpu/StandardShaderLib.h> | 16 | #include <gpu/StandardShaderLib.h> |
| 17 | 17 | ||
| 18 | #include "StencilMaskPass.h" | ||
| 18 | #include "DeferredLightingEffect.h" | 19 | #include "DeferredLightingEffect.h" |
| 19 | #include "TextureCache.h" | 20 | #include "TextureCache.h" |
| 20 | #include "render/DrawTask.h" | 21 | #include "render/DrawTask.h" |
| @@ -330,6 +331,7 @@ void addPlumberPipeline(ShapePlumber& plumber, · 309 unmodified lines | |||
| 330 | bool isWireframed = (i & 4); | 331 | bool isWireframed = (i & 4); |
| 331 | 332 | ||
| 332 | auto state = std::make_shared<gpu::State>(); | 333 | auto state = std::make_shared<gpu::State>(); |
| 334 | PrepareStencil::testMaskDrawShape(*state); | ||
| 333 | 335 | ||
| 334 | // Depth test depends on transparency | 336 | // Depth test depends on transparency |
| 335 | state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL); | 337 | state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL); |
| @@ -48,6 +48,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, · 47 unmodified lines | |||
| 48 | RenderArgs* args = renderContext->args; | 48 | RenderArgs* args = renderContext->args; |
| 49 | gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { | 49 | gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { |
| 50 | args->_batch = &batch; | 50 | args->_batch = &batch; |
| 51 | batch.enableStereo(false); | ||
| 51 | 52 | ||
| 52 | glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; | 53 | glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; |
| 53 | batch.setViewportTransform(viewport); | 54 | batch.setViewportTransform(viewport); |
| @@ -114,7 +115,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende · 60 unmodified lines | |||
| 114 | skinProgram, state); | 115 | skinProgram, state); |
| 115 | } | 116 | } |
| 116 | 117 | ||
| 117 | const auto cachedMode = task.addJob<RenderShadowSetup>("Setup"); | 118 | const auto cachedMode = task.addJob<RenderShadowSetup>("ShadowSetup"); |
| 118 | 119 | ||
| 119 | // CPU jobs: | 120 | // CPU jobs: |
| 120 | // Fetch and cull the items from the scene | 121 | // Fetch and cull the items from the scene |
| @@ -129,7 +130,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende · 8 unmodified lines | |||
| 129 | // GPU jobs: Render to shadow map | 130 | // GPU jobs: Render to shadow map |
| 130 | task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber); | 131 | task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber); |
| 131 | 132 | ||
| 132 | task.addJob<RenderShadowTeardown>("Teardown", cachedMode); | 133 | task.addJob<RenderShadowTeardown>("ShadowTeardown", cachedMode); |
| 133 | } | 134 | } |
| 134 | 135 | ||
| 135 | void RenderShadowTask::configure(const Config& configuration) { | 136 | void RenderShadowTask::configure(const Config& configuration) { |
| @@ -0,0 +1,33 @@ | |||
| 1 | // | ||
| 2 | // RenderViewTask.cpp | ||
| 3 | // render-utils/src/ | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/25/2017. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | #include "RenderViewTask.h" | ||
| 12 | |||
| 13 | #include "RenderShadowTask.h" | ||
| 14 | #include "RenderDeferredTask.h" | ||
| 15 | #include "RenderForwardTask.h" | ||
| 16 | |||
| 17 | |||
| 18 | |||
| 19 | void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) { | ||
| 20 | // auto items = input.get<Input>(); | ||
| 21 | |||
| 22 | task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor); | ||
| 23 | |||
| 24 | const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor); | ||
| 25 | assert(items.canCast<RenderFetchCullSortTask::Output>()); | ||
| 26 | |||
| 27 | if (isDeferred) { | ||
| 28 | task.addJob<RenderDeferredTask>("RenderDeferredTask", items); | ||
| 29 | } else { | ||
| 30 | task.addJob<RenderForwardTask>("Forward", items); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | // | ||
| 2 | // RenderViewTask.h | ||
| 3 | // render-utils/src/ | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/25/2017. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | #pragma once | ||
| 12 | #ifndef hifi_RenderViewTask_h | ||
| 13 | #define hifi_RenderViewTask_h | ||
| 14 | |||
| 15 | #include <render/Engine.h> | ||
| 16 | #include <render/RenderFetchCullSortTask.h> | ||
| 17 | |||
| 18 | |||
| 19 | class RenderViewTask { | ||
| 20 | public: | ||
| 21 | using Input = RenderFetchCullSortTask::Output; | ||
| 22 | using JobModel = render::Task::ModelI<RenderViewTask, Input>; | ||
| 23 | |||
| 24 | RenderViewTask() {} | ||
| 25 | |||
| 26 | void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred); | ||
| 27 | |||
| 28 | }; | ||
| 29 | |||
| 30 | |||
| 31 | #endif // hifi_RenderViewTask_h | ||
| @@ -0,0 +1,126 @@ | |||
| 1 | // | ||
| 2 | // StencilMaskPass.cpp | ||
| 3 | // render-utils/src/ | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/31/17. | ||
| 6 | // Copyright 2016 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #include "StencilMaskPass.h" | ||
| 13 | |||
| 14 | #include <RenderArgs.h> | ||
| 15 | #include <ViewFrustum.h> | ||
| 16 | #include <gpu/Context.h> | ||
| 17 | |||
| 18 | |||
| 19 | #include <gpu/StandardShaderLib.h> | ||
| 20 | |||
| 21 | #include "stencil_drawMask_frag.h" | ||
| 22 | |||
| 23 | using namespace render; | ||
| 24 | |||
| 25 | void PrepareStencil::configure(const Config& config) { | ||
| 26 | _maskMode = config.maskMode; | ||
| 27 | _forceDraw = config.forceDraw; | ||
| 28 | } | ||
| 29 | |||
| 30 | model::MeshPointer PrepareStencil::getMesh() { | ||
| 31 | if (!_mesh) { | ||
| 32 | |||
| 33 | std::vector<glm::vec3> vertices { | ||
| 34 | { -1.0f, -1.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, | ||
| 35 | { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, | ||
| 36 | { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, | ||
| 37 | { 1.0f, -1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f } }; | ||
| 38 | |||
| 39 | std::vector<uint32_t> indices { 0, 7, 1, 1, 3, 2, 3, 5, 4, 5, 7, 6 }; | ||
| 40 | _mesh = model::Mesh::createIndexedTriangles_P3F((uint32_t) vertices.size(), (uint32_t) indices.size(), vertices.data(), indices.data()); | ||
| 41 | } | ||
| 42 | return _mesh; | ||
| 43 | } | ||
| 44 | |||
| 45 | gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { | ||
| 46 | if (!_meshStencilPipeline) { | ||
| 47 | auto vs = gpu::StandardShaderLib::getDrawVertexPositionVS(); | ||
| 48 | auto ps = gpu::StandardShaderLib::getDrawNadaPS(); | ||
| 49 | auto program = gpu::Shader::createProgram(vs, ps); | ||
| 50 | gpu::Shader::makeProgram((*program)); | ||
| 51 | |||
| 52 | auto state = std::make_shared<gpu::State>(); | ||
| 53 | drawMask(*state); | ||
| 54 | state->setColorWriteMask(0); | ||
| 55 | |||
| 56 | _meshStencilPipeline = gpu::Pipeline::create(program, state); | ||
| 57 | } | ||
| 58 | return _meshStencilPipeline; | ||
| 59 | } | ||
| 60 | |||
| 61 | gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { | ||
| 62 | if (!_paintStencilPipeline) { | ||
| 63 | auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); | ||
| 64 | auto ps = gpu::Shader::createPixel(std::string(stencil_drawMask_frag)); | ||
| 65 | auto program = gpu::Shader::createProgram(vs, ps); | ||
| 66 | gpu::Shader::makeProgram((*program)); | ||
| 67 | |||
| 68 | auto state = std::make_shared<gpu::State>(); | ||
| 69 | drawMask(*state); | ||
| 70 | state->setColorWriteMask(0); | ||
| 71 | |||
| 72 | _paintStencilPipeline = gpu::Pipeline::create(program, state); | ||
| 73 | } | ||
| 74 | return _paintStencilPipeline; | ||
| 75 | } | ||
| 76 | |||
| 77 | void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { | ||
| 78 | RenderArgs* args = renderContext->args; | ||
| 79 | |||
| 80 | // Only draw the stencil mask if in HMD mode or not forced. | ||
| 81 | if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) { | ||
| 82 | return; | ||
| 83 | } | ||
| 84 | |||
| 85 | doInBatch(args->_context, [&](gpu::Batch& batch) { | ||
| 86 | batch.enableStereo(false); | ||
| 87 | |||
| 88 | batch.setViewportTransform(args->_viewport); | ||
| 89 | |||
| 90 | if (_maskMode < 0) { | ||
| 91 | batch.setPipeline(getMeshStencilPipeline()); | ||
| 92 | |||
| 93 | auto mesh = getMesh(); | ||
| 94 | batch.setIndexBuffer(mesh->getIndexBuffer()); | ||
| 95 | batch.setInputFormat((mesh->getVertexFormat())); | ||
| 96 | batch.setInputStream(0, mesh->getVertexStream()); | ||
| 97 | |||
| 98 | // Draw | ||
| 99 | auto part = mesh->getPartBuffer().get<model::Mesh::Part>(0); | ||
| 100 | batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); | ||
| 101 | } else { | ||
| 102 | batch.setPipeline(getPaintStencilPipeline()); | ||
| 103 | batch.draw(gpu::TRIANGLE_STRIP, 4); | ||
| 104 | } | ||
| 105 | }); | ||
| 106 | } | ||
| 107 | |||
| 108 | void PrepareStencil::drawMask(gpu::State& state) { | ||
| 109 | state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE)); | ||
| 110 | } | ||
| 111 | |||
| 112 | void PrepareStencil::testMask(gpu::State& state) { | ||
| 113 | state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | ||
| 114 | } | ||
| 115 | |||
| 116 | void PrepareStencil::testBackground(gpu::State& state) { | ||
| 117 | state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_BACKGROUND, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | ||
| 118 | } | ||
| 119 | |||
| 120 | void PrepareStencil::testMaskDrawShape(gpu::State& state) { | ||
| 121 | state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO)); | ||
| 122 | } | ||
| 123 | |||
| 124 | void PrepareStencil::testShape(gpu::State& state) { | ||
| 125 | state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_SHAPE, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | ||
| 126 | } | ||
| @@ -0,0 +1,71 @@ | |||
| 1 | // | ||
| 2 | // StencilMaskPass.h | ||
| 3 | // render-utils/src/ | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/31/17. | ||
| 6 | // Copyright 20154 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | #pragma once | ||
| 12 | #ifndef hifi_StencilMaskPass_h | ||
| 13 | #define hifi_StencilMaskPass_h | ||
| 14 | |||
| 15 | #include <render/Engine.h> | ||
| 16 | #include <gpu/Pipeline.h> | ||
| 17 | #include <model/Geometry.h> | ||
| 18 | |||
| 19 | class PrepareStencilConfig : public render::Job::Config { | ||
| 20 | Q_OBJECT | ||
| 21 | Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty) | ||
| 22 | Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty) | ||
| 23 | |||
| 24 | public: | ||
| 25 | PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {} | ||
| 26 | |||
| 27 | int maskMode { 0 }; | ||
| 28 | bool forceDraw { false }; | ||
| 29 | |||
| 30 | signals: | ||
| 31 | void dirty(); | ||
| 32 | }; | ||
| 33 | |||
| 34 | class PrepareStencil { | ||
| 35 | public: | ||
| 36 | using Config = PrepareStencilConfig; | ||
| 37 | |||
| 38 | using JobModel = render::Job::ModelI<PrepareStencil, gpu::FramebufferPointer, Config>; | ||
| 39 | |||
| 40 | void configure(const Config& config); | ||
| 41 | |||
| 42 | void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& dstFramebuffer); | ||
| 43 | |||
| 44 | static const gpu::int8 STENCIL_MASK = 2; | ||
| 45 | static const gpu::int8 STENCIL_BACKGROUND = 1; | ||
| 46 | static const gpu::int8 STENCIL_SHAPE = 0; | ||
| 47 | |||
| 48 | |||
| 49 | static void drawMask(gpu::State& state); | ||
| 50 | static void testMask(gpu::State& state); | ||
| 51 | static void testBackground(gpu::State& state); | ||
| 52 | static void testMaskDrawShape(gpu::State& state); | ||
| 53 | static void testShape(gpu::State& state); | ||
| 54 | |||
| 55 | |||
| 56 | private: | ||
| 57 | gpu::PipelinePointer _meshStencilPipeline; | ||
| 58 | gpu::PipelinePointer getMeshStencilPipeline(); | ||
| 59 | |||
| 60 | gpu::PipelinePointer _paintStencilPipeline; | ||
| 61 | gpu::PipelinePointer getPaintStencilPipeline(); | ||
| 62 | |||
| 63 | model::MeshPointer _mesh; | ||
| 64 | model::MeshPointer getMesh(); | ||
| 65 | |||
| 66 | int _maskMode { 0 }; | ||
| 67 | bool _forceDraw { false }; | ||
| 68 | }; | ||
| 69 | |||
| 70 | |||
| 71 | #endif // hifi_StencilMaskPass_h | ||
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | #include <gpu/Context.h> | 15 | #include <gpu/Context.h> |
| 16 | #include <gpu/StandardShaderLib.h> | 16 | #include <gpu/StandardShaderLib.h> |
| 17 | 17 | #include "StencilMaskPass.h" | |
| 18 | 18 | ||
| 19 | const int DepthLinearPass_FrameTransformSlot = 0; | 19 | const int DepthLinearPass_FrameTransformSlot = 0; |
| 20 | const int DepthLinearPass_DepthMapSlot = 0; | 20 | const int DepthLinearPass_DepthMapSlot = 0; |
| @@ -224,7 +224,7 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { · 203 unmodified lines | |||
| 224 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | 224 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); |
| 225 | 225 | ||
| 226 | // Stencil test the curvature pass for objects pixels only, not the background | 226 | // Stencil test the curvature pass for objects pixels only, not the background |
| 227 | state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | 227 | PrepareStencil::testShape(*state); |
| 228 | 228 | ||
| 229 | state->setColorWriteMask(true, false, false, false); | 229 | state->setColorWriteMask(true, false, false, false); |
| 230 | 230 | ||
| @@ -250,6 +250,7 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { · 19 unmodified lines | |||
| 250 | 250 | ||
| 251 | 251 | ||
| 252 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | 252 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); |
| 253 | PrepareStencil::testShape(*state); | ||
| 253 | 254 | ||
| 254 | state->setColorWriteMask(true, true, true, false); | 255 | state->setColorWriteMask(true, true, true, false); |
| 255 | 256 | ||
| @@ -554,7 +555,7 @@ const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { · 298 unmodified lines | |||
| 554 | 555 | ||
| 555 | #ifdef USE_STENCIL_TEST | 556 | #ifdef USE_STENCIL_TEST |
| 556 | // Stencil test the curvature pass for objects pixels only, not the background | 557 | // Stencil test the curvature pass for objects pixels only, not the background |
| 557 | state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); | 558 | PrepareStencil::testShape(*state); |
| 558 | #endif | 559 | #endif |
| 559 | // Good to go add the brand new pipeline | 560 | // Good to go add the brand new pipeline |
| 560 | _curvaturePipeline = gpu::Pipeline::create(program, state); | 561 | _curvaturePipeline = gpu::Pipeline::create(program, state); |
| @@ -15,7 +15,7 @@ · 14 unmodified lines | |||
| 15 | #include <gpu/StandardShaderLib.h> | 15 | #include <gpu/StandardShaderLib.h> |
| 16 | 16 | ||
| 17 | #include <RenderArgs.h> | 17 | #include <RenderArgs.h> |
| 18 | 18 | #include "StencilMaskPass.h" | |
| 19 | #include "FramebufferCache.h" | 19 | #include "FramebufferCache.h" |
| 20 | 20 | ||
| 21 | #include "toneMapping_frag.h" | 21 | #include "toneMapping_frag.h" |
| @@ -10,18 +10,203 @@ · 9 unmodified lines | |||
| 10 | // | 10 | // |
| 11 | #include "ZoneRenderer.h" | 11 | #include "ZoneRenderer.h" |
| 12 | 12 | ||
| 13 | |||
| 14 | #include <gpu/Context.h> | ||
| 15 | #include <gpu/StandardShaderLib.h> | ||
| 16 | |||
| 13 | #include <render/FilterTask.h> | 17 | #include <render/FilterTask.h> |
| 14 | #include <render/DrawTask.h> | 18 | #include <render/DrawTask.h> |
| 15 | 19 | ||
| 20 | #include "StencilMaskPass.h" | ||
| 21 | #include "DeferredLightingEffect.h" | ||
| 22 | |||
| 23 | #include "zone_drawKeyLight_frag.h" | ||
| 24 | #include "zone_drawAmbient_frag.h" | ||
| 25 | #include "zone_drawSkybox_frag.h" | ||
| 26 | |||
| 27 | |||
| 16 | using namespace render; | 28 | using namespace render; |
| 17 | 29 | ||
| 30 | class SetupZones { | ||
| 31 | public: | ||
| 32 | using Inputs = render::ItemBounds; | ||
| 33 | using JobModel = render::Job::ModelI<SetupZones, Inputs>; | ||
| 34 | |||
| 35 | SetupZones() {} | ||
| 36 | |||
| 37 | void run(const RenderContextPointer& context, const Inputs& inputs); | ||
| 38 | |||
| 39 | protected: | ||
| 40 | }; | ||
| 41 | |||
| 18 | const Selection::Name ZoneRendererTask::ZONES_SELECTION { "RankedZones" }; | 42 | const Selection::Name ZoneRendererTask::ZONES_SELECTION { "RankedZones" }; |
| 19 | 43 | ||
| 20 | void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& ouput) { | 44 | void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& ouput) { |
| 21 | 45 | // Filter out the sorted list of zones | |
| 22 | const auto zoneItems = task.addJob<render::SelectSortItems>("FilterZones", input, ZONES_SELECTION.c_str()); | 46 | const auto zoneItems = task.addJob<render::SelectSortItems>("FilterZones", input, ZONES_SELECTION.c_str()); |
| 23 | 47 | ||
| 24 | // just draw them... | 48 | // just setup the current zone env |
| 25 | task.addJob<DrawBounds>("DrawZones", zoneItems); | 49 | task.addJob<SetupZones>("SetupZones", zoneItems); |
| 50 | |||
| 51 | ouput = zoneItems; | ||
| 52 | } | ||
| 53 | |||
| 54 | void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) { | ||
| 55 | |||
| 56 | auto backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage(); | ||
| 57 | backgroundStage->_currentFrame.clear(); | ||
| 58 | |||
| 59 | // call render in the correct order first... | ||
| 60 | render::renderItems(context, inputs); | ||
| 61 | |||
| 62 | } | ||
| 63 | |||
| 64 | const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { | ||
| 65 | if (!_keyLightPipeline) { | ||
| 66 | auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); | ||
| 67 | auto ps = gpu::Shader::createPixel(std::string(zone_drawKeyLight_frag)); | ||
| 68 | gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); | ||
| 69 | |||
| 70 | gpu::Shader::BindingSet slotBindings; | ||
| 71 | slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); | ||
| 72 | slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ZONE_KEYLIGHT_BUFFER)); | ||
| 73 | |||
| 74 | gpu::Shader::makeProgram(*program, slotBindings); | ||
| 75 | |||
| 76 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | ||
| 77 | |||
| 78 | PrepareStencil::testMask(*state); | ||
| 79 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); | ||
| 80 | _keyLightPipeline = gpu::Pipeline::create(program, state); | ||
| 81 | } | ||
| 82 | return _keyLightPipeline; | ||
| 83 | } | ||
| 84 | |||
| 85 | const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { | ||
| 86 | if (!_ambientPipeline) { | ||
| 87 | auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); | ||
| 88 | auto ps = gpu::Shader::createPixel(std::string(zone_drawAmbient_frag)); | ||
| 89 | gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); | ||
| 90 | |||
| 91 | gpu::Shader::BindingSet slotBindings; | ||
| 92 | slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); | ||
| 93 | slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), ZONE_AMBIENT_BUFFER)); | ||
| 94 | slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ZONE_AMBIENT_MAP)); | ||
| 95 | |||
| 96 | gpu::Shader::makeProgram(*program, slotBindings); | ||
| 97 | |||
| 98 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | ||
| 99 | |||
| 100 | PrepareStencil::testMask(*state); | ||
| 101 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); | ||
| 102 | _ambientPipeline = gpu::Pipeline::create(program, state); | ||
| 103 | } | ||
| 104 | return _ambientPipeline; | ||
| 26 | } | 105 | } |
| 106 | const gpu::PipelinePointer& DebugZoneLighting::getBackgroundPipeline() { | ||
| 107 | if (!_backgroundPipeline) { | ||
| 108 | auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); | ||
| 109 | auto ps = gpu::Shader::createPixel(std::string(zone_drawSkybox_frag)); | ||
| 110 | gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); | ||
| 111 | |||
| 112 | gpu::Shader::BindingSet slotBindings; | ||
| 113 | slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); | ||
| 114 | slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ZONE_SKYBOX_MAP)); | ||
| 115 | slotBindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), ZONE_SKYBOX_BUFFER)); | ||
| 116 | |||
| 117 | gpu::Shader::makeProgram(*program, slotBindings); | ||
| 118 | |||
| 119 | gpu::StatePointer state = gpu::StatePointer(new gpu::State()); | ||
| 120 | |||
| 121 | PrepareStencil::testMask(*state); | ||
| 122 | state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); | ||
| 123 | _backgroundPipeline = gpu::Pipeline::create(program, state); | ||
| 124 | } | ||
| 125 | return _backgroundPipeline; | ||
| 126 | } | ||
| 127 | |||
| 128 | void DebugZoneLighting::run(const render::RenderContextPointer& context, const Inputs& inputs) { | ||
| 129 | RenderArgs* args = context->args; | ||
| 130 | |||
| 131 | auto deferredTransform = inputs; | ||
| 132 | |||
| 133 | auto lightStage = DependencyManager::get<DeferredLightingEffect>()->getLightStage(); | ||
| 134 | std::vector<model::LightPointer> keyLightStack; | ||
| 135 | if (lightStage && lightStage->_currentFrame._sunLights.size()) { | ||
| 136 | for (auto index : lightStage->_currentFrame._sunLights) { | ||
| 137 | keyLightStack.push_back(lightStage->getLight(index)); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | keyLightStack.push_back(DependencyManager::get<DeferredLightingEffect>()->getGlobalLight()); | ||
| 27 | 141 | ||
| 142 | std::vector<model::LightPointer> ambientLightStack; | ||
| 143 | if (lightStage && lightStage->_currentFrame._ambientLights.size()) { | ||
| 144 | for (auto index : lightStage->_currentFrame._ambientLights) { | ||
| 145 | ambientLightStack.push_back(lightStage->getLight(index)); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | ambientLightStack.push_back(DependencyManager::get<DeferredLightingEffect>()->getGlobalLight()); | ||
| 149 | |||
| 150 | |||
| 151 | auto backgroundStage = DependencyManager::get<DeferredLightingEffect>()->getBackgroundStage(); | ||
| 152 | std::vector<model::SkyboxPointer> skyboxStack; | ||
| 153 | if (backgroundStage && backgroundStage->_currentFrame._backgrounds.size()) { | ||
| 154 | for (auto index : backgroundStage->_currentFrame._backgrounds) { | ||
| 155 | auto background = backgroundStage->getBackground(index); | ||
| 156 | if (background) { | ||
| 157 | skyboxStack.push_back(background->getSkybox()); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | skyboxStack.push_back(DependencyManager::get<DeferredLightingEffect>()->getDefaultSkybox()); | ||
| 162 | |||
| 163 | |||
| 164 | gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { | ||
| 165 | |||
| 166 | batch.setViewportTransform(args->_viewport); | ||
| 167 | auto viewFrustum = args->getViewFrustum(); | ||
| 168 | batch.setProjectionTransform(viewFrustum.getProjection()); | ||
| 169 | batch.resetViewTransform(); | ||
| 170 | |||
| 171 | Transform model; | ||
| 172 | |||
| 173 | batch.setUniformBuffer(ZONE_DEFERRED_TRANSFORM_BUFFER, deferredTransform->getFrameTransformBuffer()); | ||
| 174 | |||
| 175 | batch.setPipeline(getKeyLightPipeline()); | ||
| 176 | auto numKeys = (int) keyLightStack.size(); | ||
| 177 | for (int i = numKeys - 1; i >= 0; i--) { | ||
| 178 | model.setTranslation(glm::vec3(-4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); | ||
| 179 | batch.setModelTransform(model); | ||
| 180 | if (keyLightStack[i]) { | ||
| 181 | batch.setUniformBuffer(ZONE_KEYLIGHT_BUFFER, keyLightStack[i]->getLightSchemaBuffer()); | ||
| 182 | batch.draw(gpu::TRIANGLE_STRIP, 4); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | batch.setPipeline(getAmbientPipeline()); | ||
| 187 | auto numAmbients = (int) ambientLightStack.size(); | ||
| 188 | for (int i = numAmbients - 1; i >= 0; i--) { | ||
| 189 | model.setTranslation(glm::vec3(0.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); | ||
| 190 | batch.setModelTransform(model); | ||
| 191 | if (ambientLightStack[i]) { | ||
| 192 | batch.setUniformBuffer(ZONE_AMBIENT_BUFFER, ambientLightStack[i]->getAmbientSchemaBuffer()); | ||
| 193 | if (ambientLightStack[i]->getAmbientMap()) { | ||
| 194 | batch.setResourceTexture(ZONE_AMBIENT_MAP, ambientLightStack[i]->getAmbientMap()); | ||
| 195 | } | ||
| 196 | batch.draw(gpu::TRIANGLE_STRIP, 4); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | batch.setPipeline(getBackgroundPipeline()); | ||
| 201 | auto numBackgrounds = (int) skyboxStack.size(); | ||
| 202 | for (int i = numBackgrounds - 1; i >= 0; i--) { | ||
| 203 | model.setTranslation(glm::vec3(4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); | ||
| 204 | batch.setModelTransform(model); | ||
| 205 | if (skyboxStack[i]) { | ||
| 206 | batch.setResourceTexture(ZONE_SKYBOX_MAP, skyboxStack[i]->getCubemap()); | ||
| 207 | batch.setUniformBuffer(ZONE_SKYBOX_BUFFER, skyboxStack[i]->getSchemaBuffer()); | ||
| 208 | batch.draw(gpu::TRIANGLE_STRIP, 4); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | }); | ||
| 212 | } | ||
| @@ -14,12 +14,15 @@ · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | #include "render/Engine.h" | 15 | #include "render/Engine.h" |
| 16 | 16 | ||
| 17 | #include "DeferredFrameTransform.h" | ||
| 18 | |||
| 17 | class ZoneRendererConfig : public render::Task::Config { | 19 | class ZoneRendererConfig : public render::Task::Config { |
| 18 | Q_OBJECT | 20 | Q_OBJECT |
| 19 | Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) | 21 | Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) |
| 20 | public: | 22 | public: |
| 21 | 23 | ||
| 22 | ZoneRendererConfig() : render::Task::Config(false) {} | 24 | ZoneRendererConfig() : render::Task::Config( |
| 25 | ) {} | ||
| 23 | 26 | ||
| 24 | int maxDrawn { -1 }; | 27 | int maxDrawn { -1 }; |
| 25 | 28 | ||
| @@ -49,4 +52,40 @@ protected: · 23 unmodified lines | |||
| 49 | int _maxDrawn; // initialized by Config | 52 | int _maxDrawn; // initialized by Config |
| 50 | }; | 53 | }; |
| 51 | 54 | ||
| 55 | class DebugZoneLighting { | ||
| 56 | public: | ||
| 57 | class Config : public render::JobConfig { | ||
| 58 | public: | ||
| 59 | Config(bool enabled = false) : JobConfig(enabled) {} | ||
| 60 | }; | ||
| 61 | |||
| 62 | using Inputs = DeferredFrameTransformPointer; | ||
| 63 | using JobModel = render::Job::ModelI<DebugZoneLighting, Inputs, Config>; | ||
| 64 | |||
| 65 | DebugZoneLighting() {} | ||
| 66 | |||
| 67 | void configure(const Config& configuration) {} | ||
| 68 | void run(const render::RenderContextPointer& context, const Inputs& inputs); | ||
| 69 | |||
| 70 | protected: | ||
| 71 | |||
| 72 | enum Slots { | ||
| 73 | ZONE_DEFERRED_TRANSFORM_BUFFER = 0, | ||
| 74 | ZONE_KEYLIGHT_BUFFER, | ||
| 75 | ZONE_AMBIENT_BUFFER, | ||
| 76 | ZONE_AMBIENT_MAP, | ||
| 77 | ZONE_SKYBOX_BUFFER, | ||
| 78 | ZONE_SKYBOX_MAP, | ||
| 79 | }; | ||
| 80 | |||
| 81 | gpu::PipelinePointer _keyLightPipeline; | ||
| 82 | gpu::PipelinePointer _ambientPipeline; | ||
| 83 | gpu::PipelinePointer _backgroundPipeline; | ||
| 84 | |||
| 85 | const gpu::PipelinePointer& getKeyLightPipeline(); | ||
| 86 | const gpu::PipelinePointer& getAmbientPipeline(); | ||
| 87 | const gpu::PipelinePointer& getBackgroundPipeline(); | ||
| 88 | }; | ||
| 89 | |||
| 90 | |||
| 52 | #endif | 91 | #endif |
| @@ -1,47 +0,0 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // directional_light.frag | ||
| 6 | // fragment shader | ||
| 7 | // | ||
| 8 | // Created by Andrzej Kapolka on 9/3/14. | ||
| 9 | // Copyright 2016 High Fidelity, Inc. | ||
| 10 | // | ||
| 11 | // Distributed under the Apache License, Version 2.0. | ||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 13 | // | ||
| 14 | |||
| 15 | <@include DeferredBufferRead.slh@> | ||
| 16 | <@include DeferredGlobalLight.slh@> | ||
| 17 | |||
| 18 | <$declareEvalLightmappedColor()$> | ||
| 19 | <$declareEvalAmbientGlobalColor()$> | ||
| 20 | |||
| 21 | in vec2 _texCoord0; | ||
| 22 | out vec4 _fragColor; | ||
| 23 | |||
| 24 | void main(void) { | ||
| 25 | DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); | ||
| 26 | DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); | ||
| 27 | |||
| 28 | float shadowAttenuation = 1.0; | ||
| 29 | |||
| 30 | if (frag.mode == FRAG_MODE_UNLIT) { | ||
| 31 | discard; | ||
| 32 | } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { | ||
| 33 | discard; | ||
| 34 | } else { | ||
| 35 | vec3 color = evalAmbientGlobalColor( | ||
| 36 | getViewInverse(), | ||
| 37 | shadowAttenuation, | ||
| 38 | frag.obscurance, | ||
| 39 | frag.position.xyz, | ||
| 40 | frag.normal, | ||
| 41 | frag.albedo, | ||
| 42 | frag.fresnel, | ||
| 43 | frag.metallic, | ||
| 44 | frag.roughness); | ||
| 45 | _fragColor = vec4(color, 1.0); | ||
| 46 | } | ||
| 47 | } | ||
| @@ -1,49 +0,0 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // directional_light_shadow.frag | ||
| 6 | // fragment shader | ||
| 7 | // | ||
| 8 | // Created by Zach Pomerantz on 1/18/2016. | ||
| 9 | // Copyright 2016 High Fidelity, Inc. | ||
| 10 | // | ||
| 11 | // Distributed under the Apache License, Version 2.0. | ||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 13 | // | ||
| 14 | |||
| 15 | <@include Shadow.slh@> | ||
| 16 | <@include DeferredBufferRead.slh@> | ||
| 17 | <@include DeferredGlobalLight.slh@> | ||
| 18 | |||
| 19 | <$declareEvalLightmappedColor()$> | ||
| 20 | <$declareEvalAmbientGlobalColor()$> | ||
| 21 | |||
| 22 | in vec2 _texCoord0; | ||
| 23 | out vec4 _fragColor; | ||
| 24 | |||
| 25 | void main(void) { | ||
| 26 | DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); | ||
| 27 | DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); | ||
| 28 | |||
| 29 | vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); | ||
| 30 | float shadowAttenuation = evalShadowAttenuation(worldPos); | ||
| 31 | |||
| 32 | if (frag.mode == FRAG_MODE_UNLIT) { | ||
| 33 | discard; | ||
| 34 | } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { | ||
| 35 | discard; | ||
| 36 | } else { | ||
| 37 | vec3 color = evalAmbientGlobalColor( | ||
| 38 | getViewInverse(), | ||
| 39 | shadowAttenuation, | ||
| 40 | frag.obscurance, | ||
| 41 | frag.position.xyz, | ||
| 42 | frag.normal, | ||
| 43 | frag.albedo, | ||
| 44 | frag.fresnel, | ||
| 45 | frag.metallic, | ||
| 46 | frag.roughness); | ||
| 47 | _fragColor = vec4(color, 1.0); | ||
| 48 | } | ||
| 49 | } | ||
| @@ -1,27 +0,0 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | |||
| 6 | // hit_effect.frag | ||
| 7 | // fragment shader | ||
| 8 | // | ||
| 9 | // Created by Eric Levin on 7/20 | ||
| 10 | // Copyright 2015 High Fidelity, Inc. | ||
| 11 | // | ||
| 12 | // Distributed under the Apache License, Version 2.0. | ||
| 13 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 14 | // | ||
| 15 | |||
| 16 | |||
| 17 | <@include DeferredBufferWrite.slh@> | ||
| 18 | |||
| 19 | in vec2 varQuadPosition; | ||
| 20 | out vec4 outFragColor; | ||
| 21 | |||
| 22 | void main(void) { | ||
| 23 | vec2 center = vec2(0.0, 0.0); | ||
| 24 | float distFromCenter = distance( vec2(0.0, 0.0), varQuadPosition); | ||
| 25 | float alpha = mix(0.0, 0.5, pow(distFromCenter,5.)); | ||
| 26 | outFragColor = vec4(1.0, 0.0, 0.0, alpha); | ||
| 27 | } | ||
| @@ -1,85 +0,0 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // point_light.frag | ||
| 6 | // fragment shader | ||
| 7 | // | ||
| 8 | // Created by Sam Gateau on 9/18/15. | ||
| 9 | // Copyright 2014 High Fidelity, Inc. | ||
| 10 | // | ||
| 11 | // Distributed under the Apache License, Version 2.0. | ||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 13 | // | ||
| 14 | <! | ||
| 15 | // Everything about deferred buffer | ||
| 16 | <@include DeferredBufferRead.slh@> | ||
| 17 | |||
| 18 | <$declareDeferredCurvature()$> | ||
| 19 | |||
| 20 | // Everything about light | ||
| 21 | <@include model/Light.slh@> | ||
| 22 | <$declareLightBuffer()$> | ||
| 23 | |||
| 24 | <@include LightingModel.slh@> | ||
| 25 | |||
| 26 | <@include LightPoint.slh@> | ||
| 27 | <$declareLightingPoint(supportScattering)$> | ||
| 28 | |||
| 29 | |||
| 30 | uniform vec4 texcoordFrameTransform; | ||
| 31 | |||
| 32 | in vec4 _texCoord0;!> | ||
| 33 | out vec4 _fragColor; | ||
| 34 | |||
| 35 | void main(void) { | ||
| 36 | _fragColor = vec4(1.0, 1.0, 1.0, 1.0); | ||
| 37 | |||
| 38 | <! | ||
| 39 | DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); | ||
| 40 | |||
| 41 | // Grab the fragment data from the uv | ||
| 42 | vec2 texCoord = _texCoord0.st / _texCoord0.q; | ||
| 43 | texCoord *= texcoordFrameTransform.zw; | ||
| 44 | texCoord += texcoordFrameTransform.xy; | ||
| 45 | |||
| 46 | DeferredFragment frag = unpackDeferredFragment(deferredTransform, texCoord); | ||
| 47 | |||
| 48 | if (frag.mode == FRAG_MODE_UNLIT) { | ||
| 49 | discard; | ||
| 50 | } | ||
| 51 | |||
| 52 | // Need the light now | ||
| 53 | Light light = getLight(); | ||
| 54 | |||
| 55 | // Frag pos in world | ||
| 56 | mat4 invViewMat = getViewInverse(); | ||
| 57 | vec4 fragPos = invViewMat * frag.position; | ||
| 58 | |||
| 59 | // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space | ||
| 60 | vec4 fragLightVecLen2; | ||
| 61 | if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragPos.xyz, fragLightVecLen2)) { | ||
| 62 | discard; | ||
| 63 | } | ||
| 64 | |||
| 65 | // Frag to eye vec | ||
| 66 | vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); | ||
| 67 | vec3 fragEyeDir = normalize(fragEyeVector.xyz); | ||
| 68 | |||
| 69 | |||
| 70 | vec3 diffuse; | ||
| 71 | vec3 specular; | ||
| 72 | vec4 midNormalCurvature; | ||
| 73 | vec4 lowNormalCurvature; | ||
| 74 | if (frag.mode == FRAG_MODE_SCATTERING) { | ||
| 75 | unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature); | ||
| 76 | } | ||
| 77 | evalLightingPoint(diffuse, specular, light, | ||
| 78 | fragLightVecLen2.xyz, fragEyeDir, frag.normal, frag.roughness, | ||
| 79 | frag.metallic, frag.fresnel, frag.albedo, 1.0, | ||
| 80 | frag.scattering, midNormalCurvature, lowNormalCurvature); | ||
| 81 | |||
| 82 | _fragColor.rgb += diffuse; | ||
| 83 | _fragColor.rgb += specular; | ||
| 84 | !> | ||
| 85 | } | ||
| @@ -1,115 +0,0 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // spot_light.frag | ||
| 6 | // fragment shader | ||
| 7 | // | ||
| 8 | // Created by Sam Gateau on 9/18/15. | ||
| 9 | // Copyright 2014 High Fidelity, Inc. | ||
| 10 | // | ||
| 11 | // Distributed under the Apache License, Version 2.0. | ||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 13 | // | ||
| 14 | |||
| 15 | // Everything about deferred buffer | ||
| 16 | <!<@include DeferredBufferRead.slh@> | ||
| 17 | |||
| 18 | <$declareDeferredCurvature()$> | ||
| 19 | |||
| 20 | // Everything about light | ||
| 21 | <@include model/Light.slh@> | ||
| 22 | <$declareLightBuffer(256)$> | ||
| 23 | uniform lightIndexBuffer { | ||
| 24 | int lightIndex[256]; | ||
| 25 | }; | ||
| 26 | <@include LightingModel.slh@> | ||
| 27 | |||
| 28 | <@include LightPoint.slh@> | ||
| 29 | <$declareLightingPoint(supportScattering)$> | ||
| 30 | <@include LightSpot.slh@> | ||
| 31 | <$declareLightingSpot(supportScattering)$> | ||
| 32 | |||
| 33 | //uniform vec4 texcoordFrameTransform; | ||
| 34 | !> | ||
| 35 | |||
| 36 | |||
| 37 | //in vec4 _texCoord0; | ||
| 38 | //flat in int instanceID; | ||
| 39 | out vec4 _fragColor; | ||
| 40 | |||
| 41 | void main(void) { | ||
| 42 | _fragColor = vec4(1.0, 1.0, 1.0, 1.0); | ||
| 43 | |||
| 44 | // DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); | ||
| 45 | |||
| 46 | // Grab the fragment data from the uv | ||
| 47 | //vec2 texCoord = _texCoord0.st;/* / _texCoord0.q; | ||
| 48 | /*texCoord *= texcoordFrameTransform.zw; | ||
| 49 | texCoord += texcoordFrameTransform.xy;*/ | ||
| 50 | /* | ||
| 51 | vec4 fragPosition = unpackDeferredPositionFromZeye(texCoord); | ||
| 52 | DeferredFragment frag = unpackDeferredFragmentNoPosition(texCoord); | ||
| 53 | |||
| 54 | if (frag.mode == FRAG_MODE_UNLIT) { | ||
| 55 | discard; | ||
| 56 | } | ||
| 57 | |||
| 58 | |||
| 59 | // frag.depthVal = depthValue; | ||
| 60 | frag.position = fragPosition; | ||
| 61 | |||
| 62 | vec4 midNormalCurvature; | ||
| 63 | vec4 lowNormalCurvature; | ||
| 64 | if (frag.mode == FRAG_MODE_SCATTERING) { | ||
| 65 | unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature); | ||
| 66 | } | ||
| 67 | |||
| 68 | // Frag pos in world | ||
| 69 | mat4 invViewMat = getViewInverse(); | ||
| 70 | vec4 fragPos = invViewMat * fragPosition; | ||
| 71 | |||
| 72 | // Frag to eye vec | ||
| 73 | vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); | ||
| 74 | vec3 fragEyeDir = normalize(fragEyeVector.xyz); | ||
| 75 | |||
| 76 | int numLights = lightIndex[0]; | ||
| 77 | for (int i = 0; i < numLights; i++) { | ||
| 78 | // Need the light now | ||
| 79 | Light light = getLight(lightIndex[i + 1]); | ||
| 80 | bool isSpot = light_isSpot(light); | ||
| 81 | // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space | ||
| 82 | vec4 fragLightVecLen2; | ||
| 83 | vec4 fragLightDirLen; | ||
| 84 | float cosSpotAngle; | ||
| 85 | if (isSpot) { | ||
| 86 | if (!clipFragToLightVolumeSpot(light, fragPos.xyz, fragLightVecLen2, fragLightDirLen, cosSpotAngle)) { | ||
| 87 | continue; | ||
| 88 | } | ||
| 89 | } else { | ||
| 90 | if (!clipFragToLightVolumePoint(light, fragPos.xyz, fragLightVecLen2)) { | ||
| 91 | continue; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | vec3 diffuse; | ||
| 96 | vec3 specular; | ||
| 97 | |||
| 98 | if (isSpot) { | ||
| 99 | evalLightingSpot(diffuse, specular, light, | ||
| 100 | fragLightDirLen.xyzw, cosSpotAngle, fragEyeDir, frag.normal, frag.roughness, | ||
| 101 | frag.metallic, frag.fresnel, frag.albedo, 1.0, | ||
| 102 | frag.scattering, midNormalCurvature, lowNormalCurvature); | ||
| 103 | } else { | ||
| 104 | evalLightingPoint(diffuse, specular, light, | ||
| 105 | fragLightVecLen2.xyz, fragEyeDir, frag.normal, frag.roughness, | ||
| 106 | frag.metallic, frag.fresnel, frag.albedo, 1.0, | ||
| 107 | frag.scattering, midNormalCurvature, lowNormalCurvature); | ||
| 108 | } | ||
| 109 | |||
| 110 | _fragColor.rgb += diffuse; | ||
| 111 | _fragColor.rgb += specular; | ||
| 112 | } | ||
| 113 | */ | ||
| 114 | } | ||
| 115 | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // stencil_drawMask.slf | ||
| 6 | // fragment shader | ||
| 7 | // | ||
| 8 | // Created by Sam Gateau on 5/31/17. | ||
| 9 | // Copyright 2017 High Fidelity, Inc. | ||
| 10 | // | ||
| 11 | // Distributed under the Apache License, Version 2.0. | ||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 13 | // | ||
| 14 | |||
| 15 | in vec2 varTexCoord0; | ||
| 16 | |||
| 17 | float aspectRatio = 0.95; | ||
| 18 | |||
| 19 | void main(void) { | ||
| 20 | vec2 pos = varTexCoord0 * 2.0 - vec2(1.0); | ||
| 21 | pos.x = aspectRatio * (pos.x * (pos.x > 0.0 ? 2.0 : -2.0) - 1.0); | ||
| 22 | if (1.0 - dot(pos.xy, pos.xy) > 0.0 ) discard; | ||
| 23 | } | ||
| @@ -14,6 +14,8 @@ · 13 unmodified lines | |||
| 14 | #include "../RenderUtilsLogging.h" | 14 | #include "../RenderUtilsLogging.h" |
| 15 | #include "FontFamilies.h" | 15 | #include "FontFamilies.h" |
| 16 | 16 | ||
| 17 | static std::mutex fontMutex; | ||
| 18 | |||
| 17 | struct TextureVertex { | 19 | struct TextureVertex { |
| 18 | glm::vec2 pos; | 20 | glm::vec2 pos; |
| 19 | glm::vec2 tex; | 21 | glm::vec2 tex; |
| @@ -56,6 +58,7 @@ Font::Pointer Font::load(QIODevice& fontFile) { · 36 unmodified lines | |||
| 56 | } | 58 | } |
| 57 | 59 | ||
| 58 | Font::Pointer Font::load(const QString& family) { | 60 | Font::Pointer Font::load(const QString& family) { |
| 61 | std::lock_guard<std::mutex> lock(fontMutex); | ||
| 59 | if (!LOADED_FONTS.contains(family)) { | 62 | if (!LOADED_FONTS.contains(family)) { |
| 60 | 63 | ||
| 61 | static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; | 64 | static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; |
| @@ -31,10 +31,10 @@ public: · 30 unmodified lines | |||
| 31 | const glm::vec4* color, EffectType effectType, | 31 | const glm::vec4* color, EffectType effectType, |
| 32 | const glm::vec2& bound, bool layered = false); | 32 | const glm::vec2& bound, bool layered = false); |
| 33 | 33 | ||
| 34 | static Pointer load(QIODevice& fontFile); | ||
| 35 | static Pointer load(const QString& family); | 34 | static Pointer load(const QString& family); |
| 36 | 35 | ||
| 37 | private: | 36 | private: |
| 37 | static Pointer load(QIODevice& fontFile); | ||
| 38 | QStringList tokenizeForWrapping(const QString& str) const; | 38 | QStringList tokenizeForWrapping(const QString& str) const; |
| 39 | QStringList splitLines(const QString& str) const; | 39 | QStringList splitLines(const QString& str) const; |
| 40 | glm::vec2 computeTokenExtent(const QString& str) const; | 40 | glm::vec2 computeTokenExtent(const QString& str) const; |
| @@ -0,0 +1,37 @@ | |||
| 1 | |||
| 2 | // Generated on <$_SCRIBE_DATE$> | ||
| 3 | // | ||
| 4 | // Created by Sam Gateau on 5/17/17. | ||
| 5 | // Copyright 2017 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | // | ||
| 10 | |||
| 11 | <@include DeferredTransform.slh@> | ||
| 12 | <$declareDeferredFrameTransform()$> | ||
| 13 | |||
| 14 | <@func evalGlobeWidget()@> | ||
| 15 | const float SCOPE_RADIUS = 1.0; | ||
| 16 | const float SCOPE_RADIUS2 = SCOPE_RADIUS * SCOPE_RADIUS; | ||
| 17 | const float EDGE_HALFWIDTH = 0.025; | ||
| 18 | const float EDGE_HALFWIDTH2 = EDGE_HALFWIDTH * EDGE_HALFWIDTH; | ||
| 19 | const float OUT_RADIUS = SCOPE_RADIUS + EDGE_HALFWIDTH; | ||
| 20 | |||
| 21 | vec2 sphereUV = (varTexCoord0.xy * 2.0 - vec2(1.0)) * OUT_RADIUS; | ||
| 22 | float sphereR2 = dot(sphereUV.xy, sphereUV.xy); | ||
| 23 | if (sphereR2 > OUT_RADIUS * OUT_RADIUS) { | ||
| 24 | discard; | ||
| 25 | } | ||
| 26 | float sphereR = sqrt(sphereR2); | ||
| 27 | |||
| 28 | float edgeFalloff = (SCOPE_RADIUS - sphereR) / (EDGE_HALFWIDTH); | ||
| 29 | float edgeFalloff2 = min(1.0, edgeFalloff * edgeFalloff); | ||
| 30 | |||
| 31 | vec4 base = vec4(0.0, 0.0, 0.0, 1.0 - edgeFalloff2); | ||
| 32 | if (sphereR2 > SCOPE_RADIUS2) { | ||
| 33 | _fragColor = base; | ||
| 34 | return; | ||
| 35 | } | ||
| 36 | <@endfunc@> | ||
| 37 | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/16/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | <@include zone_draw.slh@> | ||
| 12 | |||
| 13 | <@include model/Light.slh@> | ||
| 14 | |||
| 15 | <@include LightingModel.slh@> | ||
| 16 | <$declareLightAmbientBuffer()$> | ||
| 17 | |||
| 18 | <@include LightAmbient.slh@> | ||
| 19 | |||
| 20 | <$declareLightingAmbient(_SCRIBE_NULL, 1, _SCRIBE_NULL, _SCRIBE_NULL)$> | ||
| 21 | |||
| 22 | in vec2 varTexCoord0; | ||
| 23 | out vec4 _fragColor; | ||
| 24 | |||
| 25 | void main(void) { | ||
| 26 | |||
| 27 | <$evalGlobeWidget()$> | ||
| 28 | |||
| 29 | vec3 spherePos = normalize(vec3(sphereUV, sqrt(1.0 - sphereR2))); | ||
| 30 | |||
| 31 | |||
| 32 | vec3 fragNormal = vec3(getViewInverse() * vec4(spherePos, 0.0)); | ||
| 33 | |||
| 34 | |||
| 35 | LightAmbient lightAmbient = getLightAmbient(); | ||
| 36 | |||
| 37 | |||
| 38 | float roughness = 0.1; | ||
| 39 | float levels = getLightAmbientMapNumMips(lightAmbient); | ||
| 40 | float lod = min(((roughness)* levels), levels); | ||
| 41 | vec3 ambientMap = evalSkyboxLight(fragNormal, lod).xyz; | ||
| 42 | vec3 ambientSH = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz; | ||
| 43 | |||
| 44 | // vec3 ambient = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz; | ||
| 45 | // _fragColor = vec4( 0.5 * (fragNormal + vec3(1.0)), 1.0); | ||
| 46 | |||
| 47 | vec3 color = (sphereUV.x > 0 ? ambientMap : ambientSH); | ||
| 48 | |||
| 49 | color = color * 1.0 - base.w + base.xyz * base.w; | ||
| 50 | const float INV_GAMMA_22 = 1.0 / 2.2; | ||
| 51 | _fragColor = vec4(pow(color, vec3(INV_GAMMA_22)), 1.0); | ||
| 52 | } | ||
| 53 | |||
| 54 | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/16/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | <@include zone_draw.slh@> | ||
| 12 | |||
| 13 | <@include model/Light.slh@> | ||
| 14 | |||
| 15 | <@include LightingModel.slh@> | ||
| 16 | <$declareLightBuffer()$> | ||
| 17 | |||
| 18 | <@include LightDirectional.slh@> | ||
| 19 | <$declareLightingDirectional(_SCRIBE_NULL)$> | ||
| 20 | |||
| 21 | in vec2 varTexCoord0; | ||
| 22 | out vec4 _fragColor; | ||
| 23 | |||
| 24 | void main(void) { | ||
| 25 | |||
| 26 | <$evalGlobeWidget()$> | ||
| 27 | |||
| 28 | Light light = getLight(); | ||
| 29 | vec3 lightDirection = normalize(getLightDirection(light)); | ||
| 30 | vec3 lightIrradiance = getLightIrradiance(light); | ||
| 31 | vec3 color = vec3(0.0); | ||
| 32 | |||
| 33 | const float INOUT_RATIO = 0.4; | ||
| 34 | const float SUN_THRESHOLD = 0.99; | ||
| 35 | |||
| 36 | vec3 outSpherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); | ||
| 37 | vec3 outNormal = vec3(getViewInverse() * vec4(outSpherePos, 0.0)); | ||
| 38 | |||
| 39 | float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); | ||
| 40 | |||
| 41 | color = lightIrradiance * vec3(val); | ||
| 42 | |||
| 43 | if (sphereR2 < INOUT_RATIO * INOUT_RATIO * SCOPE_RADIUS2) { | ||
| 44 | vec2 inSphereUV = sphereUV / INOUT_RATIO; | ||
| 45 | vec3 inSpherePos = normalize(vec3(inSphereUV, sqrt(1.0 - dot(inSphereUV.xy, inSphereUV.xy)))); | ||
| 46 | vec3 inNormal = vec3(getViewInverse() * vec4(inSpherePos, 0.0)); | ||
| 47 | |||
| 48 | vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); | ||
| 49 | color += marbleColor; | ||
| 50 | } | ||
| 51 | |||
| 52 | color = color * 1.0 - base.w + base.xyz * base.w; | ||
| 53 | const float INV_GAMMA_22 = 1.0 / 2.2; | ||
| 54 | _fragColor = vec4(pow(color, vec3(INV_GAMMA_22)), 1.0); | ||
| 55 | } | ||
| 56 | |||
| 57 | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | <@include gpu/Config.slh@> | ||
| 2 | <$VERSION_HEADER$> | ||
| 3 | // Generated on <$_SCRIBE_DATE$> | ||
| 4 | // | ||
| 5 | // Created by Sam Gateau on 5/16/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | <@include zone_draw.slh@> | ||
| 12 | |||
| 13 | uniform samplerCube skyboxMap; | ||
| 14 | |||
| 15 | struct Skybox { | ||
| 16 | vec4 color; | ||
| 17 | }; | ||
| 18 | |||
| 19 | uniform skyboxBuffer { | ||
| 20 | Skybox skybox; | ||
| 21 | }; | ||
| 22 | |||
| 23 | in vec2 varTexCoord0; | ||
| 24 | out vec4 _fragColor; | ||
| 25 | |||
| 26 | void main(void) { | ||
| 27 | <$evalGlobeWidget()$> | ||
| 28 | |||
| 29 | vec3 spherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); | ||
| 30 | |||
| 31 | vec3 direction = vec3(getViewInverse() * vec4(spherePos, 0.0)); | ||
| 32 | |||
| 33 | vec3 color = skybox.color.rgb; | ||
| 34 | |||
| 35 | // blend is only set if there is a cubemap | ||
| 36 | if (skybox.color.a > 0.0) { | ||
| 37 | color = texture(skyboxMap, direction).rgb; | ||
| 38 | if (skybox.color.a < 1.0) { | ||
| 39 | color *= skybox.color.rgb; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | color = color * 1.0 - base.w + base.xyz * base.w; | ||
| 44 | const float INV_GAMMA_22 = 1.0 / 2.2; | ||
| 45 | _fragColor = vec4(pow(color, vec3(INV_GAMMA_22)), 1.0); | ||
| 46 | } | ||
| 47 | |||
| 48 | |||
| @@ -63,6 +63,4 @@ void EngineStats::run(const RenderContextPointer& renderContext) { · 62 unmodified lines | |||
| 63 | 63 | ||
| 64 | config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines; | 64 | config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines; |
| 65 | config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges; | 65 | config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges; |
| 66 | |||
| 67 | config->emitDirty(); | ||
| 68 | } | 66 | } |
| @@ -34,6 +34,7 @@ void TaskConfig::connectChildConfig(QConfigPointer childConfig, const std::strin · 33 unmodified lines | |||
| 34 | if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { | 34 | if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { |
| 35 | // Connect dirty->refresh if defined | 35 | // Connect dirty->refresh if defined |
| 36 | QObject::connect(childConfig.get(), SIGNAL(dirty()), this, SLOT(refresh())); | 36 | QObject::connect(childConfig.get(), SIGNAL(dirty()), this, SLOT(refresh())); |
| 37 | QObject::connect(childConfig.get(), SIGNAL(dirtyEnabled()), this, SLOT(refresh())); | ||
| 37 | } | 38 | } |
| 38 | } | 39 | } |
| 39 | 40 | ||
| @@ -50,6 +51,7 @@ void TaskConfig::transferChildrenConfigs(QConfigPointer source) { · 10 unmodified lines | |||
| 50 | if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { | 51 | if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { |
| 51 | // Connect dirty->refresh if defined | 52 | // Connect dirty->refresh if defined |
| 52 | QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh())); | 53 | QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh())); |
| 54 | QObject::connect(child, SIGNAL(dirtyEnabled()), this, SLOT(refresh())); | ||
| 53 | } | 55 | } |
| 54 | } | 56 | } |
| 55 | } | 57 | } |
| @@ -89,7 +89,7 @@ protected: · 88 unmodified lines | |||
| 89 | class JobConfig : public QObject { | 89 | class JobConfig : public QObject { |
| 90 | Q_OBJECT | 90 | Q_OBJECT |
| 91 | Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms | 91 | Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms |
| 92 | Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) | 92 | Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY dirtyEnabled()) |
| 93 | 93 | ||
| 94 | double _msCPURunTime{ 0.0 }; | 94 | double _msCPURunTime{ 0.0 }; |
| 95 | public: | 95 | public: |
| @@ -99,7 +99,7 @@ public: · 3 unmodified lines | |||
| 99 | JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {} | 99 | JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {} |
| 100 | 100 | ||
| 101 | bool isEnabled() { return alwaysEnabled || enabled; } | 101 | bool isEnabled() { return alwaysEnabled || enabled; } |
| 102 | void setEnabled(bool enable) { enabled = alwaysEnabled || enable; } | 102 | void setEnabled(bool enable) { enabled = alwaysEnabled || enable; emit dirtyEnabled(); } |
| 103 | 103 | ||
| 104 | bool alwaysEnabled{ true }; | 104 | bool alwaysEnabled{ true }; |
| 105 | bool enabled{ true }; | 105 | bool enabled{ true }; |
| @@ -121,6 +121,7 @@ public slots: · 15 unmodified lines | |||
| 121 | signals: | 121 | signals: |
| 122 | void loaded(); | 122 | void loaded(); |
| 123 | void newStats(); | 123 | void newStats(); |
| 124 | void dirtyEnabled(); | ||
| 124 | }; | 125 | }; |
| 125 | 126 | ||
| 126 | class TaskConfig : public JobConfig { | 127 | class TaskConfig : public JobConfig { |
| @@ -170,6 +170,7 @@ protected: · 169 unmodified lines | |||
| 170 | std::string _name = ""; | 170 | std::string _name = ""; |
| 171 | }; | 171 | }; |
| 172 | 172 | ||
| 173 | |||
| 173 | // A task is a specialized job to run a collection of other jobs | 174 | // A task is a specialized job to run a collection of other jobs |
| 174 | // It can be created on any type T by aliasing the type JobModel in the class T | 175 | // It can be created on any type T by aliasing the type JobModel in the class T |
| 175 | // using JobModel = Task::Model<T> | 176 | // using JobModel = Task::Model<T> |
| @@ -542,7 +542,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS · 541 unmodified lines | |||
| 542 | 542 | ||
| 543 | QObject* TabletProxy::addButton(const QVariant& properties) { | 543 | QObject* TabletProxy::addButton(const QVariant& properties) { |
| 544 | auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap())); | 544 | auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap())); |
| 545 | std::lock_guard<std::mutex> guard(_tabletMutex); | 545 | std::unique_lock<std::mutex> guard(_tabletMutex); |
| 546 | _tabletButtonProxies.push_back(tabletButtonProxy); | 546 | _tabletButtonProxies.push_back(tabletButtonProxy); |
| 547 | if (!_toolbarMode && _qmlTabletRoot) { | 547 | if (!_toolbarMode && _qmlTabletRoot) { |
| 548 | auto tablet = getQmlTablet(); | 548 | auto tablet = getQmlTablet(); |
| @@ -552,7 +552,6 @@ QObject* TabletProxy::addButton(const QVariant& properties) { · 3 unmodified lines | |||
| 552 | qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; | 552 | qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; |
| 553 | } | 553 | } |
| 554 | } else if (_toolbarMode) { | 554 | } else if (_toolbarMode) { |
| 555 | |||
| 556 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); | 555 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); |
| 557 | QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); | 556 | QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); |
| 558 | 557 | ||
| @@ -561,6 +560,8 @@ QObject* TabletProxy::addButton(const QVariant& properties) { · 2 unmodified lines | |||
| 561 | connectionType = Qt::BlockingQueuedConnection; | 560 | connectionType = Qt::BlockingQueuedConnection; |
| 562 | } | 561 | } |
| 563 | 562 | ||
| 563 | guard.unlock(); | ||
| 564 | |||
| 564 | // copy properties from tablet button proxy to toolbar button proxy. | 565 | // copy properties from tablet button proxy to toolbar button proxy. |
| 565 | QObject* toolbarButtonProxy = nullptr; | 566 | QObject* toolbarButtonProxy = nullptr; |
| 566 | bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties())); | 567 | bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties())); |
| @@ -578,31 +579,38 @@ bool TabletProxy::onHomeScreen() { · 11 unmodified lines | |||
| 578 | } | 579 | } |
| 579 | 580 | ||
| 580 | void TabletProxy::removeButton(QObject* tabletButtonProxy) { | 581 | void TabletProxy::removeButton(QObject* tabletButtonProxy) { |
| 581 | std::lock_guard<std::mutex> guard(_tabletMutex); | 582 | std::unique_lock<std::mutex> guard(_tabletMutex); |
| 582 | 583 | ||
| 583 | auto tablet = getQmlTablet(); | 584 | auto tablet = getQmlTablet(); |
| 584 | if (!tablet) { | 585 | if (!tablet) { |
| 585 | qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; | 586 | qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; |
| 586 | } | 587 | } |
| 587 | 588 | ||
| 588 | auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); | 589 | QSharedPointer<TabletButtonProxy> buttonProxy; |
| 589 | if (iter != _tabletButtonProxies.end()) { | 590 | { |
| 590 | if (!_toolbarMode && _qmlTabletRoot) { | 591 | auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); |
| 591 | (*iter)->setQmlButton(nullptr); | 592 | if (iter == _tabletButtonProxies.end()) { |
| 592 | if (tablet) { | 593 | qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; |
| 593 | QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); | 594 | return; |
| 594 | } | ||
| 595 | } else if (_toolbarMode) { | ||
| 596 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); | ||
| 597 | QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); | ||
| 598 | |||
| 599 | // remove button from toolbarProxy | ||
| 600 | QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString())); | ||
| 601 | (*iter)->setToolbarButtonProxy(nullptr); | ||
| 602 | } | 595 | } |
| 596 | buttonProxy = *iter; | ||
| 603 | _tabletButtonProxies.erase(iter); | 597 | _tabletButtonProxies.erase(iter); |
| 604 | } else { | 598 | } |
| 605 | qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; | 599 | |
| 600 | if (!_toolbarMode && _qmlTabletRoot) { | ||
| 601 | buttonProxy->setQmlButton(nullptr); | ||
| 602 | if (tablet) { | ||
| 603 | guard.unlock(); | ||
| 604 | QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); | ||
| 605 | } | ||
| 606 | } else if (_toolbarMode) { | ||
| 607 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); | ||
| 608 | QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); | ||
| 609 | |||
| 610 | // remove button from toolbarProxy | ||
| 611 | guard.unlock(); | ||
| 612 | QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString())); | ||
| 613 | buttonProxy->setToolbarButtonProxy(nullptr); | ||
| 606 | } | 614 | } |
| 607 | } | 615 | } |
| 608 | 616 | ||
| @@ -0,0 +1,113 @@ | |||
| 1 | // | ||
| 2 | // CubicHermiteSpline.h | ||
| 3 | // | ||
| 4 | // Copyright 2017 High Fidelity, Inc. | ||
| 5 | // | ||
| 6 | // Distributed under the Apache License, Version 2.0. | ||
| 7 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef hifi_CubicHermiteSpline_h | ||
| 11 | #define hifi_CubicHermiteSpline_h | ||
| 12 | |||
| 13 | #include "GLMHelpers.h" | ||
| 14 | |||
| 15 | class CubicHermiteSplineFunctor { | ||
| 16 | public: | ||
| 17 | CubicHermiteSplineFunctor() : _p0(), _m0(), _p1(), _m1() {} | ||
| 18 | CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {} | ||
| 19 | |||
| 20 | CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {} | ||
| 21 | |||
| 22 | virtual ~CubicHermiteSplineFunctor() {} | ||
| 23 | |||
| 24 | // evalute the hermite curve at parameter t (0..1) | ||
| 25 | glm::vec3 operator()(float t) const { | ||
| 26 | float t2 = t * t; | ||
| 27 | float t3 = t2 * t; | ||
| 28 | float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f; | ||
| 29 | float w1 = t3 - 2.0f * t2 + t; | ||
| 30 | float w2 = -2.0f * t3 + 3.0f * t2; | ||
| 31 | float w3 = t3 - t2; | ||
| 32 | return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; | ||
| 33 | } | ||
| 34 | |||
| 35 | // evaulate the first derivative of the hermite curve at parameter t (0..1) | ||
| 36 | glm::vec3 d(float t) const { | ||
| 37 | float t2 = t * t; | ||
| 38 | float w0 = -6.0f * t + 6.0f * t2; | ||
| 39 | float w1 = 1.0f - 4.0f * t + 3.0f * t2; | ||
| 40 | float w2 = 6.0f * t - 6.0f * t2; | ||
| 41 | float w3 = -2.0f * t + 3.0f * t2; | ||
| 42 | return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; | ||
| 43 | } | ||
| 44 | |||
| 45 | // evaulate the second derivative of the hermite curve at paramter t (0..1) | ||
| 46 | glm::vec3 d2(float t) const { | ||
| 47 | float w0 = -6.0f + 12.0f * t; | ||
| 48 | float w1 = -4.0f + 6.0f * t; | ||
| 49 | float w2 = 6.0f - 12.0f * t; | ||
| 50 | float w3 = -2.0f + 6.0f * t; | ||
| 51 | return w0 + _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; | ||
| 52 | } | ||
| 53 | |||
| 54 | protected: | ||
| 55 | glm::vec3 _p0; | ||
| 56 | glm::vec3 _m0; | ||
| 57 | glm::vec3 _p1; | ||
| 58 | glm::vec3 _m1; | ||
| 59 | }; | ||
| 60 | |||
| 61 | class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor { | ||
| 62 | public: | ||
| 63 | enum Constants { NUM_SUBDIVISIONS = 30 }; | ||
| 64 | |||
| 65 | CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() { | ||
| 66 | memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1)); | ||
| 67 | } | ||
| 68 | CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) { | ||
| 69 | // initialize _values with the accumulated arcLength along the spline. | ||
| 70 | const float DELTA = 1.0f / NUM_SUBDIVISIONS; | ||
| 71 | float alpha = 0.0f; | ||
| 72 | float accum = 0.0f; | ||
| 73 | _values[0] = 0.0f; | ||
| 74 | for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { | ||
| 75 | accum += glm::distance(this->operator()(alpha), | ||
| 76 | this->operator()(alpha + DELTA)); | ||
| 77 | alpha += DELTA; | ||
| 78 | _values[i] = accum; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) { | ||
| 83 | memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1)); | ||
| 84 | } | ||
| 85 | |||
| 86 | // given the spline parameter (0..1) output the arcLength of the spline up to that point. | ||
| 87 | float arcLength(float t) const { | ||
| 88 | float index = t * NUM_SUBDIVISIONS; | ||
| 89 | int prevIndex = std::min(std::max(0, (int)glm::floor(index)), (int)NUM_SUBDIVISIONS); | ||
| 90 | int nextIndex = std::min(std::max(0, (int)glm::ceil(index)), (int)NUM_SUBDIVISIONS); | ||
| 91 | float alpha = glm::fract(index); | ||
| 92 | return lerp(_values[prevIndex], _values[nextIndex], alpha); | ||
| 93 | } | ||
| 94 | |||
| 95 | // given an arcLength compute the spline parameter (0..1) that cooresponds to that arcLength. | ||
| 96 | float arcLengthInverse(float s) const { | ||
| 97 | // find first item in _values that is > s. | ||
| 98 | int nextIndex; | ||
| 99 | for (nextIndex = 0; nextIndex < NUM_SUBDIVISIONS; nextIndex++) { | ||
| 100 | if (_values[nextIndex] > s) { | ||
| 101 | break; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | int prevIndex = std::min(std::max(0, nextIndex - 1), (int)NUM_SUBDIVISIONS); | ||
| 105 | float alpha = glm::clamp((s - _values[prevIndex]) / (_values[nextIndex] - _values[prevIndex]), 0.0f, 1.0f); | ||
| 106 | const float DELTA = 1.0f / NUM_SUBDIVISIONS; | ||
| 107 | return lerp(prevIndex * DELTA, nextIndex * DELTA, alpha); | ||
| 108 | } | ||
| 109 | protected: | ||
| 110 | float _values[NUM_SUBDIVISIONS + 1]; | ||
| 111 | }; | ||
| 112 | |||
| 113 | #endif // hifi_CubicHermiteSpline_h | ||
| @@ -69,7 +69,7 @@ public: · 68 unmodified lines | |||
| 69 | glm::vec3 size() const { return maximum - minimum; } | 69 | glm::vec3 size() const { return maximum - minimum; } |
| 70 | float largestDimension() const { return glm::compMax(size()); } | 70 | float largestDimension() const { return glm::compMax(size()); } |
| 71 | 71 | ||
| 72 | /// \return new Extents which is original rotated around orign by rotation | 72 | /// \return new Extents which is original rotated around origin by rotation |
| 73 | Extents getRotated(const glm::quat& rotation) const { | 73 | Extents getRotated(const glm::quat& rotation) const { |
| 74 | Extents temp(minimum, maximum); | 74 | Extents temp(minimum, maximum); |
| 75 | temp.rotate(rotation); | 75 | temp.rotate(rotation); |
| @@ -49,7 +49,7 @@ const mat4 Matrices::Z_180 { createMatFromQuatAndPos(Quaternions::Z_180, Vectors · 48 unmodified lines | |||
| 49 | glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { | 49 | glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { |
| 50 | float cosa = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; | 50 | float cosa = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; |
| 51 | float ox = q2.x, oy = q2.y, oz = q2.z, ow = q2.w, s0, s1; | 51 | float ox = q2.x, oy = q2.y, oz = q2.z, ow = q2.w, s0, s1; |
| 52 | 52 | ||
| 53 | // adjust signs if necessary | 53 | // adjust signs if necessary |
| 54 | if (cosa < 0.0f) { | 54 | if (cosa < 0.0f) { |
| 55 | cosa = -cosa; | 55 | cosa = -cosa; |
| @@ -58,19 +58,19 @@ glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { · 2 unmodified lines | |||
| 58 | oz = -oz; | 58 | oz = -oz; |
| 59 | ow = -ow; | 59 | ow = -ow; |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | // calculate coefficients; if the angle is too close to zero, we must fall back | 62 | // calculate coefficients; if the angle is too close to zero, we must fall back |
| 63 | // to linear interpolation | 63 | // to linear interpolation |
| 64 | if ((1.0f - cosa) > EPSILON) { | 64 | if ((1.0f - cosa) > EPSILON) { |
| 65 | float angle = acosf(cosa), sina = sinf(angle); | 65 | float angle = acosf(cosa), sina = sinf(angle); |
| 66 | s0 = sinf((1.0f - proportion) * angle) / sina; | 66 | s0 = sinf((1.0f - proportion) * angle) / sina; |
| 67 | s1 = sinf(proportion * angle) / sina; | 67 | s1 = sinf(proportion * angle) / sina; |
| 68 | 68 | ||
| 69 | } else { | 69 | } else { |
| 70 | s0 = 1.0f - proportion; | 70 | s0 = 1.0f - proportion; |
| 71 | s1 = proportion; | 71 | s1 = proportion; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | return glm::normalize(glm::quat(s0 * q1.w + s1 * ow, s0 * q1.x + s1 * ox, s0 * q1.y + s1 * oy, s0 * q1.z + s1 * oz)); | 74 | return glm::normalize(glm::quat(s0 * q1.w + s1 * ow, s0 * q1.x + s1 * ox, s0 * q1.y + s1 * oy, s0 * q1.z + s1 * oz)); |
| 75 | } | 75 | } |
| 76 | 76 | ||
| @@ -105,10 +105,10 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm · 28 unmodified lines | |||
| 105 | 105 | ||
| 106 | int packFloatAngleToTwoByte(unsigned char* buffer, float degrees) { | 106 | int packFloatAngleToTwoByte(unsigned char* buffer, float degrees) { |
| 107 | const float ANGLE_CONVERSION_RATIO = (std::numeric_limits<uint16_t>::max() / 360.0f); | 107 | const float ANGLE_CONVERSION_RATIO = (std::numeric_limits<uint16_t>::max() / 360.0f); |
| 108 | 108 | ||
| 109 | uint16_t angleHolder = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO); | 109 | uint16_t angleHolder = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO); |
| 110 | memcpy(buffer, &angleHolder, sizeof(uint16_t)); | 110 | memcpy(buffer, &angleHolder, sizeof(uint16_t)); |
| 111 | 111 | ||
| 112 | return sizeof(uint16_t); | 112 | return sizeof(uint16_t); |
| 113 | } | 113 | } |
| 114 | 114 | ||
| @@ -125,7 +125,7 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput · 10 unmodified lines | |||
| 125 | quatParts[1] = floorf((quatNormalized.y + 1.0f) * QUAT_PART_CONVERSION_RATIO); | 125 | quatParts[1] = floorf((quatNormalized.y + 1.0f) * QUAT_PART_CONVERSION_RATIO); |
| 126 | quatParts[2] = floorf((quatNormalized.z + 1.0f) * QUAT_PART_CONVERSION_RATIO); | 126 | quatParts[2] = floorf((quatNormalized.z + 1.0f) * QUAT_PART_CONVERSION_RATIO); |
| 127 | quatParts[3] = floorf((quatNormalized.w + 1.0f) * QUAT_PART_CONVERSION_RATIO); | 127 | quatParts[3] = floorf((quatNormalized.w + 1.0f) * QUAT_PART_CONVERSION_RATIO); |
| 128 | 128 | ||
| 129 | memcpy(buffer, &quatParts, sizeof(quatParts)); | 129 | memcpy(buffer, &quatParts, sizeof(quatParts)); |
| 130 | return sizeof(quatParts); | 130 | return sizeof(quatParts); |
| 131 | } | 131 | } |
| @@ -133,12 +133,12 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput · 1 unmodified line | |||
| 133 | int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput) { | 133 | int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput) { |
| 134 | uint16_t quatParts[4]; | 134 | uint16_t quatParts[4]; |
| 135 | memcpy(&quatParts, buffer, sizeof(quatParts)); | 135 | memcpy(&quatParts, buffer, sizeof(quatParts)); |
| 136 | 136 | ||
| 137 | quatOutput.x = ((quatParts[0] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; | 137 | quatOutput.x = ((quatParts[0] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; |
| 138 | quatOutput.y = ((quatParts[1] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; | 138 | quatOutput.y = ((quatParts[1] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; |
| 139 | quatOutput.z = ((quatParts[2] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; | 139 | quatOutput.z = ((quatParts[2] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; |
| 140 | quatOutput.w = ((quatParts[3] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; | 140 | quatOutput.w = ((quatParts[3] / (float) std::numeric_limits<uint16_t>::max()) * 2.0f) - 1.0f; |
| 141 | 141 | ||
| 142 | return sizeof(quatParts); | 142 | return sizeof(quatParts); |
| 143 | } | 143 | } |
| 144 | 144 | ||
| @@ -235,7 +235,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { · 90 unmodified lines | |||
| 235 | atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)), | 235 | atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)), |
| 236 | asinf(sy), | 236 | asinf(sy), |
| 237 | atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z))); | 237 | atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z))); |
| 238 | 238 | ||
| 239 | } else { | 239 | } else { |
| 240 | // not a unique solution; x + z = atan2(-m21, m11) | 240 | // not a unique solution; x + z = atan2(-m21, m11) |
| 241 | eulers = glm::vec3( | 241 | eulers = glm::vec3( |
| @@ -250,7 +250,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { · 8 unmodified lines | |||
| 250 | PI_OVER_TWO, | 250 | PI_OVER_TWO, |
| 251 | -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))); | 251 | -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))); |
| 252 | } | 252 | } |
| 253 | 253 | ||
| 254 | // adjust so that z, rather than y, is in [-pi/2, pi/2] | 254 | // adjust so that z, rather than y, is in [-pi/2, pi/2] |
| 255 | if (eulers.z < -PI_OVER_TWO) { | 255 | if (eulers.z < -PI_OVER_TWO) { |
| 256 | if (eulers.x < 0.0f) { | 256 | if (eulers.x < 0.0f) { |
| @@ -265,7 +265,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { · 8 unmodified lines | |||
| 265 | eulers.y -= PI; | 265 | eulers.y -= PI; |
| 266 | } | 266 | } |
| 267 | eulers.z += PI; | 267 | eulers.z += PI; |
| 268 | 268 | ||
| 269 | } else if (eulers.z > PI_OVER_TWO) { | 269 | } else if (eulers.z > PI_OVER_TWO) { |
| 270 | if (eulers.x < 0.0f) { | 270 | if (eulers.x < 0.0f) { |
| 271 | eulers.x += PI; | 271 | eulers.x += PI; |
| @@ -320,7 +320,7 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { · 48 unmodified lines | |||
| 320 | for (int i = 0; i < 10; i++) { | 320 | for (int i = 0; i < 10; i++) { |
| 321 | // store the results of the previous iteration | 321 | // store the results of the previous iteration |
| 322 | glm::mat3 previous = upper; | 322 | glm::mat3 previous = upper; |
| 323 | 323 | ||
| 324 | // compute average of the matrix with its inverse transpose | 324 | // compute average of the matrix with its inverse transpose |
| 325 | float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; | 325 | float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; |
| 326 | float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; | 326 | float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; |
| @@ -334,15 +334,15 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { · 7 unmodified lines | |||
| 334 | upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; | 334 | upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; |
| 335 | upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; | 335 | upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; |
| 336 | upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; | 336 | upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; |
| 337 | 337 | ||
| 338 | upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; | 338 | upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; |
| 339 | upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; | 339 | upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; |
| 340 | upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; | 340 | upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; |
| 341 | 341 | ||
| 342 | upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; | 342 | upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; |
| 343 | upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; | 343 | upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; |
| 344 | upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; | 344 | upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; |
| 345 | 345 | ||
| 346 | // compute the difference; if it's small enough, we're done | 346 | // compute the difference; if it's small enough, we're done |
| 347 | glm::mat3 diff = upper - previous; | 347 | glm::mat3 diff = upper - previous; |
| 348 | if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + | 348 | if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + |
| @@ -352,7 +352,7 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { · 3 unmodified lines | |||
| 352 | } | 352 | } |
| 353 | } | 353 | } |
| 354 | } | 354 | } |
| 355 | 355 | ||
| 356 | // now that we have a nice orthogonal matrix, we can extract the rotation quaternion | 356 | // now that we have a nice orthogonal matrix, we can extract the rotation quaternion |
| 357 | // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions | 357 | // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions |
| 358 | float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); | 358 | float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); |
| @@ -473,7 +473,7 @@ glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& · 114 unmodified lines | |||
| 473 | glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); | 473 | glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); |
| 474 | } | 474 | } |
| 475 | 475 | ||
| 476 | // cancel out roll | 476 | // cancel out roll |
| 477 | glm::quat cancelOutRoll(const glm::quat& q) { | 477 | glm::quat cancelOutRoll(const glm::quat& q) { |
| 478 | glm::vec3 forward = q * Vectors::FRONT; | 478 | glm::vec3 forward = q * Vectors::FRONT; |
| 479 | return glm::quat_cast(glm::inverse(glm::lookAt(Vectors::ZERO, forward, Vectors::UP))); | 479 | return glm::quat_cast(glm::inverse(glm::lookAt(Vectors::ZERO, forward, Vectors::UP))); |
| @@ -538,17 +538,16 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda · 58 unmodified lines | |||
| 538 | uAxisOut = glm::normalize(primaryAxis); | 538 | uAxisOut = glm::normalize(primaryAxis); |
| 539 | glm::vec3 normSecondary = glm::normalize(secondaryAxis); | 539 | glm::vec3 normSecondary = glm::normalize(secondaryAxis); |
| 540 | 540 | ||
| 541 | // if secondaryAxis is parallel with the primaryAxis, pick another axis. | 541 | // if normSecondary is parallel with the primaryAxis, pick another secondary. |
| 542 | const float EPSILON = 1.0e-4f; | 542 | const float EPSILON = 1.0e-4f; |
| 543 | if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { | 543 | if (fabsf(fabsf(glm::dot(uAxisOut, normSecondary)) - 1.0f) < EPSILON) { |
| 544 | // pick a better secondaryAxis. | 544 | normSecondary = Vectors::UNIT_X; |
| 545 | normSecondary = glm::vec3(1.0f, 0.0f, 0.0f); | 545 | if (fabsf(fabsf(glm::dot(uAxisOut, normSecondary)) - 1.0f) < EPSILON) { |
| 546 | if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { | 546 | normSecondary = Vectors::UNIT_Y; |
| 547 | normSecondary = glm::vec3(0.0f, 1.0f, 0.0f); | ||
| 548 | } | 547 | } |
| 549 | } | 548 | } |
| 550 | 549 | ||
| 551 | wAxisOut = glm::normalize(glm::cross(uAxisOut, secondaryAxis)); | 550 | wAxisOut = glm::normalize(glm::cross(uAxisOut, normSecondary)); |
| 552 | vAxisOut = glm::cross(wAxisOut, uAxisOut); | 551 | vAxisOut = glm::cross(wAxisOut, uAxisOut); |
| 553 | } | 552 | } |
| 554 | 553 | ||
| @@ -290,12 +290,12 @@ glm::vec3 Triangle::getNormal() const { · 289 unmodified lines | |||
| 290 | } | 290 | } |
| 291 | 291 | ||
| 292 | bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, | 292 | bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 293 | const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) { | 293 | const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { |
| 294 | glm::vec3 firstSide = v0 - v1; | 294 | glm::vec3 firstSide = v0 - v1; |
| 295 | glm::vec3 secondSide = v2 - v1; | 295 | glm::vec3 secondSide = v2 - v1; |
| 296 | glm::vec3 normal = glm::cross(secondSide, firstSide); | 296 | glm::vec3 normal = glm::cross(secondSide, firstSide); |
| 297 | float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); | 297 | float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); |
| 298 | if (dividend > 0.0f) { | 298 | if (!allowBackface && dividend > 0.0f) { |
| 299 | return false; // origin below plane | 299 | return false; // origin below plane |
| 300 | } | 300 | } |
| 301 | float divisor = glm::dot(normal, direction); | 301 | float divisor = glm::dot(normal, direction); |
| @@ -83,7 +83,7 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire · 82 unmodified lines | |||
| 83 | const glm::vec3& position, const glm::vec2& dimensions, float& distance); | 83 | const glm::vec3& position, const glm::vec2& dimensions, float& distance); |
| 84 | 84 | ||
| 85 | bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, | 85 | bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 86 | const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance); | 86 | const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); |
| 87 | 87 | ||
| 88 | /// \brief decomposes rotation into its components such that: rotation = swing * twist | 88 | /// \brief decomposes rotation into its components such that: rotation = swing * twist |
| 89 | /// \param rotation[in] rotation to decompose | 89 | /// \param rotation[in] rotation to decompose |
| @@ -104,8 +104,8 @@ public: · 14 unmodified lines | |||
| 104 | }; | 104 | }; |
| 105 | 105 | ||
| 106 | inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, | 106 | inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 107 | const Triangle& triangle, float& distance) { | 107 | const Triangle& triangle, float& distance, bool allowBackface = false) { |
| 108 | return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance); | 108 | return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface); |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | 111 | ||
| @@ -50,10 +50,13 @@ const int KILO_PER_MEGA = 1000; · 49 unmodified lines | |||
| 50 | 50 | ||
| 51 | #define KB_TO_BYTES_SHIFT 10 | 51 | #define KB_TO_BYTES_SHIFT 10 |
| 52 | #define MB_TO_BYTES_SHIFT 20 | 52 | #define MB_TO_BYTES_SHIFT 20 |
| 53 | #define GB_TO_BYTES_SHIFT 30 | ||
| 53 | 54 | ||
| 55 | #define GB_TO_BYTES(X) ((size_t)(X) << GB_TO_BYTES_SHIFT) | ||
| 54 | #define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT) | 56 | #define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT) |
| 55 | #define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT) | 57 | #define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT) |
| 56 | 58 | ||
| 59 | #define BYTES_TO_GB(X) (X >> GB_TO_BYTES_SHIFT) | ||
| 57 | #define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT) | 60 | #define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT) |
| 58 | #define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT) | 61 | #define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT) |
| 59 | 62 | ||
| @@ -76,7 +76,7 @@ public: · 75 unmodified lines | |||
| 76 | class RenderArgs { | 76 | class RenderArgs { |
| 77 | public: | 77 | public: |
| 78 | enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE }; | 78 | enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE }; |
| 79 | enum RenderSide { MONO, STEREO_LEFT, STEREO_RIGHT }; | 79 | enum DisplayMode { MONO, STEREO_MONITOR, STEREO_HMD }; |
| 80 | enum DebugFlags { | 80 | enum DebugFlags { |
| 81 | RENDER_DEBUG_NONE = 0, | 81 | RENDER_DEBUG_NONE = 0, |
| 82 | RENDER_DEBUG_HULLS = 1 | 82 | RENDER_DEBUG_HULLS = 1 |
| @@ -87,7 +87,7 @@ public: · 4 unmodified lines | |||
| 87 | float sizeScale = 1.0f, | 87 | float sizeScale = 1.0f, |
| 88 | int boundaryLevelAdjust = 0, | 88 | int boundaryLevelAdjust = 0, |
| 89 | RenderMode renderMode = DEFAULT_RENDER_MODE, | 89 | RenderMode renderMode = DEFAULT_RENDER_MODE, |
| 90 | RenderSide renderSide = MONO, | 90 | DisplayMode displayMode = MONO, |
| 91 | DebugFlags debugFlags = RENDER_DEBUG_NONE, | 91 | DebugFlags debugFlags = RENDER_DEBUG_NONE, |
| 92 | gpu::Batch* batch = nullptr) : | 92 | gpu::Batch* batch = nullptr) : |
| 93 | _context(context), | 93 | _context(context), |
| @@ -95,7 +95,7 @@ public: · 1 unmodified line | |||
| 95 | _sizeScale(sizeScale), | 95 | _sizeScale(sizeScale), |
| 96 | _boundaryLevelAdjust(boundaryLevelAdjust), | 96 | _boundaryLevelAdjust(boundaryLevelAdjust), |
| 97 | _renderMode(renderMode), | 97 | _renderMode(renderMode), |
| 98 | _renderSide(renderSide), | 98 | _displayMode(displayMode), |
| 99 | _debugFlags(debugFlags), | 99 | _debugFlags(debugFlags), |
| 100 | _batch(batch) { | 100 | _batch(batch) { |
| 101 | } | 101 | } |
| @@ -121,7 +121,7 @@ public: · 19 unmodified lines | |||
| 121 | float _sizeScale = 1.0f; | 121 | float _sizeScale = 1.0f; |
| 122 | int _boundaryLevelAdjust = 0; | 122 | int _boundaryLevelAdjust = 0; |
| 123 | RenderMode _renderMode = DEFAULT_RENDER_MODE; | 123 | RenderMode _renderMode = DEFAULT_RENDER_MODE; |
| 124 | RenderSide _renderSide = MONO; | 124 | DisplayMode _displayMode = MONO; |
| 125 | DebugFlags _debugFlags = RENDER_DEBUG_NONE; | 125 | DebugFlags _debugFlags = RENDER_DEBUG_NONE; |
| 126 | gpu::Batch* _batch = nullptr; | 126 | gpu::Batch* _batch = nullptr; |
| 127 | 127 | ||
| @@ -13,44 +13,16 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | #include <QFile> | 14 | #include <QFile> |
| 15 | #include <QStandardPaths> | 15 | #include <QStandardPaths> |
| 16 | #include <QThread> | ||
| 17 | #include <QTimer> | ||
| 18 | 16 | ||
| 19 | #include "NumericalConstants.h" | ||
| 20 | #include "PathUtils.h" | 17 | #include "PathUtils.h" |
| 21 | 18 | ||
| 22 | RunningMarker::RunningMarker(QObject* parent, QString name) : | 19 | RunningMarker::RunningMarker(QString name) : |
| 23 | _parent(parent), | ||
| 24 | _name(name) | 20 | _name(name) |
| 25 | { | 21 | { |
| 26 | } | 22 | } |
| 27 | 23 | ||
| 28 | void RunningMarker::startRunningMarker() { | ||
| 29 | static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND; | ||
| 30 | |||
| 31 | // start the nodeThread so its event loop is running | ||
| 32 | _runningMarkerThread = new QThread(_parent); | ||
| 33 | _runningMarkerThread->setObjectName("Running Marker Thread"); | ||
| 34 | _runningMarkerThread->start(); | ||
| 35 | |||
| 36 | writeRunningMarkerFile(); // write the first file, even before timer | ||
| 37 | |||
| 38 | _runningMarkerTimer = new QTimer(); | ||
| 39 | QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){ | ||
| 40 | writeRunningMarkerFile(); | ||
| 41 | }); | ||
| 42 | _runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS); | ||
| 43 | |||
| 44 | // put the time on the thread | ||
| 45 | _runningMarkerTimer->moveToThread(_runningMarkerThread); | ||
| 46 | } | ||
| 47 | |||
| 48 | RunningMarker::~RunningMarker() { | 24 | RunningMarker::~RunningMarker() { |
| 49 | deleteRunningMarkerFile(); | 25 | deleteRunningMarkerFile(); |
| 50 | QMetaObject::invokeMethod(_runningMarkerTimer, "stop", Qt::BlockingQueuedConnection); | ||
| 51 | _runningMarkerThread->quit(); | ||
| 52 | _runningMarkerTimer->deleteLater(); | ||
| 53 | _runningMarkerThread->deleteLater(); | ||
| 54 | } | 26 | } |
| 55 | 27 | ||
| 56 | bool RunningMarker::fileExists() const { | 28 | bool RunningMarker::fileExists() const { |
| @@ -77,8 +49,3 @@ void RunningMarker::deleteRunningMarkerFile() { · 20 unmodified lines | |||
| 77 | QString RunningMarker::getFilePath() const { | 49 | QString RunningMarker::getFilePath() const { |
| 78 | return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; | 50 | return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; |
| 79 | } | 51 | } |
| 80 | |||
| 81 | QString RunningMarker::getMarkerFilePath(QString name) { | ||
| 82 | return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name; | ||
| 83 | } | ||
| 84 | |||
| @@ -12,21 +12,14 @@ · 11 unmodified lines | |||
| 12 | #ifndef hifi_RunningMarker_h | 12 | #ifndef hifi_RunningMarker_h |
| 13 | #define hifi_RunningMarker_h | 13 | #define hifi_RunningMarker_h |
| 14 | 14 | ||
| 15 | #include <QObject> | ||
| 16 | #include <QString> | 15 | #include <QString> |
| 17 | 16 | ||
| 18 | class QThread; | ||
| 19 | class QTimer; | ||
| 20 | |||
| 21 | class RunningMarker { | 17 | class RunningMarker { |
| 22 | public: | 18 | public: |
| 23 | RunningMarker(QObject* parent, QString name); | 19 | RunningMarker(QString name); |
| 24 | ~RunningMarker(); | 20 | ~RunningMarker(); |
| 25 | 21 | ||
| 26 | void startRunningMarker(); | ||
| 27 | |||
| 28 | QString getFilePath() const; | 22 | QString getFilePath() const; |
| 29 | static QString getMarkerFilePath(QString name); | ||
| 30 | 23 | ||
| 31 | bool fileExists() const; | 24 | bool fileExists() const; |
| 32 | 25 | ||
| @@ -34,10 +27,7 @@ public: · 1 unmodified line | |||
| 34 | void deleteRunningMarkerFile(); | 27 | void deleteRunningMarkerFile(); |
| 35 | 28 | ||
| 36 | private: | 29 | private: |
| 37 | QObject* _parent { nullptr }; | ||
| 38 | QString _name; | 30 | QString _name; |
| 39 | QThread* _runningMarkerThread { nullptr }; | ||
| 40 | QTimer* _runningMarkerTimer { nullptr }; | ||
| 41 | }; | 31 | }; |
| 42 | 32 | ||
| 43 | #endif // hifi_RunningMarker_h | 33 | #endif // hifi_RunningMarker_h |
| @@ -0,0 +1,28 @@ | |||
| 1 | // | ||
| 2 | // Created by Bradley Austin Davis on 2017/06/06 | ||
| 3 | // Copyright 2013-2017 High Fidelity, Inc. | ||
| 4 | // | ||
| 5 | // Distributed under the Apache License, Version 2.0. | ||
| 6 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 7 | // | ||
| 8 | |||
| 9 | #pragma once | ||
| 10 | #ifndef hifi_TBBHelpers_h | ||
| 11 | #define hifi_TBBHelpers_h | ||
| 12 | |||
| 13 | #ifdef _WIN32 | ||
| 14 | #pragma warning( push ) | ||
| 15 | #pragma warning( disable : 4334 ) | ||
| 16 | #endif | ||
| 17 | |||
| 18 | #include <tbb/concurrent_queue.h> | ||
| 19 | #include <tbb/concurrent_unordered_map.h> | ||
| 20 | #include <tbb/concurrent_unordered_set.h> | ||
| 21 | #include <tbb/concurrent_vector.h> | ||
| 22 | #include <tbb/parallel_for.h> | ||
| 23 | |||
| 24 | #ifdef _WIN32 | ||
| 25 | #pragma warning( pop ) | ||
| 26 | #endif | ||
| 27 | |||
| 28 | #endif // hifi_TBBHelpers_h | ||
| @@ -0,0 +1,39 @@ | |||
| 1 | // | ||
| 2 | // Created by Bradley Austin Davis on 2017/06/06 | ||
| 3 | // Copyright 2013-2017 High Fidelity, Inc. | ||
| 4 | // | ||
| 5 | // Distributed under the Apache License, Version 2.0. | ||
| 6 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 7 | // | ||
| 8 | |||
| 9 | #include "ThreadHelpers.h" | ||
| 10 | |||
| 11 | #include <QtCore/QDebug> | ||
| 12 | |||
| 13 | |||
| 14 | void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority) { | ||
| 15 | Q_ASSERT(QThread::currentThread() == object->thread()); | ||
| 16 | // setup a thread for the NodeList and its PacketReceiver | ||
| 17 | QThread* thread = new QThread(); | ||
| 18 | thread->setObjectName(name); | ||
| 19 | |||
| 20 | if (priority != QThread::InheritPriority) { | ||
| 21 | thread->setPriority(priority); | ||
| 22 | } | ||
| 23 | |||
| 24 | QString tempName = name; | ||
| 25 | QObject::connect(thread, &QThread::started, [startCallback] { | ||
| 26 | startCallback(); | ||
| 27 | }); | ||
| 28 | // Make sure the thread will be destroyed and cleaned up | ||
| 29 | QObject::connect(object, &QObject::destroyed, thread, &QThread::quit); | ||
| 30 | QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); | ||
| 31 | |||
| 32 | // put the object on the thread | ||
| 33 | object->moveToThread(thread); | ||
| 34 | thread->start(); | ||
| 35 | } | ||
| 36 | |||
| 37 | void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority) { | ||
| 38 | moveToNewNamedThread(object, name, [] {}, priority); | ||
| 39 | } | ||
| @@ -12,8 +12,13 @@ · 11 unmodified lines | |||
| 12 | #define hifi_ThreadHelpers_h | 12 | #define hifi_ThreadHelpers_h |
| 13 | 13 | ||
| 14 | #include <exception> | 14 | #include <exception> |
| 15 | #include <QMutex> | 15 | #include <functional> |
| 16 | #include <QMutexLocker> | 16 | |
| 17 | #include <QtCore/QMutex> | ||
| 18 | #include <QtCore/QMutexLocker> | ||
| 19 | #include <QtCore/QObject> | ||
| 20 | #include <QtCore/QString> | ||
| 21 | #include <QtCore/QThread> | ||
| 17 | 22 | ||
| 18 | template <typename L, typename F> | 23 | template <typename L, typename F> |
| 19 | void withLock(L lock, F function) { | 24 | void withLock(L lock, F function) { |
| @@ -26,4 +31,7 @@ void withLock(QMutex& lock, F function) { · 6 unmodified lines | |||
| 26 | function(); | 31 | function(); |
| 27 | } | 32 | } |
| 28 | 33 | ||
| 34 | void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority = QThread::InheritPriority); | ||
| 35 | void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority = QThread::InheritPriority); | ||
| 36 | |||
| 29 | #endif | 37 | #endif |
| @@ -31,7 +31,7 @@ void TriangleSet::clear() { · 30 unmodified lines | |||
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, | 33 | bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 34 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { | 34 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) { |
| 35 | 35 | ||
| 36 | // reset our distance to be the max possible, lower level tests will store best distance here | 36 | // reset our distance to be the max possible, lower level tests will store best distance here |
| 37 | distance = std::numeric_limits<float>::max(); | 37 | distance = std::numeric_limits<float>::max(); |
| @@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& · 3 unmodified lines | |||
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | int trianglesTouched = 0; | 43 | int trianglesTouched = 0; |
| 44 | auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); | 44 | auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface); |
| 45 | 45 | ||
| 46 | #if WANT_DEBUGGING | 46 | #if WANT_DEBUGGING |
| 47 | if (precision) { | 47 | if (precision) { |
| @@ -95,7 +95,7 @@ void TriangleSet::balanceOctree() { · 47 unmodified lines | |||
| 95 | // Determine of the given ray (origin/direction) in model space intersects with any triangles | 95 | // Determine of the given ray (origin/direction) in model space intersects with any triangles |
| 96 | // in the set. If an intersection occurs, the distance and surface normal will be provided. | 96 | // in the set. If an intersection occurs, the distance and surface normal will be provided. |
| 97 | bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, | 97 | bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, |
| 98 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { | 98 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { |
| 99 | 99 | ||
| 100 | bool intersectedSomething = false; | 100 | bool intersectedSomething = false; |
| 101 | float boxDistance = distance; | 101 | float boxDistance = distance; |
| @@ -114,7 +114,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec · 12 unmodified lines | |||
| 114 | const auto& triangle = _allTriangles[triangleIndex]; | 114 | const auto& triangle = _allTriangles[triangleIndex]; |
| 115 | float thisTriangleDistance; | 115 | float thisTriangleDistance; |
| 116 | trianglesTouched++; | 116 | trianglesTouched++; |
| 117 | if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { | 117 | if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) { |
| 118 | if (thisTriangleDistance < bestDistance) { | 118 | if (thisTriangleDistance < bestDistance) { |
| 119 | bestDistance = thisTriangleDistance; | 119 | bestDistance = thisTriangleDistance; |
| 120 | intersectedSomething = true; | 120 | intersectedSomething = true; |
| @@ -203,7 +203,7 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { · 82 unmodified lines | |||
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, | 205 | bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 206 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { | 206 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { |
| 207 | 207 | ||
| 208 | if (_population < 1) { | 208 | if (_population < 1) { |
| 209 | return false; // no triangles below here, so we can't intersect | 209 | return false; // no triangles below here, so we can't intersect |
| @@ -247,7 +247,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi · 37 unmodified lines | |||
| 247 | } | 247 | } |
| 248 | } | 248 | } |
| 249 | // also check our local triangle set | 249 | // also check our local triangle set |
| 250 | if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { | 250 | if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) { |
| 251 | if (childDistance < bestLocalDistance) { | 251 | if (childDistance < bestLocalDistance) { |
| 252 | bestLocalDistance = childDistance; | 252 | bestLocalDistance = childDistance; |
| 253 | bestLocalFace = childFace; | 253 | bestLocalFace = childFace; |
| @@ -27,7 +27,7 @@ class TriangleSet { · 26 unmodified lines | |||
| 27 | void clear(); | 27 | void clear(); |
| 28 | 28 | ||
| 29 | bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, | 29 | bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 30 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); | 30 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); |
| 31 | 31 | ||
| 32 | const AABox& getBounds() const { return _bounds; } | 32 | const AABox& getBounds() const { return _bounds; } |
| 33 | 33 | ||
| @@ -38,7 +38,7 @@ class TriangleSet { · 4 unmodified lines | |||
| 38 | 38 | ||
| 39 | // checks our internal list of triangles | 39 | // checks our internal list of triangles |
| 40 | bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, | 40 | bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, |
| 41 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); | 41 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); |
| 42 | 42 | ||
| 43 | std::vector<Triangle>& _allTriangles; | 43 | std::vector<Triangle>& _allTriangles; |
| 44 | std::map<AABox::OctreeChild, TriangleOctreeCell> _children; | 44 | std::map<AABox::OctreeChild, TriangleOctreeCell> _children; |
| @@ -60,7 +60,7 @@ public: · 15 unmodified lines | |||
| 60 | void insert(const Triangle& t); | 60 | void insert(const Triangle& t); |
| 61 | 61 | ||
| 62 | bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, | 62 | bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, |
| 63 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision); | 63 | float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false); |
| 64 | 64 | ||
| 65 | void balanceOctree(); | 65 | void balanceOctree(); |
| 66 | 66 | ||
| @@ -91,14 +91,14 @@ QObject* OffscreenUi::getFlags() { · 90 unmodified lines | |||
| 91 | 91 | ||
| 92 | void OffscreenUi::create(QOpenGLContext* context) { | 92 | void OffscreenUi::create(QOpenGLContext* context) { |
| 93 | OffscreenQmlSurface::create(context); | 93 | OffscreenQmlSurface::create(context); |
| 94 | auto rootContext = getRootContext(); | 94 | auto myContext = getSurfaceContext(); |
| 95 | 95 | ||
| 96 | rootContext->setContextProperty("OffscreenUi", this); | 96 | myContext->setContextProperty("OffscreenUi", this); |
| 97 | rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); | 97 | myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); |
| 98 | rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); | 98 | myContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); |
| 99 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); | 99 | auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); |
| 100 | TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); | 100 | TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); |
| 101 | rootContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); | 101 | myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) { | 104 | void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) { |
| @@ -547,14 +547,14 @@ void OffscreenUi::createDesktop(const QUrl& url) { · 442 unmodified lines | |||
| 547 | } | 547 | } |
| 548 | 548 | ||
| 549 | #ifdef DEBUG | 549 | #ifdef DEBUG |
| 550 | getRootContext()->setContextProperty("DebugQML", QVariant(true)); | 550 | getSurfaceContext()->setContextProperty("DebugQML", QVariant(true)); |
| 551 | #else | 551 | #else |
| 552 | getRootContext()->setContextProperty("DebugQML", QVariant(false)); | 552 | getSurfaceContext()->setContextProperty("DebugQML", QVariant(false)); |
| 553 | #endif | 553 | #endif |
| 554 | 554 | ||
| 555 | _desktop = dynamic_cast<QQuickItem*>(load(url)); | 555 | _desktop = dynamic_cast<QQuickItem*>(load(url)); |
| 556 | Q_ASSERT(_desktop); | 556 | Q_ASSERT(_desktop); |
| 557 | getRootContext()->setContextProperty("desktop", _desktop); | 557 | getSurfaceContext()->setContextProperty("desktop", _desktop); |
| 558 | 558 | ||
| 559 | _toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow"); | 559 | _toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow"); |
| 560 | 560 | ||
| @@ -104,9 +104,9 @@ void QmlWindowClass::initQml(QVariantMap properties) { · 103 unmodified lines | |||
| 104 | Q_ASSERT(invokeResult); | 104 | Q_ASSERT(invokeResult); |
| 105 | } else { | 105 | } else { |
| 106 | // Build the event bridge and wrapper on the main thread | 106 | // Build the event bridge and wrapper on the main thread |
| 107 | offscreenUi->load(qmlSource(), [&](QQmlContext* context, QObject* object) { | 107 | offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) { |
| 108 | _qmlWindow = object; | 108 | _qmlWindow = object; |
| 109 | _qmlWindow->setProperty("eventBridge", QVariant::fromValue(this)); | 109 | context->setContextProperty("eventBridge", this); |
| 110 | context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); | 110 | context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); |
| 111 | context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); | 111 | context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); |
| 112 | if (properties.contains(TITLE_PROPERTY)) { | 112 | if (properties.contains(TITLE_PROPERTY)) { |
| @@ -115,7 +115,7 @@ private: · 114 unmodified lines | |||
| 115 | 115 | ||
| 116 | VrMenu::VrMenu(OffscreenUi* parent) : QObject(parent) { | 116 | VrMenu::VrMenu(OffscreenUi* parent) : QObject(parent) { |
| 117 | _rootMenu = parent->getRootItem()->findChild<QObject*>("rootMenu"); | 117 | _rootMenu = parent->getRootItem()->findChild<QObject*>("rootMenu"); |
| 118 | parent->getRootContext()->setContextProperty("rootMenu", _rootMenu); | 118 | parent->getSurfaceContext()->setContextProperty("rootMenu", _rootMenu); |
| 119 | } | 119 | } |
| 120 | 120 | ||
| 121 | QObject* VrMenu::findMenuObject(const QString& menuOption) { | 121 | QObject* VrMenu::findMenuObject(const QString& menuOption) { |
| @@ -273,7 +273,7 @@ bool KinectPlugin::activate() { · 272 unmodified lines | |||
| 273 | return false; | 273 | return false; |
| 274 | } | 274 | } |
| 275 | 275 | ||
| 276 | bool KinectPlugin::isHandController() const { | 276 | bool KinectPlugin::isHandController() const { |
| 277 | bool sensorAvailable = false; | 277 | bool sensorAvailable = false; |
| 278 | #ifdef HAVE_KINECT | 278 | #ifdef HAVE_KINECT |
| 279 | if (_kinectSensor) { | 279 | if (_kinectSensor) { |
| @@ -285,6 +285,10 @@ bool KinectPlugin::isHandController() const { · 5 unmodified lines | |||
| 285 | return _enabled && _initialized && sensorAvailable; | 285 | return _enabled && _initialized && sensorAvailable; |
| 286 | } | 286 | } |
| 287 | 287 | ||
| 288 | bool KinectPlugin::isHeadController() const { | ||
| 289 | return isHandController(); | ||
| 290 | } | ||
| 291 | |||
| 288 | 292 | ||
| 289 | bool KinectPlugin::initializeDefaultSensor() const { | 293 | bool KinectPlugin::initializeDefaultSensor() const { |
| 290 | #ifdef HAVE_KINECT | 294 | #ifdef HAVE_KINECT |
| @@ -654,4 +658,4 @@ void KinectPlugin::InputDevice::clearState() { · 363 unmodified lines | |||
| 654 | int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i); | 658 | int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i); |
| 655 | _poseStateMap[poseIndex] = controller::Pose(); | 659 | _poseStateMap[poseIndex] = controller::Pose(); |
| 656 | } | 660 | } |
| 657 | } | 661 | } |
| @@ -43,6 +43,7 @@ class KinectPlugin : public InputPlugin { · 42 unmodified lines | |||
| 43 | Q_OBJECT | 43 | Q_OBJECT |
| 44 | public: | 44 | public: |
| 45 | bool isHandController() const override; | 45 | bool isHandController() const override; |
| 46 | bool isHeadController() const override; | ||
| 46 | 47 | ||
| 47 | // Plugin functions | 48 | // Plugin functions |
| 48 | virtual void init() override; | 49 | virtual void init() override; |
| @@ -25,8 +25,6 @@ class NeuronPlugin : public InputPlugin { · 24 unmodified lines | |||
| 25 | public: | 25 | public: |
| 26 | friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data); | 26 | friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data); |
| 27 | 27 | ||
| 28 | bool isHandController() const override { return false; } | ||
| 29 | |||
| 30 | // Plugin functions | 28 | // Plugin functions |
| 31 | virtual void init() override; | 29 | virtual void init() override; |
| 32 | virtual bool isSupported() const override; | 30 | virtual bool isSupported() const override; |
| @@ -27,7 +27,6 @@ public: · 26 unmodified lines | |||
| 27 | const QString getName() const override { return NAME; } | 27 | const QString getName() const override { return NAME; } |
| 28 | 28 | ||
| 29 | QStringList getSubdeviceNames() override; | 29 | QStringList getSubdeviceNames() override; |
| 30 | bool isHandController() const override { return false; } | ||
| 31 | 30 | ||
| 32 | void init() override; | 31 | void init() override; |
| 33 | void deinit() override; | 32 | void deinit() override; |
| @@ -33,7 +33,7 @@ public: · 32 unmodified lines | |||
| 33 | 33 | ||
| 34 | // Sixense always seems to initialize even if the hydras are not present. Is there | 34 | // Sixense always seems to initialize even if the hydras are not present. Is there |
| 35 | // a way we can properly detect whether the hydras are present? | 35 | // a way we can properly detect whether the hydras are present? |
| 36 | bool isHandController() const override { return false; } | 36 | // bool isHandController() const override { return true; } |
| 37 | 37 | ||
| 38 | virtual bool activate() override; | 38 | virtual bool activate() override; |
| 39 | virtual void deactivate() override; | 39 | virtual void deactivate() override; |
| @@ -123,10 +123,19 @@ bool OculusBaseDisplayPlugin::internalActivate() { · 122 unmodified lines | |||
| 123 | 123 | ||
| 124 | void OculusBaseDisplayPlugin::internalDeactivate() { | 124 | void OculusBaseDisplayPlugin::internalDeactivate() { |
| 125 | Parent::internalDeactivate(); | 125 | Parent::internalDeactivate(); |
| 126 | } | ||
| 127 | |||
| 128 | bool OculusBaseDisplayPlugin::activateStandBySession() { | ||
| 129 | _session = acquireOculusSession(); | ||
| 130 | if (!_session) { | ||
| 131 | return false; | ||
| 132 | } | ||
| 133 | return true; | ||
| 134 | } | ||
| 135 | void OculusBaseDisplayPlugin::deactivateSession() { | ||
| 126 | releaseOculusSession(); | 136 | releaseOculusSession(); |
| 127 | _session = nullptr; | 137 | _session = nullptr; |
| 128 | } | 138 | } |
| 129 | |||
| 130 | void OculusBaseDisplayPlugin::updatePresentPose() { | 139 | void OculusBaseDisplayPlugin::updatePresentPose() { |
| 131 | //mat4 sensorResetMat; | 140 | //mat4 sensorResetMat; |
| 132 | //_currentPresentFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); | 141 | //_currentPresentFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); |
| @@ -26,6 +26,7 @@ public: · 25 unmodified lines | |||
| 26 | void resetSensors() override final; | 26 | void resetSensors() override final; |
| 27 | bool beginFrameRender(uint32_t frameIndex) override; | 27 | bool beginFrameRender(uint32_t frameIndex) override; |
| 28 | float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; } | 28 | float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; } |
| 29 | bool getSupportsAutoSwitch() override final { return true; } | ||
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | protected: | 32 | protected: |
| @@ -33,6 +34,8 @@ protected: · 1 unmodified line | |||
| 33 | void uncustomizeContext() override; | 34 | void uncustomizeContext() override; |
| 34 | bool internalActivate() override; | 35 | bool internalActivate() override; |
| 35 | void internalDeactivate() override; | 36 | void internalDeactivate() override; |
| 37 | bool activateStandBySession() override; | ||
| 38 | void deactivateSession() override; | ||
| 36 | void updatePresentPose() override; | 39 | void updatePresentPose() override; |
| 37 | 40 | ||
| 38 | protected: | 41 | protected: |
| @@ -19,6 +19,8 @@ · 18 unmodified lines | |||
| 19 | 19 | ||
| 20 | #include <PerfStat.h> | 20 | #include <PerfStat.h> |
| 21 | #include <PathUtils.h> | 21 | #include <PathUtils.h> |
| 22 | #include <NumericalConstants.h> | ||
| 23 | #include <StreamUtils.h> | ||
| 22 | 24 | ||
| 23 | #include <OVR_CAPI.h> | 25 | #include <OVR_CAPI.h> |
| 24 | 26 | ||
| @@ -208,13 +210,18 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() { · 183 unmodified lines | |||
| 208 | _buttonPressedMap.clear(); | 210 | _buttonPressedMap.clear(); |
| 209 | } | 211 | } |
| 210 | 212 | ||
| 211 | void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { | 213 | bool OculusControllerManager::isHeadControllerMounted() const { |
| 212 | _buttonPressedMap.clear(); | ||
| 213 | ovrSessionStatus status; | 214 | ovrSessionStatus status; |
| 214 | if (!OVR_SUCCESS(ovr_GetSessionStatus(_parent._session, &status)) || (ovrFalse == status.HmdMounted)) { | 215 | bool success = OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)); |
| 215 | // if the HMD isn't on someone's head, don't take input from the controllers | 216 | if (!success) { |
| 216 | return; | 217 | return false; |
| 217 | } | 218 | } |
| 219 | return status.HmdMounted == ovrTrue; | ||
| 220 | } | ||
| 221 | |||
| 222 | void OculusControllerManager::TouchDevice::update(float deltaTime, | ||
| 223 | const controller::InputCalibrationData& inputCalibrationData) { | ||
| 224 | _buttonPressedMap.clear(); | ||
| 218 | 225 | ||
| 219 | int numTrackedControllers = 0; | 226 | int numTrackedControllers = 0; |
| 220 | quint64 currentTime = usecTimestampNow(); | 227 | quint64 currentTime = usecTimestampNow(); |
| @@ -230,14 +237,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control · 9 unmodified lines | |||
| 230 | _lastControllerPose[controller] = tracking.HandPoses[hand]; | 237 | _lastControllerPose[controller] = tracking.HandPoses[hand]; |
| 231 | return; | 238 | return; |
| 232 | } | 239 | } |
| 233 | 240 | ||
| 234 | if (_lostTracking[controller]) { | 241 | if (_lostTracking[controller]) { |
| 235 | if (currentTime > _regainTrackingDeadline[controller]) { | 242 | if (currentTime > _regainTrackingDeadline[controller]) { |
| 236 | _poseStateMap.erase(controller); | 243 | _poseStateMap.erase(controller); |
| 237 | _poseStateMap[controller].valid = false; | 244 | _poseStateMap[controller].valid = false; |
| 238 | return; | 245 | return; |
| 239 | } | 246 | } |
| 240 | 247 | ||
| 241 | } else { | 248 | } else { |
| 242 | quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; | 249 | quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; |
| 243 | _regainTrackingDeadline[controller] = deadlineToRegainTracking; | 250 | _regainTrackingDeadline[controller] = deadlineToRegainTracking; |
| @@ -245,6 +252,13 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control · 1 unmodified line | |||
| 245 | } | 252 | } |
| 246 | handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]); | 253 | handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]); |
| 247 | }); | 254 | }); |
| 255 | |||
| 256 | if (_parent.isHeadControllerMounted()) { | ||
| 257 | handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose); | ||
| 258 | } else { | ||
| 259 | _poseStateMap[controller::HEAD].valid = false; | ||
| 260 | } | ||
| 261 | |||
| 248 | using namespace controller; | 262 | using namespace controller; |
| 249 | // Axes | 263 | // Axes |
| 250 | const auto& inputState = _parent._inputState; | 264 | const auto& inputState = _parent._inputState; |
| @@ -269,7 +283,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control · 18 unmodified lines | |||
| 269 | if (inputState.Touches & pair.first) { | 283 | if (inputState.Touches & pair.first) { |
| 270 | _buttonPressedMap.insert(pair.second); | 284 | _buttonPressedMap.insert(pair.second); |
| 271 | } | 285 | } |
| 272 | } | 286 | } |
| 273 | 287 | ||
| 274 | // Haptics | 288 | // Haptics |
| 275 | { | 289 | { |
| @@ -292,16 +306,38 @@ void OculusControllerManager::TouchDevice::focusOutEvent() { · 16 unmodified lines | |||
| 292 | _buttonPressedMap.clear(); | 306 | _buttonPressedMap.clear(); |
| 293 | }; | 307 | }; |
| 294 | 308 | ||
| 295 | void OculusControllerManager::TouchDevice::handlePose(float deltaTime, | 309 | void OculusControllerManager::TouchDevice::handlePose(float deltaTime, |
| 296 | const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, | 310 | const controller::InputCalibrationData& inputCalibrationData, |
| 297 | const ovrPoseStatef& handPose) { | 311 | ovrHandType hand, const ovrPoseStatef& handPose) { |
| 298 | auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; | 312 | auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; |
| 299 | auto& pose = _poseStateMap[poseId]; | 313 | auto& pose = _poseStateMap[poseId]; |
| 300 | pose = ovrControllerPoseToHandPose(hand, handPose); | 314 | pose = ovrControllerPoseToHandPose(hand, handPose); |
| 301 | // transform into avatar frame | 315 | // transform into avatar frame |
| 302 | glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; | 316 | glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; |
| 303 | pose = pose.transform(controllerToAvatar); | 317 | pose = pose.transform(controllerToAvatar); |
| 318 | } | ||
| 319 | |||
| 320 | void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime, | ||
| 321 | const controller::InputCalibrationData& inputCalibrationData, | ||
| 322 | const ovrPoseStatef& headPose) { | ||
| 323 | glm::mat4 mat = createMatFromQuatAndPos(toGlm(headPose.ThePose.Orientation), | ||
| 324 | toGlm(headPose.ThePose.Position)); | ||
| 325 | |||
| 326 | //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z | ||
| 327 | glm::mat4 matYFlip = mat * Matrices::Y_180; | ||
| 328 | controller::Pose pose(extractTranslation(matYFlip), | ||
| 329 | glmExtractRotation(matYFlip), | ||
| 330 | toGlm(headPose.LinearVelocity), // XXX * matYFlip ? | ||
| 331 | toGlm(headPose.AngularVelocity)); | ||
| 332 | |||
| 333 | glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; | ||
| 334 | glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * | ||
| 335 | inputCalibrationData.defaultHeadMat; | ||
| 336 | |||
| 337 | controller::Pose hmdHeadPose = pose.transform(sensorToAvatar); | ||
| 304 | 338 | ||
| 339 | pose.valid = true; | ||
| 340 | _poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset); | ||
| 305 | } | 341 | } |
| 306 | 342 | ||
| 307 | void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, | 343 | void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, |
| @@ -382,6 +418,7 @@ controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailabl · 74 unmodified lines | |||
| 382 | 418 | ||
| 383 | makePair(LEFT_HAND, "LeftHand"), | 419 | makePair(LEFT_HAND, "LeftHand"), |
| 384 | makePair(RIGHT_HAND, "RightHand"), | 420 | makePair(RIGHT_HAND, "RightHand"), |
| 421 | makePair(HEAD, "Head"), | ||
| 385 | 422 | ||
| 386 | makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), | 423 | makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), |
| 387 | makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), | 424 | makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), |
| @@ -28,6 +28,8 @@ public: · 27 unmodified lines | |||
| 28 | const QString getName() const override { return NAME; } | 28 | const QString getName() const override { return NAME; } |
| 29 | 29 | ||
| 30 | bool isHandController() const override { return _touch != nullptr; } | 30 | bool isHandController() const override { return _touch != nullptr; } |
| 31 | bool isHeadController() const override { return true; } | ||
| 32 | bool isHeadControllerMounted() const; | ||
| 31 | QStringList getSubdeviceNames() override; | 33 | QStringList getSubdeviceNames() override; |
| 32 | 34 | ||
| 33 | bool activate() override; | 35 | bool activate() override; |
| @@ -75,8 +77,13 @@ private: · 41 unmodified lines | |||
| 75 | 77 | ||
| 76 | private: | 78 | private: |
| 77 | void stopHapticPulse(bool leftHand); | 79 | void stopHapticPulse(bool leftHand); |
| 78 | void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose); | 80 | void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, |
| 79 | void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose); | 81 | ovrHandType hand, const ovrPoseStatef& handPose); |
| 82 | void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, | ||
| 83 | ovrHandType hand, const ovrPoseStatef& handPose); | ||
| 84 | void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, | ||
| 85 | const ovrPoseStatef& headPose); | ||
| 86 | |||
| 80 | int _trackedControllers { 0 }; | 87 | int _trackedControllers { 0 }; |
| 81 | 88 | ||
| 82 | // perform an action when the TouchDevice mutex is acquired. | 89 | // perform an action when the TouchDevice mutex is acquired. |
| @@ -24,7 +24,7 @@ · 23 unmodified lines | |||
| 24 | #include <gl/QOpenGLContextWrapper.h> | 24 | #include <gl/QOpenGLContextWrapper.h> |
| 25 | #include <PerfStat.h> | 25 | #include <PerfStat.h> |
| 26 | #include <ViewFrustum.h> | 26 | #include <ViewFrustum.h> |
| 27 | #include <gpu/gl/GLbackend.h> | 27 | #include <gpu/gl/GLBackend.h> |
| 28 | 28 | ||
| 29 | #include <ui-plugins/PluginContainer.h> | 29 | #include <ui-plugins/PluginContainer.h> |
| 30 | 30 | ||
| @@ -10,10 +10,10 @@ if (WIN32) · 9 unmodified lines | |||
| 10 | # we're using static GLEW, so define GLEW_STATIC | 10 | # we're using static GLEW, so define GLEW_STATIC |
| 11 | add_definitions(-DGLEW_STATIC) | 11 | add_definitions(-DGLEW_STATIC) |
| 12 | set(TARGET_NAME openvr) | 12 | set(TARGET_NAME openvr) |
| 13 | setup_hifi_plugin(OpenGL Script Qml Widgets) | 13 | setup_hifi_plugin(OpenGL Script Qml Widgets Multimedia) |
| 14 | link_hifi_libraries(shared gl networking controllers ui | 14 | link_hifi_libraries(shared gl networking controllers ui |
| 15 | plugins display-plugins ui-plugins input-plugins script-engine | 15 | plugins display-plugins ui-plugins input-plugins script-engine |
| 16 | render-utils model gpu gpu-gl render model-networking fbx ktx image) | 16 | audio-client render-utils model gpu gpu-gl render model-networking fbx ktx image procedural) |
| 17 | 17 | ||
| 18 | include_hifi_library_headers(octree) | 18 | include_hifi_library_headers(octree) |
| 19 | 19 | ||
| @@ -21,4 +21,5 @@ if (WIN32) · 1 unmodified line | |||
| 21 | find_package(OpenVR REQUIRED) | 21 | find_package(OpenVR REQUIRED) |
| 22 | target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) | 22 | target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) |
| 23 | target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) | 23 | target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) |
| 24 | target_link_libraries(${TARGET_NAME} Winmm.lib) | ||
| 24 | endif() | 25 | endif() |
| @@ -7,6 +7,9 @@ · 6 unmodified lines | |||
| 7 | // | 7 | // |
| 8 | #include "OpenVrDisplayPlugin.h" | 8 | #include "OpenVrDisplayPlugin.h" |
| 9 | 9 | ||
| 10 | // Odd ordering of header is required to avoid 'macro redinition warnings' | ||
| 11 | #include <AudioClient.h> | ||
| 12 | |||
| 10 | #include <QtCore/QThread> | 13 | #include <QtCore/QThread> |
| 11 | #include <QtCore/QLoggingCategory> | 14 | #include <QtCore/QLoggingCategory> |
| 12 | #include <QtCore/QFileInfo> | 15 | #include <QtCore/QFileInfo> |
| @@ -713,3 +716,30 @@ bool OpenVrDisplayPlugin::isKeyboardVisible() { · 700 unmodified lines | |||
| 713 | int OpenVrDisplayPlugin::getRequiredThreadCount() const { | 716 | int OpenVrDisplayPlugin::getRequiredThreadCount() const { |
| 714 | return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0); | 717 | return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0); |
| 715 | } | 718 | } |
| 719 | |||
| 720 | QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const { | ||
| 721 | QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String); | ||
| 722 | if (!device.isEmpty()) { | ||
| 723 | static const WCHAR INIT = 0; | ||
| 724 | size_t size = device.size() + 1; | ||
| 725 | std::vector<WCHAR> deviceW; | ||
| 726 | deviceW.assign(size, INIT); | ||
| 727 | device.toWCharArray(deviceW.data()); | ||
| 728 | device = AudioClient::friendlyNameForAudioDevice(deviceW.data()); | ||
| 729 | } | ||
| 730 | return device; | ||
| 731 | } | ||
| 732 | |||
| 733 | QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { | ||
| 734 | QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnRecordDevice_String); | ||
| 735 | if (!device.isEmpty()) { | ||
| 736 | static const WCHAR INIT = 0; | ||
| 737 | size_t size = device.size() + 1; | ||
| 738 | std::vector<WCHAR> deviceW; | ||
| 739 | deviceW.assign(size, INIT); | ||
| 740 | device.toWCharArray(deviceW.data()); | ||
| 741 | device = AudioClient::friendlyNameForAudioDevice(deviceW.data()); | ||
| 742 | } | ||
| 743 | return device; | ||
| 744 | } | ||
| 745 | |||
| @@ -56,7 +56,10 @@ public: · 55 unmodified lines | |||
| 56 | bool isKeyboardVisible() override; | 56 | bool isKeyboardVisible() override; |
| 57 | 57 | ||
| 58 | // Possibly needs an additional thread for VR submission | 58 | // Possibly needs an additional thread for VR submission |
| 59 | int getRequiredThreadCount() const override; | 59 | int getRequiredThreadCount() const override; |
| 60 | |||
| 61 | QString getPreferredAudioInDevice() const override; | ||
| 62 | QString getPreferredAudioOutDevice() const override; | ||
| 60 | 63 | ||
| 61 | protected: | 64 | protected: |
| 62 | bool internalActivate() override; | 65 | bool internalActivate() override; |
| @@ -72,6 +72,21 @@ bool openVrSupported() { · 71 unmodified lines | |||
| 72 | return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); | 72 | return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | QString getVrSettingString(const char* section, const char* setting) { | ||
| 76 | QString result; | ||
| 77 | static const uint32_t BUFFER_SIZE = 1024; | ||
| 78 | static char BUFFER[BUFFER_SIZE]; | ||
| 79 | vr::IVRSettings * vrSettings = vr::VRSettings(); | ||
| 80 | if (vrSettings) { | ||
| 81 | vr::EVRSettingsError error = vr::VRSettingsError_None; | ||
| 82 | vrSettings->GetString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String, BUFFER, BUFFER_SIZE, &error); | ||
| 83 | if (error == vr::VRSettingsError_None) { | ||
| 84 | result = BUFFER; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | return result; | ||
| 88 | } | ||
| 89 | |||
| 75 | vr::IVRSystem* acquireOpenVrSystem() { | 90 | vr::IVRSystem* acquireOpenVrSystem() { |
| 76 | bool hmdPresent = vr::VR_IsHmdPresent(); | 91 | bool hmdPresent = vr::VR_IsHmdPresent(); |
| 77 | if (hmdPresent) { | 92 | if (hmdPresent) { |
| @@ -82,6 +97,7 @@ vr::IVRSystem* acquireOpenVrSystem() { · 4 unmodified lines | |||
| 82 | #endif | 97 | #endif |
| 83 | vr::EVRInitError eError = vr::VRInitError_None; | 98 | vr::EVRInitError eError = vr::VRInitError_None; |
| 84 | activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); | 99 | activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); |
| 100 | |||
| 85 | #if DEV_BUILD | 101 | #if DEV_BUILD |
| 86 | qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; | 102 | qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; |
| 87 | #endif | 103 | #endif |
| @@ -25,6 +25,7 @@ bool openVrQuitRequested(); · 24 unmodified lines | |||
| 25 | void enableOpenVrKeyboard(PluginContainer* container); | 25 | void enableOpenVrKeyboard(PluginContainer* container); |
| 26 | void disableOpenVrKeyboard(); | 26 | void disableOpenVrKeyboard(); |
| 27 | bool isOpenVrKeyboardShown(); | 27 | bool isOpenVrKeyboardShown(); |
| 28 | QString getVrSettingString(const char* section, const char* setting); | ||
| 28 | 29 | ||
| 29 | 30 | ||
| 30 | template<typename F> | 31 | template<typename F> |
| @@ -50,11 +50,15 @@ static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; · 49 unmodified lines | |||
| 50 | static const int MIN_PUCK_COUNT = 2; | 50 | static const int MIN_PUCK_COUNT = 2; |
| 51 | static const int MIN_FEET_AND_HIPS = 3; | 51 | static const int MIN_FEET_AND_HIPS = 3; |
| 52 | static const int MIN_FEET_HIPS_CHEST = 4; | 52 | static const int MIN_FEET_HIPS_CHEST = 4; |
| 53 | static const int MIN_FEET_HIPS_HEAD = 4; | ||
| 53 | static const int MIN_FEET_HIPS_SHOULDERS = 5; | 54 | static const int MIN_FEET_HIPS_SHOULDERS = 5; |
| 55 | static const int MIN_FEET_HIPS_CHEST_HEAD = 5; | ||
| 54 | static const int FIRST_FOOT = 0; | 56 | static const int FIRST_FOOT = 0; |
| 55 | static const int SECOND_FOOT = 1; | 57 | static const int SECOND_FOOT = 1; |
| 56 | static const int HIP = 2; | 58 | static const int HIP = 2; |
| 57 | static const int CHEST = 3; | 59 | static const int CHEST = 3; |
| 60 | static float HEAD_PUCK_Y_OFFSET = -0.0254f; | ||
| 61 | static float HEAD_PUCK_Z_OFFSET = -0.152f; | ||
| 58 | 62 | ||
| 59 | const char* ViveControllerManager::NAME { "OpenVR" }; | 63 | const char* ViveControllerManager::NAME { "OpenVR" }; |
| 60 | 64 | ||
| @@ -80,6 +84,28 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck) · 19 unmodified lines | |||
| 80 | return (firstPuck.second.translation.x < secondPuck.second.translation.x); | 84 | return (firstPuck.second.translation.x < secondPuck.second.translation.x); |
| 81 | } | 85 | } |
| 82 | 86 | ||
| 87 | static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) { | ||
| 88 | glm::vec3 poseAPosition = poseA.getTranslation(); | ||
| 89 | glm::vec3 poseBPosition = poseB.getTranslation(); | ||
| 90 | |||
| 91 | glm::vec3 poseAVector = poseAPosition - axisOrigin; | ||
| 92 | glm::vec3 poseBVector = poseBPosition - axisOrigin; | ||
| 93 | |||
| 94 | float poseAProjection = glm::dot(poseAVector, axis); | ||
| 95 | float poseBProjection = glm::dot(poseBVector, axis); | ||
| 96 | return (poseAProjection > poseBProjection); | ||
| 97 | } | ||
| 98 | |||
| 99 | static glm::vec3 getReferenceHeadXAxis(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) { | ||
| 100 | glm::mat4 finalHead = defaultToReferenceMat * defaultHead; | ||
| 101 | return glmExtractRotation(finalHead) * Vectors::UNIT_X; | ||
| 102 | } | ||
| 103 | |||
| 104 | static glm::vec3 getReferenceHeadPosition(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) { | ||
| 105 | glm::mat4 finalHead = defaultToReferenceMat * defaultHead; | ||
| 106 | return extractTranslation(finalHead); | ||
| 107 | } | ||
| 108 | |||
| 83 | static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) { | 109 | static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) { |
| 84 | QString result; | 110 | QString result; |
| 85 | auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult); | 111 | auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult); |
| @@ -97,15 +123,18 @@ bool ViveControllerManager::isSupported() const { · 11 unmodified lines | |||
| 97 | bool ViveControllerManager::activate() { | 123 | bool ViveControllerManager::activate() { |
| 98 | InputPlugin::activate(); | 124 | InputPlugin::activate(); |
| 99 | 125 | ||
| 100 | _container->addMenu(MENU_PATH); | ||
| 101 | _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, | ||
| 102 | [this] (bool clicked) { this->setRenderControllers(clicked); }, | ||
| 103 | true, true); | ||
| 104 | |||
| 105 | if (!_system) { | 126 | if (!_system) { |
| 106 | _system = acquireOpenVrSystem(); | 127 | _system = acquireOpenVrSystem(); |
| 107 | } | 128 | } |
| 108 | Q_ASSERT(_system); | 129 | |
| 130 | if (!_system) { | ||
| 131 | return false; | ||
| 132 | } | ||
| 133 | |||
| 134 | _container->addMenu(MENU_PATH); | ||
| 135 | _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, | ||
| 136 | [this](bool clicked) { this->setRenderControllers(clicked); }, | ||
| 137 | true, true); | ||
| 109 | 138 | ||
| 110 | enableOpenVrKeyboard(_container); | 139 | enableOpenVrKeyboard(_container); |
| 111 | 140 | ||
| @@ -138,6 +167,14 @@ void ViveControllerManager::deactivate() { · 26 unmodified lines | |||
| 138 | _registeredWithInputMapper = false; | 167 | _registeredWithInputMapper = false; |
| 139 | } | 168 | } |
| 140 | 169 | ||
| 170 | bool ViveControllerManager::isHeadControllerMounted() const { | ||
| 171 | if (_inputDevice && _inputDevice->isHeadControllerMounted()) { | ||
| 172 | return true; | ||
| 173 | } | ||
| 174 | vr::EDeviceActivityLevel activityLevel = _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd); | ||
| 175 | return activityLevel == vr::k_EDeviceActivityLevel_UserInteraction; | ||
| 176 | } | ||
| 177 | |||
| 141 | void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { | 178 | void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { |
| 142 | 179 | ||
| 143 | if (!_system) { | 180 | if (!_system) { |
| @@ -175,6 +212,8 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : contro · 31 unmodified lines | |||
| 175 | _configStringMap[Config::FeetAndHips] = QString("FeetAndHips"); | 212 | _configStringMap[Config::FeetAndHips] = QString("FeetAndHips"); |
| 176 | _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest"); | 213 | _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest"); |
| 177 | _configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders"); | 214 | _configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders"); |
| 215 | _configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead"); | ||
| 216 | _configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead"); | ||
| 178 | 217 | ||
| 179 | if (openVrSupported()) { | 218 | if (openVrSupported()) { |
| 180 | createPreferences(); | 219 | createPreferences(); |
| @@ -355,6 +394,23 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr · 174 unmodified lines | |||
| 355 | int firstShoulderIndex = 3; | 394 | int firstShoulderIndex = 3; |
| 356 | int secondShoulderIndex = 4; | 395 | int secondShoulderIndex = 4; |
| 357 | calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex); | 396 | calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex); |
| 397 | } else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) { | ||
| 398 | glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration); | ||
| 399 | glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); | ||
| 400 | glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); | ||
| 401 | calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition); | ||
| 402 | calibrateHips(headPuckDefaultToReferenceMat, inputCalibration); | ||
| 403 | calibrateHead(headPuckDefaultToReferenceMat, inputCalibration); | ||
| 404 | _overrideHead = true; | ||
| 405 | } else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) { | ||
| 406 | glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration); | ||
| 407 | glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); | ||
| 408 | glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); | ||
| 409 | calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition); | ||
| 410 | calibrateHips(headPuckDefaultToReferenceMat, inputCalibration); | ||
| 411 | calibrateChest(headPuckDefaultToReferenceMat, inputCalibration); | ||
| 412 | calibrateHead(headPuckDefaultToReferenceMat, inputCalibration); | ||
| 413 | _overrideHead = true; | ||
| 358 | } else { | 414 | } else { |
| 359 | qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks"; | 415 | qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks"; |
| 360 | uncalibrate(); | 416 | uncalibrate(); |
| @@ -369,6 +425,7 @@ void ViveControllerManager::InputDevice::uncalibrate() { · 8 unmodified lines | |||
| 369 | _pucksOffset.clear(); | 425 | _pucksOffset.clear(); |
| 370 | _jointToPuckMap.clear(); | 426 | _jointToPuckMap.clear(); |
| 371 | _calibrated = false; | 427 | _calibrated = false; |
| 428 | _overrideHead = false; | ||
| 372 | } | 429 | } |
| 373 | 430 | ||
| 374 | void ViveControllerManager::InputDevice::updateCalibratedLimbs() { | 431 | void ViveControllerManager::InputDevice::updateCalibratedLimbs() { |
| @@ -378,6 +435,10 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() { · 3 unmodified lines | |||
| 378 | _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); | 435 | _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); |
| 379 | _poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM); | 436 | _poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM); |
| 380 | _poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM); | 437 | _poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM); |
| 438 | |||
| 439 | if (_overrideHead) { | ||
| 440 | _poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD); | ||
| 441 | } | ||
| 381 | } | 442 | } |
| 382 | 443 | ||
| 383 | controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const { | 444 | controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const { |
| @@ -443,6 +504,43 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u · 59 unmodified lines | |||
| 443 | } | 504 | } |
| 444 | } | 505 | } |
| 445 | 506 | ||
| 507 | glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) { | ||
| 508 | glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat; | ||
| 509 | glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; | ||
| 510 | size_t headPuckIndex = _validTrackedObjects.size() - 1; | ||
| 511 | controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second; | ||
| 512 | glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180; | ||
| 513 | glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat); | ||
| 514 | glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f); | ||
| 515 | glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f); | ||
| 516 | |||
| 517 | // check that the head puck z axis is not parrallel to the world up | ||
| 518 | const float EPSILON = 1.0e-4f; | ||
| 519 | glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f); | ||
| 520 | if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) { | ||
| 521 | headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f); | ||
| 522 | } | ||
| 523 | |||
| 524 | glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f); | ||
| 525 | glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis)); | ||
| 526 | glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime)); | ||
| 527 | glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), | ||
| 528 | glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f)); | ||
| 529 | |||
| 530 | glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), | ||
| 531 | glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f)); | ||
| 532 | |||
| 533 | glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset; | ||
| 534 | |||
| 535 | glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; | ||
| 536 | |||
| 537 | glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset; | ||
| 538 | |||
| 539 | // calculate the defaultToRefrenceXform | ||
| 540 | glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); | ||
| 541 | return defaultToReferenceMat; | ||
| 542 | } | ||
| 543 | |||
| 446 | void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { | 544 | void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { |
| 447 | // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. | 545 | // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. |
| 448 | const float CENTER_DEADBAND = 0.6f; | 546 | const float CENTER_DEADBAND = 0.6f; |
| @@ -630,6 +728,26 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer · 181 unmodified lines | |||
| 630 | } | 728 | } |
| 631 | } | 729 | } |
| 632 | 730 | ||
| 731 | |||
| 732 | void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) { | ||
| 733 | auto& firstFoot = _validTrackedObjects[FIRST_FOOT]; | ||
| 734 | auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; | ||
| 735 | controller::Pose& firstFootPose = firstFoot.second; | ||
| 736 | controller::Pose& secondFootPose = secondFoot.second; | ||
| 737 | |||
| 738 | if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { | ||
| 739 | _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; | ||
| 740 | _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); | ||
| 741 | _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; | ||
| 742 | _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose); | ||
| 743 | } else { | ||
| 744 | _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; | ||
| 745 | _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose); | ||
| 746 | _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; | ||
| 747 | _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose); | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 633 | void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { | 751 | void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { |
| 634 | _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; | 752 | _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; |
| 635 | _pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second); | 753 | _pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second); |
| @@ -658,7 +776,19 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo · 22 unmodified lines | |||
| 658 | _jointToPuckMap[controller::RIGHT_ARM] = firstShoulder.first; | 776 | _jointToPuckMap[controller::RIGHT_ARM] = firstShoulder.first; |
| 659 | _pucksOffset[firstShoulder.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightArm, firstShoulder.second); | 777 | _pucksOffset[firstShoulder.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightArm, firstShoulder.second); |
| 660 | } | 778 | } |
| 661 | } | 779 | } |
| 780 | |||
| 781 | void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { | ||
| 782 | size_t headIndex = _validTrackedObjects.size() - 1; | ||
| 783 | const PuckPosePair& head = _validTrackedObjects[headIndex]; | ||
| 784 | |||
| 785 | // assume the person is wearing the head puck on his/her forehead | ||
| 786 | glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; | ||
| 787 | controller::Pose newHead = head.second.postTransform(defaultHeadOffset); | ||
| 788 | |||
| 789 | _jointToPuckMap[controller::HEAD] = head.first; | ||
| 790 | _pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead); | ||
| 791 | } | ||
| 662 | 792 | ||
| 663 | 793 | ||
| 664 | void ViveControllerManager::InputDevice::loadSettings() { | 794 | void ViveControllerManager::InputDevice::loadSettings() { |
| @@ -694,6 +824,10 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu · 29 unmodified lines | |||
| 694 | _preferedConfig = Config::FeetHipsAndChest; | 824 | _preferedConfig = Config::FeetHipsAndChest; |
| 695 | } else if (value == "FeetHipsAndShoulders") { | 825 | } else if (value == "FeetHipsAndShoulders") { |
| 696 | _preferedConfig = Config::FeetHipsAndShoulders; | 826 | _preferedConfig = Config::FeetHipsAndShoulders; |
| 827 | } else if (value == "FeetHipsChestAndHead") { | ||
| 828 | _preferedConfig = Config::FeetHipsChestAndHead; | ||
| 829 | } else if (value == "FeetHipsAndHead") { | ||
| 830 | _preferedConfig = Config::FeetHipsAndHead; | ||
| 697 | } | 831 | } |
| 698 | } | 832 | } |
| 699 | 833 | ||
| @@ -702,11 +836,43 @@ void ViveControllerManager::InputDevice::createPreferences() { · 2 unmodified lines | |||
| 702 | auto preferences = DependencyManager::get<Preferences>(); | 836 | auto preferences = DependencyManager::get<Preferences>(); |
| 703 | static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration"; | 837 | static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration"; |
| 704 | 838 | ||
| 839 | { | ||
| 840 | static const float MIN_VALUE = -3.0f; | ||
| 841 | static const float MAX_VALUE = 3.0f; | ||
| 842 | static const float STEP = 0.01f; | ||
| 843 | |||
| 844 | auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; }; | ||
| 845 | auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; }; | ||
| 846 | |||
| 847 | auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter); | ||
| 848 | preference->setMin(MIN_VALUE); | ||
| 849 | preference->setMax(MAX_VALUE); | ||
| 850 | preference->setDecimals(3); | ||
| 851 | preference->setStep(STEP); | ||
| 852 | preferences->addPreference(preference); | ||
| 853 | } | ||
| 854 | |||
| 855 | { | ||
| 856 | static const float MIN_VALUE = -3.0f; | ||
| 857 | static const float MAX_VALUE = 3.0f; | ||
| 858 | static const float STEP = 0.01f; | ||
| 859 | |||
| 860 | auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; }; | ||
| 861 | auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; }; | ||
| 862 | |||
| 863 | auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter); | ||
| 864 | preference->setMin(MIN_VALUE); | ||
| 865 | preference->setMax(MAX_VALUE); | ||
| 866 | preference->setStep(STEP); | ||
| 867 | preference->setDecimals(3); | ||
| 868 | preferences->addPreference(preference); | ||
| 869 | } | ||
| 870 | |||
| 705 | { | 871 | { |
| 706 | auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; }; | 872 | auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; }; |
| 707 | auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); }; | 873 | auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); }; |
| 708 | auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter); | 874 | auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter); |
| 709 | QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders"}; | 875 | QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"}; |
| 710 | preference->setItems(list); | 876 | preference->setItems(list); |
| 711 | preferences->addPreference(preference); | 877 | preferences->addPreference(preference); |
| 712 | 878 | ||
| @@ -41,6 +41,8 @@ public: · 40 unmodified lines | |||
| 41 | const QString getName() const override { return NAME; } | 41 | const QString getName() const override { return NAME; } |
| 42 | 42 | ||
| 43 | bool isHandController() const override { return true; } | 43 | bool isHandController() const override { return true; } |
| 44 | bool isHeadController() const override { return true; } | ||
| 45 | bool isHeadControllerMounted() const; | ||
| 44 | 46 | ||
| 45 | bool activate() override; | 47 | bool activate() override; |
| 46 | void deactivate() override; | 48 | void deactivate() override; |
| @@ -54,6 +56,7 @@ private: · 7 unmodified lines | |||
| 54 | class InputDevice : public controller::InputDevice { | 56 | class InputDevice : public controller::InputDevice { |
| 55 | public: | 57 | public: |
| 56 | InputDevice(vr::IVRSystem*& system); | 58 | InputDevice(vr::IVRSystem*& system); |
| 59 | bool isHeadControllerMounted() const { return _overrideHead; } | ||
| 57 | private: | 60 | private: |
| 58 | // Device functions | 61 | // Device functions |
| 59 | controller::Input::NamedVector getAvailableInputs() const override; | 62 | controller::Input::NamedVector getAvailableInputs() const override; |
| @@ -67,6 +70,7 @@ private: · 7 unmodified lines | |||
| 67 | void calibrate(const controller::InputCalibrationData& inputCalibration); | 70 | void calibrate(const controller::InputCalibrationData& inputCalibration); |
| 68 | void uncalibrate(); | 71 | void uncalibrate(); |
| 69 | controller::Pose addOffsetToPuckPose(int joint) const; | 72 | controller::Pose addOffsetToPuckPose(int joint) const; |
| 73 | glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration); | ||
| 70 | void updateCalibratedLimbs(); | 74 | void updateCalibratedLimbs(); |
| 71 | bool checkForCalibrationEvent(); | 75 | bool checkForCalibrationEvent(); |
| 72 | void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); | 76 | void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); |
| @@ -84,11 +88,14 @@ private: · 11 unmodified lines | |||
| 84 | void loadSettings(); | 88 | void loadSettings(); |
| 85 | void saveSettings() const; | 89 | void saveSettings() const; |
| 86 | void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); | 90 | void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); |
| 91 | void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition); | ||
| 87 | void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); | 92 | void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); |
| 88 | void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); | 93 | void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); |
| 89 | 94 | ||
| 90 | void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, | 95 | void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, |
| 91 | int firstShoulderIndex, int secondShoulderIndex); | 96 | int firstShoulderIndex, int secondShoulderIndex); |
| 97 | void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); | ||
| 98 | |||
| 92 | 99 | ||
| 93 | class FilteredStick { | 100 | class FilteredStick { |
| 94 | public: | 101 | public: |
| @@ -119,6 +126,8 @@ private: · 24 unmodified lines | |||
| 119 | FeetAndHips, | 126 | FeetAndHips, |
| 120 | FeetHipsAndChest, | 127 | FeetHipsAndChest, |
| 121 | FeetHipsAndShoulders, | 128 | FeetHipsAndShoulders, |
| 129 | FeetHipsChestAndHead, | ||
| 130 | FeetHipsAndHead | ||
| 122 | }; | 131 | }; |
| 123 | Config _config { Config::Auto }; | 132 | Config _config { Config::Auto }; |
| 124 | Config _preferedConfig { Config::Auto }; | 133 | Config _preferedConfig { Config::Auto }; |
| @@ -137,7 +146,7 @@ private: · 12 unmodified lines | |||
| 137 | 146 | ||
| 138 | int _trackedControllers { 0 }; | 147 | int _trackedControllers { 0 }; |
| 139 | vr::IVRSystem*& _system; | 148 | vr::IVRSystem*& _system; |
| 140 | quint64 _timeTilCalibration { 0.0f }; | 149 | quint64 _timeTilCalibration { 0 }; |
| 141 | float _leftHapticStrength { 0.0f }; | 150 | float _leftHapticStrength { 0.0f }; |
| 142 | float _leftHapticDuration { 0.0f }; | 151 | float _leftHapticDuration { 0.0f }; |
| 143 | float _rightHapticStrength { 0.0f }; | 152 | float _rightHapticStrength { 0.0f }; |
| @@ -145,6 +154,7 @@ private: · 1 unmodified line | |||
| 145 | bool _triggersPressedHandled { false }; | 154 | bool _triggersPressedHandled { false }; |
| 146 | bool _calibrated { false }; | 155 | bool _calibrated { false }; |
| 147 | bool _timeTilCalibrationSet { false }; | 156 | bool _timeTilCalibrationSet { false }; |
| 157 | bool _overrideHead { false }; | ||
| 148 | mutable std::recursive_mutex _lock; | 158 | mutable std::recursive_mutex _lock; |
| 149 | 159 | ||
| 150 | QString configToString(Config config); | 160 | QString configToString(Config config); |
| @@ -31,7 +31,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ · 30 unmodified lines | |||
| 31 | "system/tablet-ui/tabletUI.js" | 31 | "system/tablet-ui/tabletUI.js" |
| 32 | ]; | 32 | ]; |
| 33 | var DEFAULT_SCRIPTS_SEPARATE = [ | 33 | var DEFAULT_SCRIPTS_SEPARATE = [ |
| 34 | "system/controllers/controllerScripts.js" | 34 | "system/controllers/controllerScripts.js", |
| 35 | "system/chat.js" | ||
| 35 | ]; | 36 | ]; |
| 36 | 37 | ||
| 37 | // add a menu item for debugging | 38 | // add a menu item for debugging |
| @@ -0,0 +1,127 @@ | |||
| 1 | var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; | ||
| 2 | |||
| 3 | var debugSphereBaseProperties = { | ||
| 4 | type: "Sphere", | ||
| 5 | dimensions: { x: 0.2, y: 0.2, z: 0.2 }, | ||
| 6 | dynamic: false, | ||
| 7 | collisionless: true, | ||
| 8 | gravity: { x: 0, y: 0, z: 0 }, | ||
| 9 | lifetime: 10.0, | ||
| 10 | userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" | ||
| 11 | }; | ||
| 12 | |||
| 13 | var debugBoxBaseProperties = { | ||
| 14 | type: "Box", | ||
| 15 | dimensions: { x: 0.2, y: 0.2, z: 0.2 }, | ||
| 16 | dynamic: false, | ||
| 17 | collisionless: true, | ||
| 18 | gravity: { x: 0, y: 0, z: 0 }, | ||
| 19 | lifetime: 10.0, | ||
| 20 | userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" | ||
| 21 | }; | ||
| 22 | |||
| 23 | //jointToWorldPoint | ||
| 24 | // create sphere for finger on left hand | ||
| 25 | // each frame, calculate world position of finger, with some offset | ||
| 26 | // update sphere to match this position | ||
| 27 | var jointToWorldPointTest_sphereEntity; | ||
| 28 | function jointToWorldPointTest() { | ||
| 29 | var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4"); | ||
| 30 | var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 }; | ||
| 31 | var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex); | ||
| 32 | |||
| 33 | var jointSphereProps = Object.create(debugSphereBaseProperties); | ||
| 34 | jointSphereProps.name = "jointToWorldPointTest_Sphere"; | ||
| 35 | jointSphereProps.color = { blue: 240, green: 150, red: 150 }; | ||
| 36 | jointSphereProps.position = worldPos; | ||
| 37 | jointToWorldPointTest_sphereEntity = Entities.addEntity(jointSphereProps); | ||
| 38 | } | ||
| 39 | function jointToWorldPointTest_update(deltaTime) { | ||
| 40 | var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4"); | ||
| 41 | var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 }; | ||
| 42 | var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex); | ||
| 43 | var newProperties = { position: worldPos }; | ||
| 44 | Entities.editEntity(jointToWorldPointTest_sphereEntity, newProperties); | ||
| 45 | } | ||
| 46 | |||
| 47 | //jointToWorldDirection | ||
| 48 | // create line in world space | ||
| 49 | // each frame calculate world space direction of players head z axis | ||
| 50 | // update line to match | ||
| 51 | var jointToWorldDirectionTest_lineEntity; | ||
| 52 | function jointToWorldDirectionTest() { | ||
| 53 | var jointIndex = MyAvatar.getJointIndex("Head"); | ||
| 54 | var avatarPos = MyAvatar.getJointPosition(jointIndex); | ||
| 55 | |||
| 56 | var jointDir = { x: 1, y: 0, z: 1 }; | ||
| 57 | var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex); | ||
| 58 | print(worldDir.x); | ||
| 59 | print(worldDir.y); | ||
| 60 | print(worldDir.z); | ||
| 61 | jointToWorldDirectionTest_lineEntity = Entities.addEntity({ | ||
| 62 | type: "Line", | ||
| 63 | color: {red: 250, green: 0, blue: 0}, | ||
| 64 | dimensions: {x: 5, y: 5, z: 5}, | ||
| 65 | lifetime: 10.0, | ||
| 66 | linePoints: [{ | ||
| 67 | x: 0, | ||
| 68 | y: 0, | ||
| 69 | z: 0 | ||
| 70 | }, worldDir | ||
| 71 | ], | ||
| 72 | position : avatarPos, | ||
| 73 | }); | ||
| 74 | } | ||
| 75 | function jointToWorldDirection_update(deltaTime) { | ||
| 76 | var jointIndex = MyAvatar.getJointIndex("Head"); | ||
| 77 | var avatarPos = MyAvatar.getJointPosition(jointIndex); | ||
| 78 | var jointDir = { x: 1, y: 0, z: 0 }; | ||
| 79 | var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex); | ||
| 80 | var newProperties = { | ||
| 81 | linePoints: [{ | ||
| 82 | x: 0, | ||
| 83 | y: 0, | ||
| 84 | z: 0 | ||
| 85 | }, worldDir | ||
| 86 | ], | ||
| 87 | position : avatarPos | ||
| 88 | }; | ||
| 89 | |||
| 90 | Entities.editEntity(jointToWorldDirectionTest_lineEntity, newProperties); | ||
| 91 | } | ||
| 92 | |||
| 93 | //jointToWorldRotation | ||
| 94 | // create box in world space | ||
| 95 | // each frame calculate world space rotation of players head | ||
| 96 | // update box rotation to match | ||
| 97 | var jointToWorldRotationTest_boxEntity; | ||
| 98 | function jointToWorldRotationTest() { | ||
| 99 | var jointIndex = MyAvatar.getJointIndex("Head"); | ||
| 100 | var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); | ||
| 101 | var jointRot = MyAvatar.getJointRotation(jointIndex); | ||
| 102 | var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex); | ||
| 103 | |||
| 104 | var boxProps = Object.create(debugBoxBaseProperties); | ||
| 105 | boxProps.name = "jointToWorldRotationTest_Box"; | ||
| 106 | boxProps.color = { blue: 250, green: 250, red: 250 }; | ||
| 107 | boxProps.position = jointPosition_WorldSpace; | ||
| 108 | boxProps.rotation = jointRot_WorldSpace; | ||
| 109 | jointToWorldRotationTest_boxEntity = Entities.addEntity(boxProps); | ||
| 110 | } | ||
| 111 | function jointToWorldRotationTest_update(deltaTime) { | ||
| 112 | var jointIndex = MyAvatar.getJointIndex("Head"); | ||
| 113 | var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); | ||
| 114 | var jointRot = MyAvatar.getJointRotation(jointIndex); | ||
| 115 | var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex); | ||
| 116 | var newProperties = { position: jointPosition_WorldSpace, rotation: jointRot_WorldSpace }; | ||
| 117 | Entities.editEntity(jointToWorldRotationTest_boxEntity, newProperties); | ||
| 118 | } | ||
| 119 | |||
| 120 | jointToWorldPointTest(); | ||
| 121 | Script.update.connect(jointToWorldPointTest_update); | ||
| 122 | |||
| 123 | jointToWorldDirectionTest(); | ||
| 124 | Script.update.connect(jointToWorldDirection_update); | ||
| 125 | |||
| 126 | jointToWorldRotationTest(); | ||
| 127 | Script.update.connect(jointToWorldRotationTest_update); | ||
| @@ -0,0 +1,134 @@ | |||
| 1 | var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; | ||
| 2 | |||
| 3 | var debugSphereBaseProperties = { | ||
| 4 | type: "Sphere", | ||
| 5 | dimensions: { x: 0.2, y: 0.2, z: 0.2 }, | ||
| 6 | dynamic: false, | ||
| 7 | collisionless: true, | ||
| 8 | gravity: { x: 0, y: 0, z: 0 }, | ||
| 9 | lifetime: 10.0, | ||
| 10 | userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" | ||
| 11 | }; | ||
| 12 | |||
| 13 | var debugBoxBaseProperties = { | ||
| 14 | type: "Box", | ||
| 15 | dimensions: { x: 0.2, y: 0.2, z: 0.2 }, | ||
| 16 | dynamic: false, | ||
| 17 | collisionless: true, | ||
| 18 | gravity: { x: 0, y: 0, z: 0 }, | ||
| 19 | lifetime: 10.0, | ||
| 20 | userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" | ||
| 21 | }; | ||
| 22 | |||
| 23 | //worldToJointPoint | ||
| 24 | // calculate position offset from joint using getJointPosition | ||
| 25 | // pass through worldToJointPoint to get offset in joint space of players joint | ||
| 26 | // create a blue sphere and attach it to players joint using the joint space offset | ||
| 27 | // The two spheres should appear in the same place, but the blue sphere will follow the avatar | ||
| 28 | function worldToJointPointTest() { | ||
| 29 | var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4"); | ||
| 30 | var avatarPos = MyAvatar.position; | ||
| 31 | |||
| 32 | var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); | ||
| 33 | var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 }; | ||
| 34 | var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace); | ||
| 35 | var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex); | ||
| 36 | |||
| 37 | var jointSphereProps = Object.create(debugSphereBaseProperties); | ||
| 38 | jointSphereProps.name = "worldToJointPointTest_Joint"; | ||
| 39 | jointSphereProps.color = { blue: 240, green: 150, red: 150 }; | ||
| 40 | jointSphereProps.localPosition = jointPosition_JointSpaceOffset; | ||
| 41 | jointSphereProps.parentID = AVATAR_SELF_ID; | ||
| 42 | jointSphereProps.parentJointIndex = jointIndex; | ||
| 43 | Entities.addEntity(jointSphereProps); | ||
| 44 | |||
| 45 | var worldSphereProps = Object.create(debugSphereBaseProperties); | ||
| 46 | worldSphereProps.name = "worldToJointPointTest_World"; | ||
| 47 | worldSphereProps.color = { blue: 150, green: 250, red: 150 }; | ||
| 48 | worldSphereProps.position = jointPosition_WorldSpaceOffset; | ||
| 49 | Entities.addEntity(worldSphereProps); | ||
| 50 | } | ||
| 51 | |||
| 52 | //worldToJointDirection | ||
| 53 | // create line and attach to avatars head | ||
| 54 | // each frame calculate direction of world x axis in joint space of players head | ||
| 55 | // update arrow orientation to match | ||
| 56 | var worldToJointDirectionTest_lineEntity; | ||
| 57 | function worldToJointDirectionTest() { | ||
| 58 | var jointIndex = MyAvatar.getJointIndex("Head"); | ||
| 59 | |||
| 60 | var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); | ||
| 61 | var jointOffset_WorldSpace = { x: 0, y: 0, z: 0 }; | ||
| 62 | var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace); | ||
| 63 | var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex); | ||
| 64 | |||
| 65 | var worldDir = { x: 1, y: 0, z: 0 }; | ||
| 66 | var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex); | ||
| 67 | |||
| 68 | worldToJointDirectionTest_lineEntity = Entities.addEntity({ | ||
| 69 | type: "Line", | ||
| 70 | color: {red: 200, green: 250, blue: 0}, | ||
| 71 | dimensions: {x: 5, y: 5, z: 5}, | ||
| 72 | lifetime: 10.0, | ||
| 73 | linePoints: [{ | ||
| 74 | x: 0, | ||
| 75 | y: 0, | ||
| 76 | z: 0 | ||
| 77 | }, avatarDir | ||
| 78 | ], | ||
| 79 | localPosition : jointOffset_WorldSpace, | ||
| 80 | parentID : AVATAR_SELF_ID, | ||
| 81 | parentJointIndex : jointIndex | ||
| 82 | }); | ||
| 83 | } | ||
| 84 | |||
| 85 | function worldToJointDirectionTest_update(deltaTime) { | ||
| 86 | var jointIndex = MyAvatar.getJointIndex("Head"); | ||
| 87 | var worldDir = { x: 1, y: 0, z: 0 }; | ||
| 88 | var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex); | ||
| 89 | var newProperties = { linePoints: [{ | ||
| 90 | x: 0, | ||
| 91 | y: 0, | ||
| 92 | z: 0 | ||
| 93 | }, avatarDir | ||
| 94 | ]}; | ||
| 95 | |||
| 96 | Entities.editEntity(worldToJointDirectionTest_lineEntity, newProperties); | ||
| 97 | } | ||
| 98 | |||
| 99 | //worldToJointRotation | ||
| 100 | // create box and parent to some player joint | ||
| 101 | // convert world identity rotation to joint space rotation | ||
| 102 | // each frame, update box with new orientation | ||
| 103 | var worldToJointRotationTest_boxEntity; | ||
| 104 | function worldToJointRotationTest() { | ||
| 105 | var jointIndex = MyAvatar.getJointIndex("RightHandPinky4"); | ||
| 106 | var avatarPos = MyAvatar.position; | ||
| 107 | |||
| 108 | var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); | ||
| 109 | var jointOffset_WorldSpace = { x: 0.0, y: 0, z: 0 }; | ||
| 110 | var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace); | ||
| 111 | var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex); | ||
| 112 | |||
| 113 | var jointBoxProps = Object.create(debugBoxBaseProperties); | ||
| 114 | jointBoxProps.name = "worldToJointRotationTest_Box"; | ||
| 115 | jointBoxProps.color = { blue: 0, green: 0, red: 250 }; | ||
| 116 | jointBoxProps.localPosition = jointPosition_JointSpaceOffset; | ||
| 117 | jointBoxProps.parentID = AVATAR_SELF_ID; | ||
| 118 | jointBoxProps.parentJointIndex = jointIndex; | ||
| 119 | worldToJointRotationTest_boxEntity = Entities.addEntity(jointBoxProps); | ||
| 120 | } | ||
| 121 | function worldToJointRotationTest_update(deltaTime) { | ||
| 122 | var jointIndex = MyAvatar.getJointIndex("RightHandPinky4"); | ||
| 123 | var worldRot = Quat.fromPitchYawRollDegrees(0,0,0); | ||
| 124 | var avatarRot = MyAvatar.worldToJointRotation(worldRot, jointIndex); | ||
| 125 | var newProperties = { localRotation: avatarRot }; | ||
| 126 | Entities.editEntity(worldToJointRotationTest_boxEntity, newProperties); | ||
| 127 | } | ||
| 128 | |||
| 129 | worldToJointPointTest(); | ||
| 130 | worldToJointDirectionTest(); | ||
| 131 | worldToJointRotationTest(); | ||
| 132 | |||
| 133 | Script.update.connect(worldToJointDirectionTest_update); | ||
| 134 | Script.update.connect(worldToJointRotationTest_update); | ||
| @@ -64,6 +64,7 @@ Column { · 63 unmodified lines | |||
| 64 | "Point:LightingModel:enablePointLight", | 64 | "Point:LightingModel:enablePointLight", |
| 65 | "Spot:LightingModel:enableSpotLight", | 65 | "Spot:LightingModel:enableSpotLight", |
| 66 | "Light Contour:LightingModel:showLightContour", | 66 | "Light Contour:LightingModel:showLightContour", |
| 67 | "Zone Stack:DrawZoneStack:enabled", | ||
| 67 | "Shadow:RenderShadowTask:enabled" | 68 | "Shadow:RenderShadowTask:enabled" |
| 68 | ] | 69 | ] |
| 69 | CheckBox { | 70 | CheckBox { |
| @@ -162,13 +163,9 @@ Column { · 92 unmodified lines | |||
| 162 | } | 163 | } |
| 163 | } | 164 | } |
| 164 | 165 | ||
| 166 | Row { | ||
| 165 | Column { | 167 | Column { |
| 166 | id: metas | 168 | |
| 167 | CheckBox { | ||
| 168 | text: "Metas" | ||
| 169 | checked: Render.getConfig("DrawMetaBounds")["enabled"] | ||
| 170 | onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked } | ||
| 171 | } | ||
| 172 | CheckBox { | 169 | CheckBox { |
| 173 | text: "Opaques" | 170 | text: "Opaques" |
| 174 | checked: Render.getConfig("DrawOpaqueBounds")["enabled"] | 171 | checked: Render.getConfig("DrawOpaqueBounds")["enabled"] |
| @@ -189,11 +186,24 @@ Column { · 14 unmodified lines | |||
| 189 | checked: Render.getConfig("DrawOverlayTransparentBounds")["enabled"] | 186 | checked: Render.getConfig("DrawOverlayTransparentBounds")["enabled"] |
| 190 | onCheckedChanged: { Render.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked } | 187 | onCheckedChanged: { Render.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked } |
| 191 | } | 188 | } |
| 189 | } | ||
| 190 | Column { | ||
| 191 | CheckBox { | ||
| 192 | text: "Metas" | ||
| 193 | checked: Render.getConfig("DrawMetaBounds")["enabled"] | ||
| 194 | onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked } | ||
| 195 | } | ||
| 196 | CheckBox { | ||
| 197 | text: "Lights" | ||
| 198 | checked: Render.getConfig("DrawLightBounds")["enabled"] | ||
| 199 | onCheckedChanged: { Render.getConfig("DrawLightBounds")["enabled"] = checked; } | ||
| 200 | } | ||
| 192 | CheckBox { | 201 | CheckBox { |
| 193 | text: "Zones" | 202 | text: "Zones" |
| 194 | checked: Render.getConfig("DrawZones")["enabled"] | 203 | checked: Render.getConfig("DrawZones")["enabled"] |
| 195 | onCheckedChanged: { Render.getConfig("ZoneRenderer")["enabled"] = checked; Render.getConfig("DrawZones")["enabled"] = checked; } | 204 | onCheckedChanged: { Render.getConfig("ZoneRenderer")["enabled"] = checked; Render.getConfig("DrawZones")["enabled"] = checked; } |
| 196 | } | 205 | } |
| 197 | } | 206 | } |
| 207 | } | ||
| 198 | } | 208 | } |
| 199 | 209 | ||
| @@ -20,9 +20,9 @@ Item { · 19 unmodified lines | |||
| 20 | id: stats | 20 | id: stats |
| 21 | spacing: 8 | 21 | spacing: 8 |
| 22 | anchors.fill:parent | 22 | anchors.fill:parent |
| 23 | 23 | ||
| 24 | property var config: Render.getConfig("Stats") | 24 | property var config: Render.getConfig("Stats") |
| 25 | 25 | ||
| 26 | function evalEvenHeight() { | 26 | function evalEvenHeight() { |
| 27 | // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? | 27 | // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? |
| 28 | return (height - spacing * (children.length - 1)) / children.length | 28 | return (height - spacing * (children.length - 1)) / children.length |
| @@ -81,7 +81,7 @@ Item { · 52 unmodified lines | |||
| 81 | color: "#1AC567" | 81 | color: "#1AC567" |
| 82 | }, | 82 | }, |
| 83 | { | 83 | { |
| 84 | prop: "textureGPUTransferCount", | 84 | prop: "texturePendingGPUTransferCount", |
| 85 | label: "Transfer", | 85 | label: "Transfer", |
| 86 | color: "#9495FF" | 86 | color: "#9495FF" |
| 87 | } | 87 | } |
| @@ -158,7 +158,7 @@ Item { · 70 unmodified lines | |||
| 158 | } | 158 | } |
| 159 | ] | 159 | ] |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | PlotPerf { | 162 | PlotPerf { |
| 163 | title: "State Changes" | 163 | title: "State Changes" |
| 164 | height: parent.evalEvenHeight() | 164 | height: parent.evalEvenHeight() |
| @@ -180,7 +180,7 @@ Item { · 15 unmodified lines | |||
| 180 | color: "#1AC567" | 180 | color: "#1AC567" |
| 181 | } | 181 | } |
| 182 | ] | 182 | ] |
| 183 | } | 183 | } |
| 184 | 184 | ||
| 185 | property var drawOpaqueConfig: Render.getConfig("DrawOpaqueDeferred") | 185 | property var drawOpaqueConfig: Render.getConfig("DrawOpaqueDeferred") |
| 186 | property var drawTransparentConfig: Render.getConfig("DrawTransparentDeferred") | 186 | property var drawTransparentConfig: Render.getConfig("DrawTransparentDeferred") |
| @@ -211,7 +211,7 @@ Item { · 24 unmodified lines | |||
| 211 | color: "#FED959" | 211 | color: "#FED959" |
| 212 | } | 212 | } |
| 213 | ] | 213 | ] |
| 214 | } | 214 | } |
| 215 | 215 | ||
| 216 | PlotPerf { | 216 | PlotPerf { |
| 217 | title: "Timing" | 217 | title: "Timing" |
| @@ -250,4 +250,3 @@ Item { · 32 unmodified lines | |||
| 250 | } | 250 | } |
| 251 | 251 | ||
| 252 | } | 252 | } |
| 253 | |||
Binary file not shown.
Binary file not shown.
| @@ -10,7 +10,7 @@ · 9 unmodified lines | |||
| 10 | // Distributed under the Apache License, Version 2.0. | 10 | // Distributed under the Apache License, Version 2.0. |
| 11 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 11 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 12 | // | 12 | // |
| 13 | /* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ | 13 | /* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ |
| 14 | 14 | ||
| 15 | (function () { // BEGIN LOCAL_SCOPE | 15 | (function () { // BEGIN LOCAL_SCOPE |
| 16 | var button; | 16 | var button; |
| @@ -76,6 +76,7 @@ · 59 unmodified lines | |||
| 76 | // Called from the C++ scripting interface to show the bubble overlay | 76 | // Called from the C++ scripting interface to show the bubble overlay |
| 77 | function enteredIgnoreRadius() { | 77 | function enteredIgnoreRadius() { |
| 78 | createOverlays(); | 78 | createOverlays(); |
| 79 | UserActivityLogger.bubbleActivated(); | ||
| 79 | } | 80 | } |
| 80 | 81 | ||
| 81 | // Used to set the state of the bubble HUD button | 82 | // Used to set the state of the bubble HUD button |
| @@ -139,10 +140,14 @@ · 57 unmodified lines | |||
| 139 | } | 140 | } |
| 140 | 141 | ||
| 141 | // When the space bubble is toggled... | 142 | // When the space bubble is toggled... |
| 142 | function onBubbleToggled() { | 143 | // NOTE: the c++ calls this with just the first param -- we added a second |
| 143 | var bubbleActive = Users.getIgnoreRadiusEnabled(); | 144 | // just for not logging the initial state of the bubble when we startup. |
| 144 | writeButtonProperties(bubbleActive); | 145 | function onBubbleToggled(enabled, doNotLog) { |
| 145 | if (bubbleActive) { | 146 | writeButtonProperties(enabled); |
| 147 | if (doNotLog !== true) { | ||
| 148 | UserActivityLogger.bubbleToggled(enabled); | ||
| 149 | } | ||
| 150 | if (enabled) { | ||
| 146 | createOverlays(); | 151 | createOverlays(); |
| 147 | } else { | 152 | } else { |
| 148 | hideOverlays(); | 153 | hideOverlays(); |
| @@ -163,7 +168,7 @@ · 14 unmodified lines | |||
| 163 | sortOrder: 4 | 168 | sortOrder: 4 |
| 164 | }); | 169 | }); |
| 165 | 170 | ||
| 166 | onBubbleToggled(); | 171 | onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table |
| 167 | 172 | ||
| 168 | button.clicked.connect(Users.toggleIgnoreRadius); | 173 | button.clicked.connect(Users.toggleIgnoreRadius); |
| 169 | Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); | 174 | Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); |
| @@ -9,7 +9,7 @@ · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | (function() { | 10 | (function() { |
| 11 | 11 | ||
| 12 | var webPageURL = "ChatPage.html"; // URL of tablet web page. | 12 | var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page. |
| 13 | var randomizeWebPageURL = true; // Set to true for debugging. | 13 | var randomizeWebPageURL = true; // Set to true for debugging. |
| 14 | var lastWebPageURL = ""; // Last random URL of tablet web page. | 14 | var lastWebPageURL = ""; // Last random URL of tablet web page. |
| 15 | var onChatPage = false; // True when chat web page is opened. | 15 | var onChatPage = false; // True when chat web page is opened. |
| @@ -3334,7 +3334,8 @@ function MyController(hand) { · 3333 unmodified lines | |||
| 3334 | // If both secondary triggers squeezed, and the non-holding hand is empty, start scaling | 3334 | // If both secondary triggers squeezed, and the non-holding hand is empty, start scaling |
| 3335 | if (this.secondarySqueezed() && | 3335 | if (this.secondarySqueezed() && |
| 3336 | this.getOtherHandController().secondarySqueezed() && | 3336 | this.getOtherHandController().secondarySqueezed() && |
| 3337 | this.getOtherHandController().state === STATE_OFF) { | 3337 | this.grabbedThingID && this.getOtherHandController().grabbedThingID && |
| 3338 | this.grabbedThingID == this.getOtherHandController().grabbedThingID) { | ||
| 3338 | this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), | 3339 | this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), |
| 3339 | this.getOtherHandController().getHandPosition())); | 3340 | this.getOtherHandController().getHandPosition())); |
| 3340 | this.scalingStartDimensions = props.dimensions; | 3341 | this.scalingStartDimensions = props.dimensions; |
| @@ -366,45 +366,25 @@ function Teleporter() { · 365 unmodified lines | |||
| 366 | } | 366 | } |
| 367 | 367 | ||
| 368 | // related to repositioning the avatar after you teleport | 368 | // related to repositioning the avatar after you teleport |
| 369 | var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; | ||
| 370 | var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; | ||
| 369 | function getAvatarFootOffset() { | 371 | function getAvatarFootOffset() { |
| 370 | var data = getJointData(); | ||
| 371 | var upperLeg, lowerLeg, foot, toe, toeTop; | ||
| 372 | data.forEach(function(d) { | ||
| 373 | |||
| 374 | var jointName = d.joint; | ||
| 375 | if (jointName === "RightUpLeg") { | ||
| 376 | upperLeg = d.translation.y; | ||
| 377 | } else if (jointName === "RightLeg") { | ||
| 378 | lowerLeg = d.translation.y; | ||
| 379 | } else if (jointName === "RightFoot") { | ||
| 380 | foot = d.translation.y; | ||
| 381 | } else if (jointName === "RightToeBase") { | ||
| 382 | toe = d.translation.y; | ||
| 383 | } else if (jointName === "RightToe_End") { | ||
| 384 | toeTop = d.translation.y; | ||
| 385 | } | ||
| 386 | }); | ||
| 387 | |||
| 388 | var offset = upperLeg + lowerLeg + foot + toe + toeTop; | ||
| 389 | offset = offset / 100; | ||
| 390 | return offset; | ||
| 391 | } | ||
| 392 | 372 | ||
| 393 | function getJointData() { | 373 | // find a valid foot jointIndex |
| 394 | var allJointData = []; | 374 | var footJointIndex = -1; |
| 395 | var jointNames = MyAvatar.jointNames; | 375 | var i, l = FOOT_JOINT_NAMES.length; |
| 396 | jointNames.forEach(function(joint, index) { | 376 | for (i = 0; i < l; i++) { |
| 397 | var translation = MyAvatar.getJointTranslation(index); | 377 | footJointIndex = MyAvatar.getJointIndex(FOOT_JOINT_NAMES[i]); |
| 398 | var rotation = MyAvatar.getJointRotation(index); | 378 | if (footJointIndex != -1) { |
| 399 | allJointData.push({ | 379 | break; |
| 400 | joint: joint, | 380 | } |
| 401 | index: index, | 381 | } |
| 402 | translation: translation, | 382 | if (footJointIndex != -1) { |
| 403 | rotation: rotation | 383 | // default vertical offset from foot to avatar root. |
| 404 | }); | 384 | return -MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex).y; |
| 405 | }); | 385 | } else { |
| 406 | 386 | return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale; | |
| 407 | return allJointData; | 387 | } |
| 408 | } | 388 | } |
| 409 | 389 | ||
| 410 | var leftPad = new ThumbPad('left'); | 390 | var leftPad = new ThumbPad('left'); |
| @@ -12,7 +12,7 @@ · 11 unmodified lines | |||
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 13 | // | 13 | // |
| 14 | 14 | ||
| 15 | /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, | 15 | /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, |
| 16 | Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ | 16 | Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ |
| 17 | 17 | ||
| 18 | (function() { // BEGIN LOCAL_SCOPE | 18 | (function() { // BEGIN LOCAL_SCOPE |
| @@ -80,27 +80,7 @@ selectionManager.addEventListener(function () { · 61 unmodified lines | |||
| 80 | } | 80 | } |
| 81 | var type = Entities.getEntityProperties(selectedEntityID, "type").type; | 81 | var type = Entities.getEntityProperties(selectedEntityID, "type").type; |
| 82 | if (type === "ParticleEffect") { | 82 | if (type === "ParticleEffect") { |
| 83 | // Destroy the old particles web view first | 83 | selectParticleEntity(selectedEntityID); |
| 84 | particleExplorerTool.destroyWebView(); | ||
| 85 | particleExplorerTool.createWebView(); | ||
| 86 | var properties = Entities.getEntityProperties(selectedEntityID); | ||
| 87 | var particleData = { | ||
| 88 | messageType: "particle_settings", | ||
| 89 | currentProperties: properties | ||
| 90 | }; | ||
| 91 | selectedParticleEntityID = selectedEntityID; | ||
| 92 | particleExplorerTool.setActiveParticleEntity(selectedParticleEntityID); | ||
| 93 | |||
| 94 | particleExplorerTool.webView.webEventReceived.connect(function (data) { | ||
| 95 | data = JSON.parse(data); | ||
| 96 | if (data.messageType === "page_loaded") { | ||
| 97 | particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); | ||
| 98 | } | ||
| 99 | }); | ||
| 100 | |||
| 101 | // Switch to particle explorer | ||
| 102 | var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); | ||
| 103 | tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}}); | ||
| 104 | } else { | 84 | } else { |
| 105 | needToDestroyParticleExplorer = true; | 85 | needToDestroyParticleExplorer = true; |
| 106 | } | 86 | } |
| @@ -217,6 +197,32 @@ function hideMarketplace() { · 110 unmodified lines | |||
| 217 | // } | 197 | // } |
| 218 | // } | 198 | // } |
| 219 | 199 | ||
| 200 | function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { | ||
| 201 | // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original | ||
| 202 | // position in the given direction. | ||
| 203 | var CORNERS = [ | ||
| 204 | { x: 0, y: 0, z: 0 }, | ||
| 205 | { x: 0, y: 0, z: 1 }, | ||
| 206 | { x: 0, y: 1, z: 0 }, | ||
| 207 | { x: 0, y: 1, z: 1 }, | ||
| 208 | { x: 1, y: 0, z: 0 }, | ||
| 209 | { x: 1, y: 0, z: 1 }, | ||
| 210 | { x: 1, y: 1, z: 0 }, | ||
| 211 | { x: 1, y: 1, z: 1 }, | ||
| 212 | ]; | ||
| 213 | |||
| 214 | // Go through all corners and find least (most negative) distance in front of position. | ||
| 215 | var distance = 0; | ||
| 216 | for (var i = 0, length = CORNERS.length; i < length; i++) { | ||
| 217 | var cornerVector = | ||
| 218 | Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); | ||
| 219 | var cornerDistance = Vec3.dot(cornerVector, direction); | ||
| 220 | distance = Math.min(cornerDistance, distance); | ||
| 221 | } | ||
| 222 | position = Vec3.sum(Vec3.multiply(distance, direction), position); | ||
| 223 | return position; | ||
| 224 | } | ||
| 225 | |||
| 220 | var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); | 226 | var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); |
| 221 | var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; | 227 | var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; |
| 222 | var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; | 228 | var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; |
| @@ -234,6 +240,32 @@ var toolBar = (function () { · 11 unmodified lines | |||
| 234 | var position = getPositionToCreateEntity(); | 240 | var position = getPositionToCreateEntity(); |
| 235 | var entityID = null; | 241 | var entityID = null; |
| 236 | if (position !== null && position !== undefined) { | 242 | if (position !== null && position !== undefined) { |
| 243 | var direction; | ||
| 244 | if (Camera.mode === "entity" || Camera.mode === "independent") { | ||
| 245 | direction = Camera.orientation; | ||
| 246 | } else { | ||
| 247 | direction = MyAvatar.orientation; | ||
| 248 | } | ||
| 249 | direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); | ||
| 250 | |||
| 251 | var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; | ||
| 252 | if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { | ||
| 253 | // Adjust position of entity per bounding box prior to creating it. | ||
| 254 | var registration = properties.registration; | ||
| 255 | if (registration === undefined) { | ||
| 256 | var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; | ||
| 257 | registration = DEFAULT_REGISTRATION; | ||
| 258 | } | ||
| 259 | |||
| 260 | var orientation = properties.orientation; | ||
| 261 | if (orientation === undefined) { | ||
| 262 | var DEFAULT_ORIENTATION = Quat.fromPitchYawRollDegrees(0, 0, 0); | ||
| 263 | orientation = DEFAULT_ORIENTATION; | ||
| 264 | } | ||
| 265 | |||
| 266 | position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); | ||
| 267 | } | ||
| 268 | |||
| 237 | position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); | 269 | position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); |
| 238 | properties.position = position; | 270 | properties.position = position; |
| 239 | if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) { | 271 | if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) { |
| @@ -243,6 +275,32 @@ var toolBar = (function () { · 3 unmodified lines | |||
| 243 | if (properties.type == "ParticleEffect") { | 275 | if (properties.type == "ParticleEffect") { |
| 244 | selectParticleEntity(entityID); | 276 | selectParticleEntity(entityID); |
| 245 | } | 277 | } |
| 278 | |||
| 279 | var POST_ADJUST_ENTITY_TYPES = ["Model"]; | ||
| 280 | if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { | ||
| 281 | // Adjust position of entity per bounding box after it has been created and auto-resized. | ||
| 282 | var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; | ||
| 283 | var DIMENSIONS_CHECK_INTERVAL = 200; | ||
| 284 | var MAX_DIMENSIONS_CHECKS = 10; | ||
| 285 | var dimensionsCheckCount = 0; | ||
| 286 | var dimensionsCheckFunction = function () { | ||
| 287 | dimensionsCheckCount++; | ||
| 288 | var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); | ||
| 289 | if (!Vec3.equal(properties.dimensions, initialDimensions)) { | ||
| 290 | position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, | ||
| 291 | properties.dimensions, properties.rotation); | ||
| 292 | position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), | ||
| 293 | properties.dimensions); | ||
| 294 | Entities.editEntity(entityID, { | ||
| 295 | position: position | ||
| 296 | }); | ||
| 297 | selectionManager._update(); | ||
| 298 | } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { | ||
| 299 | Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); | ||
| 300 | } | ||
| 301 | }; | ||
| 302 | Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); | ||
| 303 | } | ||
| 246 | } else { | 304 | } else { |
| 247 | Window.notifyEditError("Can't create " + properties.type + ": " + | 305 | Window.notifyEditError("Can't create " + properties.type + ": " + |
| 248 | properties.type + " would be out of bounds."); | 306 | properties.type + " would be out of bounds."); |
| @@ -540,7 +598,6 @@ var toolBar = (function () { · 291 unmodified lines | |||
| 540 | 598 | ||
| 541 | that.toggle = function () { | 599 | that.toggle = function () { |
| 542 | that.setActive(!isActive); | 600 | that.setActive(!isActive); |
| 543 | activeButton.editProperties({isActive: isActive}); | ||
| 544 | if (!isActive) { | 601 | if (!isActive) { |
| 545 | tablet.gotoHomeScreen(); | 602 | tablet.gotoHomeScreen(); |
| 546 | } | 603 | } |
| @@ -564,6 +621,10 @@ var toolBar = (function () { · 17 unmodified lines | |||
| 564 | enabled: active | 621 | enabled: active |
| 565 | })); | 622 | })); |
| 566 | isActive = active; | 623 | isActive = active; |
| 624 | activeButton.editProperties({isActive: isActive}); | ||
| 625 | |||
| 626 | var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); | ||
| 627 | |||
| 567 | if (!isActive) { | 628 | if (!isActive) { |
| 568 | entityListTool.setVisible(false); | 629 | entityListTool.setVisible(false); |
| 569 | gridTool.setVisible(false); | 630 | gridTool.setVisible(false); |
| @@ -572,8 +633,8 @@ var toolBar = (function () { · 2 unmodified lines | |||
| 572 | selectionManager.clearSelections(); | 633 | selectionManager.clearSelections(); |
| 573 | cameraManager.disable(); | 634 | cameraManager.disable(); |
| 574 | selectionDisplay.triggerMapping.disable(); | 635 | selectionDisplay.triggerMapping.disable(); |
| 636 | tablet.landscape = false; | ||
| 575 | } else { | 637 | } else { |
| 576 | var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); | ||
| 577 | tablet.loadQMLSource("Edit.qml"); | 638 | tablet.loadQMLSource("Edit.qml"); |
| 578 | UserActivityLogger.enabledEdit(); | 639 | UserActivityLogger.enabledEdit(); |
| 579 | entityListTool.setVisible(true); | 640 | entityListTool.setVisible(true); |
| @@ -581,6 +642,8 @@ var toolBar = (function () { · 1 unmodified line | |||
| 581 | grid.setEnabled(true); | 642 | grid.setEnabled(true); |
| 582 | propertiesTool.setVisible(true); | 643 | propertiesTool.setVisible(true); |
| 583 | selectionDisplay.triggerMapping.enable(); | 644 | selectionDisplay.triggerMapping.enable(); |
| 645 | print("starting tablet in landscape mode") | ||
| 646 | tablet.landscape = true; | ||
| 584 | // Not sure what the following was meant to accomplish, but it currently causes | 647 | // Not sure what the following was meant to accomplish, but it currently causes |
| 585 | // everybody else to think that Interface has lost focus overall. fogbugzid:558 | 648 | // everybody else to think that Interface has lost focus overall. fogbugzid:558 |
| 586 | // Window.setFocus(); | 649 | // Window.setFocus(); |
| @@ -1290,7 +1353,7 @@ function parentSelectedEntities() { · 703 unmodified lines | |||
| 1290 | } | 1353 | } |
| 1291 | }); | 1354 | }); |
| 1292 | 1355 | ||
| 1293 | if(parentCheck) { | 1356 | if (parentCheck) { |
| 1294 | Window.notify("Entities parented"); | 1357 | Window.notify("Entities parented"); |
| 1295 | }else { | 1358 | }else { |
| 1296 | Window.notify("Entities are already parented to last"); | 1359 | Window.notify("Entities are already parented to last"); |
| @@ -1407,40 +1470,26 @@ function handeMenuEvent(menuItem) { · 110 unmodified lines | |||
| 1407 | } | 1470 | } |
| 1408 | tooltip.show(false); | 1471 | tooltip.show(false); |
| 1409 | } | 1472 | } |
| 1410 | function getPositionToCreateEntity() { | ||
| 1411 | var HALF_TREE_SCALE = 16384; | ||
| 1412 | var direction = Quat.getForward(MyAvatar.orientation); | ||
| 1413 | var distance = 1; | ||
| 1414 | var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance)); | ||
| 1415 | |||
| 1416 | if (Camera.mode === "entity" || Camera.mode === "independent") { | ||
| 1417 | position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), distance)); | ||
| 1418 | } | ||
| 1419 | position.y += 0.5; | ||
| 1420 | if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { | ||
| 1421 | return null; | ||
| 1422 | } | ||
| 1423 | return position; | ||
| 1424 | } | ||
| 1425 | 1473 | ||
| 1426 | function getPositionToImportEntity() { | 1474 | var HALF_TREE_SCALE = 16384; |
| 1427 | var dimensions = Clipboard.getContentsDimensions(); | ||
| 1428 | var HALF_TREE_SCALE = 16384; | ||
| 1429 | var direction = Quat.getForward(MyAvatar.orientation); | ||
| 1430 | var longest = 1; | ||
| 1431 | longest = Math.sqrt(Math.pow(dimensions.x, 2) + Math.pow(dimensions.z, 2)); | ||
| 1432 | var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, longest)); | ||
| 1433 | 1475 | ||
| 1476 | function getPositionToCreateEntity(extra) { | ||
| 1477 | var CREATE_DISTANCE = 2; | ||
| 1478 | var position; | ||
| 1479 | var delta = extra !== undefined ? extra : 0; | ||
| 1434 | if (Camera.mode === "entity" || Camera.mode === "independent") { | 1480 | if (Camera.mode === "entity" || Camera.mode === "independent") { |
| 1435 | position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), longest)); | 1481 | position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); |
| 1482 | } else { | ||
| 1483 | position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); | ||
| 1484 | position.y += 0.5; | ||
| 1436 | } | 1485 | } |
| 1437 | 1486 | ||
| 1438 | if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { | 1487 | if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { |
| 1439 | return null; | 1488 | return null; |
| 1440 | } | 1489 | } |
| 1441 | |||
| 1442 | return position; | 1490 | return position; |
| 1443 | } | 1491 | } |
| 1492 | |||
| 1444 | function importSVO(importURL) { | 1493 | function importSVO(importURL) { |
| 1445 | if (!Entities.canRez() && !Entities.canRezTmp()) { | 1494 | if (!Entities.canRez() && !Entities.canRezTmp()) { |
| 1446 | Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); | 1495 | Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); |
| @@ -1458,22 +1507,73 @@ function importSVO(importURL) { · 11 unmodified lines | |||
| 1458 | 1507 | ||
| 1459 | if (success) { | 1508 | if (success) { |
| 1460 | var VERY_LARGE = 10000; | 1509 | var VERY_LARGE = 10000; |
| 1461 | var position = { | 1510 | var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; |
| 1462 | x: 0, | 1511 | var position = Vec3.ZERO; |
| 1463 | y: 0, | 1512 | if (!isLargeImport) { |
| 1464 | z: 0 | 1513 | position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); |
| 1465 | }; | ||
| 1466 | if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) { | ||
| 1467 | position = getPositionToImportEntity(); | ||
| 1468 | } | 1514 | } |
| 1469 | if (position !== null && position !== undefined) { | 1515 | if (position !== null && position !== undefined) { |
| 1470 | var pastedEntityIDs = Clipboard.pasteEntities(position); | 1516 | var pastedEntityIDs = Clipboard.pasteEntities(position); |
| 1517 | if (!isLargeImport) { | ||
| 1518 | // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move | ||
| 1519 | // entities after they're imported so that they're all the correct distance in front of and with geometric mean | ||
| 1520 | // centered on the avatar/camera direction. | ||
| 1521 | var deltaPosition = Vec3.ZERO; | ||
| 1522 | |||
| 1523 | var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]); | ||
| 1524 | var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; | ||
| 1525 | if (NO_ADJUST_ENTITY_TYPES.indexOf(properties.type) === -1) { | ||
| 1526 | var targetDirection; | ||
| 1527 | if (Camera.mode === "entity" || Camera.mode === "independent") { | ||
| 1528 | targetDirection = Camera.orientation; | ||
| 1529 | } else { | ||
| 1530 | targetDirection = MyAvatar.orientation; | ||
| 1531 | } | ||
| 1532 | targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); | ||
| 1533 | |||
| 1534 | var targetPosition = getPositionToCreateEntity(); | ||
| 1535 | var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. | ||
| 1536 | var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. | ||
| 1537 | var entityPositions = []; | ||
| 1538 | for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { | ||
| 1539 | var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", | ||
| 1540 | "registrationPoint", "rotation"]); | ||
| 1541 | var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, | ||
| 1542 | properties.registrationPoint, properties.dimensions, properties.rotation); | ||
| 1543 | var delta = Vec3.subtract(adjustedPosition, properties.position); | ||
| 1544 | var distance = Vec3.dot(delta, targetDirection); | ||
| 1545 | deltaParallel = Math.min(distance, deltaParallel); | ||
| 1546 | deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), | ||
| 1547 | deltaPerpendicular); | ||
| 1548 | entityPositions[i] = properties.position; | ||
| 1549 | } | ||
| 1550 | deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); | ||
| 1551 | deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); | ||
| 1552 | } | ||
| 1553 | |||
| 1554 | if (grid.getSnapToGrid()) { | ||
| 1555 | var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", | ||
| 1556 | "registrationPoint"]); | ||
| 1557 | var position = Vec3.sum(deltaPosition, properties.position); | ||
| 1558 | position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, | ||
| 1559 | properties.registrationPoint), properties.dimensions, properties.registrationPoint); | ||
| 1560 | deltaPosition = Vec3.subtract(position, properties.position); | ||
| 1561 | } | ||
| 1562 | |||
| 1563 | if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { | ||
| 1564 | for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { | ||
| 1565 | Entities.editEntity(pastedEntityIDs[i], { | ||
| 1566 | position: Vec3.sum(deltaPosition, entityPositions[i]) | ||
| 1567 | }); | ||
| 1568 | } | ||
| 1569 | } | ||
| 1570 | } | ||
| 1471 | 1571 | ||
| 1472 | if (isActive) { | 1572 | if (isActive) { |
| 1473 | selectionManager.setSelections(pastedEntityIDs); | 1573 | selectionManager.setSelections(pastedEntityIDs); |
| 1474 | } | 1574 | } |
| 1475 | } else { | 1575 | } else { |
| 1476 | Window.notifyEditError("Can't import objects: objects would be out of bounds."); | 1576 | Window.notifyEditError("Can't import entities: entities would be out of bounds."); |
| 1477 | } | 1577 | } |
| 1478 | } else { | 1578 | } else { |
| 1479 | Window.notifyEditError("There was an error importing the entity file."); | 1579 | Window.notifyEditError("There was an error importing the entity file."); |
| @@ -1787,11 +1887,11 @@ var PropertiesTool = function (opts) { · 307 unmodified lines | |||
| 1787 | } | 1887 | } |
| 1788 | pushCommandForSelections(); | 1888 | pushCommandForSelections(); |
| 1789 | selectionManager._update(); | 1889 | selectionManager._update(); |
| 1790 | } else if(data.type === 'parent') { | 1890 | } else if (data.type === 'parent') { |
| 1791 | parentSelectedEntities(); | 1891 | parentSelectedEntities(); |
| 1792 | } else if(data.type === 'unparent') { | 1892 | } else if (data.type === 'unparent') { |
| 1793 | unparentSelectedEntities(); | 1893 | unparentSelectedEntities(); |
| 1794 | } else if(data.type === 'saveUserData'){ | 1894 | } else if (data.type === 'saveUserData'){ |
| 1795 | //the event bridge and json parsing handle our avatar id string differently. | 1895 | //the event bridge and json parsing handle our avatar id string differently. |
| 1796 | var actualID = data.id.split('"')[1]; | 1896 | var actualID = data.id.split('"')[1]; |
| 1797 | Entities.editEntity(actualID, data.properties); | 1897 | Entities.editEntity(actualID, data.properties); |
| @@ -2083,6 +2183,10 @@ var selectedParticleEntityID = null; · 285 unmodified lines | |||
| 2083 | 2183 | ||
| 2084 | function selectParticleEntity(entityID) { | 2184 | function selectParticleEntity(entityID) { |
| 2085 | var properties = Entities.getEntityProperties(entityID); | 2185 | var properties = Entities.getEntityProperties(entityID); |
| 2186 | |||
| 2187 | if (properties.emitOrientation) { | ||
| 2188 | properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); | ||
| 2189 | } | ||
| 2086 | var particleData = { | 2190 | var particleData = { |
| 2087 | messageType: "particle_settings", | 2191 | messageType: "particle_settings", |
| 2088 | currentProperties: properties | 2192 | currentProperties: properties |
| @@ -2092,6 +2196,7 @@ function selectParticleEntity(entityID) { · 3 unmodified lines | |||
| 2092 | 2196 | ||
| 2093 | selectedParticleEntity = entityID; | 2197 | selectedParticleEntity = entityID; |
| 2094 | particleExplorerTool.setActiveParticleEntity(entityID); | 2198 | particleExplorerTool.setActiveParticleEntity(entityID); |
| 2199 | |||
| 2095 | particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); | 2200 | particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); |
| 2096 | 2201 | ||
| 2097 | // Switch to particle explorer | 2202 | // Switch to particle explorer |
| @@ -2100,10 +2205,16 @@ function selectParticleEntity(entityID) { · 2 unmodified lines | |||
| 2100 | } | 2205 | } |
| 2101 | 2206 | ||
| 2102 | entityListTool.webView.webEventReceived.connect(function (data) { | 2207 | entityListTool.webView.webEventReceived.connect(function (data) { |
| 2103 | data = JSON.parse(data); | 2208 | try { |
| 2209 | data = JSON.parse(data); | ||
| 2210 | } catch(e) { | ||
| 2211 | print("edit.js: Error parsing JSON: " + e.name + " data " + data) | ||
| 2212 | return; | ||
| 2213 | } | ||
| 2214 | |||
| 2104 | if (data.type === 'parent') { | 2215 | if (data.type === 'parent') { |
| 2105 | parentSelectedEntities(); | 2216 | parentSelectedEntities(); |
| 2106 | } else if(data.type === 'unparent') { | 2217 | } else if (data.type === 'unparent') { |
| 2107 | unparentSelectedEntities(); | 2218 | unparentSelectedEntities(); |
| 2108 | } else if (data.type === "selectionUpdate") { | 2219 | } else if (data.type === "selectionUpdate") { |
| 2109 | var ids = data.entityIds; | 2220 | var ids = data.entityIds; |
| @@ -2124,4 +2235,3 @@ entityListTool.webView.webEventReceived.connect(function (data) { · 14 unmodified lines | |||
| 2124 | }); | 2235 | }); |
| 2125 | 2236 | ||
| 2126 | }()); // END LOCAL_SCOPE | 2237 | }()); // END LOCAL_SCOPE |
| 2127 | |||
File renamed without changes.
| @@ -28,7 +28,11 @@ · 27 unmodified lines | |||
| 28 | </form> | 28 | </form> |
| 29 | </div> | 29 | </div> |
| 30 | <input type="button" id="snap-button" onclick="takeSnapshot()" /> | 30 | <input type="button" id="snap-button" onclick="takeSnapshot()" /> |
| 31 | <div id="snap-settings-right"></div> | 31 | <div id="snap-settings-right"> |
| 32 | <button type="image" class="blueButton" id="print-button" onclick="printToPolaroid()"> | ||
| 33 | <div id="print-icon" class="print-icon print-icon-default"> | ||
| 34 | </button> | ||
| 35 | </div> | ||
| 32 | </div> | 36 | </div> |
| 33 | </body> | 37 | </body> |
| 34 | </html> | 38 | </html> |
| @@ -286,6 +286,43 @@ input[type=button].naked:active { · 285 unmodified lines | |||
| 286 | // END styling of snapshot controls (bottom panel) and its contents | 286 | // END styling of snapshot controls (bottom panel) and its contents |
| 287 | */ | 287 | */ |
| 288 | 288 | ||
| 289 | |||
| 290 | /* | ||
| 291 | // START polaroid styling | ||
| 292 | */ | ||
| 293 | |||
| 294 | #print-button { | ||
| 295 | width: 72px; | ||
| 296 | height: 72px; | ||
| 297 | margin-left: 30px; | ||
| 298 | margin-top: -10px; | ||
| 299 | box-sizing: content-box; | ||
| 300 | display: inline; | ||
| 301 | outline:none; | ||
| 302 | } | ||
| 303 | |||
| 304 | .print-icon { | ||
| 305 | margin: auto; | ||
| 306 | } | ||
| 307 | |||
| 308 | .print-icon-default { | ||
| 309 | background: url(../img/button-snap-print.svg) no-repeat; | ||
| 310 | margin-right: -1px; | ||
| 311 | width: 64px; | ||
| 312 | height: 64px; | ||
| 313 | } | ||
| 314 | |||
| 315 | .print-icon-loading { | ||
| 316 | background: url(../img/loader.gif) no-repeat; | ||
| 317 | width: 32px; | ||
| 318 | height: 32px; | ||
| 319 | } | ||
| 320 | |||
| 321 | /* | ||
| 322 | // END polaroid styling | ||
| 323 | */ | ||
| 324 | |||
| 325 | |||
| 289 | /* | 326 | /* |
| 290 | // START misc styling | 327 | // START misc styling |
| 291 | */ | 328 | */ |
| @@ -610,6 +610,7 @@ hr { · 609 unmodified lines | |||
| 610 | .dropdown dl[dropped="true"] { | 610 | .dropdown dl[dropped="true"] { |
| 611 | color: #404040; | 611 | color: #404040; |
| 612 | background: linear-gradient(#afafaf, #afafaf); | 612 | background: linear-gradient(#afafaf, #afafaf); |
| 613 | z-index: 998; | ||
| 613 | } | 614 | } |
| 614 | 615 | ||
| 615 | .dropdown dt { | 616 | .dropdown dt { |
| @@ -657,7 +658,8 @@ hr { · 41 unmodified lines | |||
| 657 | font-family: FiraSans-SemiBold; | 658 | font-family: FiraSans-SemiBold; |
| 658 | font-size: 15px; | 659 | font-size: 15px; |
| 659 | color: #404040; | 660 | color: #404040; |
| 660 | background-color: #afafaf | 661 | background-color: #afafaf; |
| 662 | z-index: 999; | ||
| 661 | } | 663 | } |
| 662 | .dropdown li:hover { | 664 | .dropdown li:hover { |
| 663 | background-color: #00b4ef; | 665 | background-color: #00b4ef; |
| @@ -172,4 +172,4 @@ input[type=radio]:active + label > span > span{ · 171 unmodified lines | |||
| 172 | } | 172 | } |
| 173 | .blueButton:disabled { | 173 | .blueButton:disabled { |
| 174 | background-image: linear-gradient(#FFFFFF, #AFAFAF); | 174 | background-image: linear-gradient(#FFFFFF, #AFAFAF); |
| 175 | } | 175 | } |
| @@ -503,7 +503,7 @@ div.jsoneditor-contextmenu-root { · 502 unmodified lines | |||
| 503 | div.jsoneditor-contextmenu { | 503 | div.jsoneditor-contextmenu { |
| 504 | position: absolute; | 504 | position: absolute; |
| 505 | box-sizing: content-box; | 505 | box-sizing: content-box; |
| 506 | z-index: 99999; | 506 | z-index: 998; |
| 507 | } | 507 | } |
| 508 | 508 | ||
| 509 | div.jsoneditor-contextmenu ul, | 509 | div.jsoneditor-contextmenu ul, |
| @@ -51,6 +51,9 @@ · 50 unmodified lines | |||
| 51 | <option value="Hexagon">Hexagon</option> | 51 | <option value="Hexagon">Hexagon</option> |
| 52 | <option value="Triangle">Triangle</option> | 52 | <option value="Triangle">Triangle</option> |
| 53 | <option value="Octagon">Octagon</option> | 53 | <option value="Octagon">Octagon</option> |
| 54 | <option value="Cylinder">Cylinder</option> | ||
| 55 | <option value="Cone">Cone</option> | ||
| 56 | <option value="Circle">Circle</option> | ||
| 54 | </select> | 57 | </select> |
| 55 | </div> | 58 | </div> |
| 56 | <div class="property text"> | 59 | <div class="property text"> |
| @@ -0,0 +1,25 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | ||
| 3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||
| 4 | viewBox="0 0 199.8 231" style="enable-background:new 0 0 199.8 231;" xml:space="preserve"> | ||
| 5 | <style type="text/css"> | ||
| 6 | .st0{fill:#FFFFFF;} | ||
| 7 | </style> | ||
| 8 | <g> | ||
| 9 | <g> | ||
| 10 | <path class="st0" d="M146.7,145.3H48V50.1h98.8V145.3z M56.2,122.8h83.1V57.6H56.2V122.8z"/> | ||
| 11 | <circle class="st0" cx="96.8" cy="83.5" r="10.9"/> | ||
| 12 | <path class="st0" d="M116.9,112.8h-40v-1.2c0-6.5,5.9-11.8,12.5-11.8H105c6.5,0,11.8,5.3,11.8,11.8V112.8z"/> | ||
| 13 | <polygon class="st0" points="155.5,80.6 155.5,125.7 170.9,39.2 74.6,19.6 70.3,43.7 78.3,43.7 81,28.8 161.7,45.6 "/> | ||
| 14 | </g> | ||
| 15 | <g> | ||
| 16 | <path class="st0" d="M12.4,213.7v-29.8h14c6.5,0,10,4.4,10,9.6c0,5.2-3.6,9.6-10,9.6h-7.6v10.6H12.4z M29.9,193.4 | ||
| 17 | c0-2.5-1.9-4-4.4-4h-6.7v8h6.7C28,197.4,29.9,195.9,29.9,193.4z"/> | ||
| 18 | <path class="st0" d="M69.9,213.7L64,203.1h-4.7v10.6H53v-29.8h14c6.2,0,10.1,4.1,10.1,9.6c0,5.2-3.4,8.1-6.6,8.9l6.8,11.4H69.9z | ||
| 19 | M70.5,193.4c0-2.5-1.9-4-4.4-4h-6.7v8.1h6.7C68.6,197.5,70.5,195.9,70.5,193.4z"/> | ||
| 20 | <path class="st0" d="M94.4,213.7v-29.8h6.4v29.8H94.4z"/> | ||
| 21 | <path class="st0" d="M139.8,213.7l-14.2-19.5v19.5h-6.4v-29.8h6.5l13.8,18.7v-18.7h6.4v29.8H139.8z"/> | ||
| 22 | <path class="st0" d="M171.4,213.7v-24.2h-8.7v-5.6h23.8v5.6h-8.7v24.2H171.4z"/> | ||
| 23 | </g> | ||
| 24 | </g> | ||
| 25 | </svg> | ||
| @@ -21,6 +21,11 @@ var blastShareText = "Blast to my Connections", · 20 unmodified lines | |||
| 21 | hifiAlreadySharedText = "Already Shared to Snaps Feed", | 21 | hifiAlreadySharedText = "Already Shared to Snaps Feed", |
| 22 | facebookShareText = "Share to Facebook", | 22 | facebookShareText = "Share to Facebook", |
| 23 | twitterShareText = "Share to Twitter"; | 23 | twitterShareText = "Share to Twitter"; |
| 24 | |||
| 25 | function fileExtensionMatches(filePath, extension) { | ||
| 26 | return filePath.split('.').pop().toLowerCase() === extension; | ||
| 27 | } | ||
| 28 | |||
| 24 | function showSetupInstructions() { | 29 | function showSetupInstructions() { |
| 25 | var snapshotImagesDiv = document.getElementById("snapshot-images"); | 30 | var snapshotImagesDiv = document.getElementById("snapshot-images"); |
| 26 | snapshotImagesDiv.className = "snapshotInstructions"; | 31 | snapshotImagesDiv.className = "snapshotInstructions"; |
| @@ -276,10 +281,10 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi · 249 unmodified lines | |||
| 276 | if (!image_data.localPath) { | 281 | if (!image_data.localPath) { |
| 277 | return; | 282 | return; |
| 278 | } | 283 | } |
| 279 | var id = "p" + (idCounter++), | 284 | var imageContainer = document.createElement("DIV"), |
| 280 | imageContainer = document.createElement("DIV"), | ||
| 281 | img = document.createElement("IMG"), | 285 | img = document.createElement("IMG"), |
| 282 | isGif; | 286 | isGif = fileExtensionMatches(image_data.localPath, "gif"), |
| 287 | id = "p" + (isGif ? "1" : "0"); | ||
| 283 | imageContainer.id = id; | 288 | imageContainer.id = id; |
| 284 | imageContainer.style.width = "95%"; | 289 | imageContainer.style.width = "95%"; |
| 285 | imageContainer.style.height = "240px"; | 290 | imageContainer.style.height = "240px"; |
| @@ -290,22 +295,34 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi · 4 unmodified lines | |||
| 290 | imageContainer.style.position = "relative"; | 295 | imageContainer.style.position = "relative"; |
| 291 | img.id = id + "img"; | 296 | img.id = id + "img"; |
| 292 | img.src = image_data.localPath; | 297 | img.src = image_data.localPath; |
| 293 | isGif = img.src.split('.').pop().toLowerCase() === "gif"; | ||
| 294 | imageContainer.appendChild(img); | 298 | imageContainer.appendChild(img); |
| 295 | document.getElementById("snapshot-images").appendChild(imageContainer); | 299 | document.getElementById("snapshot-images").appendChild(imageContainer); |
| 296 | paths.push(image_data.localPath); | 300 | paths.push(image_data.localPath); |
| 297 | if (isGif) { | 301 | img.onload = function () { |
| 298 | imageContainer.innerHTML += '<span class="gifLabel">GIF</span>'; | 302 | if (isGif) { |
| 299 | } | 303 | imageContainer.innerHTML += '<span class="gifLabel">GIF</span>'; |
| 300 | if (!isGifLoading) { | 304 | } |
| 301 | appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); | 305 | if (!isGifLoading) { |
| 302 | } | 306 | appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); |
| 303 | if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) { | 307 | } |
| 304 | shareForUrl(id); | 308 | if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) { |
| 305 | } | 309 | shareForUrl(id); |
| 306 | if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { | 310 | } |
| 307 | updateShareInfo(id, image_data.story_id); | 311 | if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { |
| 308 | } | 312 | updateShareInfo(id, image_data.story_id); |
| 313 | } | ||
| 314 | if (isShowingPreviousImages) { | ||
| 315 | requestPrintButtonUpdate(); | ||
| 316 | } | ||
| 317 | }; | ||
| 318 | img.onerror = function () { | ||
| 319 | img.onload = null; | ||
| 320 | img.src = image_data.errorPath; | ||
| 321 | EventBridge.emitWebEvent(JSON.stringify({ | ||
| 322 | type: "snapshot", | ||
| 323 | action: "alertSnapshotLoadFailed" | ||
| 324 | })); | ||
| 325 | }; | ||
| 309 | } | 326 | } |
| 310 | function showConfirmationMessage(selectedID, destination) { | 327 | function showConfirmationMessage(selectedID, destination) { |
| 311 | if (selectedID.id) { | 328 | if (selectedID.id) { |
| @@ -632,9 +649,8 @@ window.onload = function () { · 320 unmodified lines | |||
| 632 | // The last element of the message contents list contains a bunch of options, | 649 | // The last element of the message contents list contains a bunch of options, |
| 633 | // including whether or not we can share stuff | 650 | // including whether or not we can share stuff |
| 634 | // The other elements of the list contain image paths. | 651 | // The other elements of the list contain image paths. |
| 635 | 652 | if (messageOptions.containsGif === true) { | |
| 636 | if (messageOptions.containsGif) { | 653 | if (messageOptions.processingGif === true) { |
| 637 | if (messageOptions.processingGif) { | ||
| 638 | imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon | 654 | imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon |
| 639 | message.image_data.push({ localPath: messageOptions.loadingGifPath }); | 655 | message.image_data.push({ localPath: messageOptions.loadingGifPath }); |
| 640 | message.image_data.forEach(function (element, idx) { | 656 | message.image_data.forEach(function (element, idx) { |
| @@ -661,10 +677,22 @@ window.onload = function () { · 20 unmodified lines | |||
| 661 | break; | 677 | break; |
| 662 | case 'captureSettings': | 678 | case 'captureSettings': |
| 663 | handleCaptureSetting(message.setting); | 679 | handleCaptureSetting(message.setting); |
| 680 | break; | ||
| 681 | case 'setPrintButtonEnabled': | ||
| 682 | setPrintButtonEnabled(); | ||
| 683 | break; | ||
| 684 | case 'setPrintButtonLoading': | ||
| 685 | setPrintButtonLoading(); | ||
| 686 | break; | ||
| 687 | case 'setPrintButtonDisabled': | ||
| 688 | setPrintButtonDisabled(); | ||
| 664 | break; | 689 | break; |
| 665 | case 'snapshotUploadComplete': | 690 | case 'snapshotUploadComplete': |
| 666 | var isGif = message.image_url.split('.').pop().toLowerCase() === "gif"; | 691 | var isGif = fileExtensionMatches(message.image_url, "gif"); |
| 667 | updateShareInfo(isGif ? "p1" : "p0", message.story_id); | 692 | updateShareInfo(isGif ? "p1" : "p0", message.story_id); |
| 693 | if (isPrintProcessing()) { | ||
| 694 | setPrintButtonEnabled(); | ||
| 695 | } | ||
| 668 | break; | 696 | break; |
| 669 | default: | 697 | default: |
| 670 | console.log("Unknown message action received in SnapshotReview.js."); | 698 | console.log("Unknown message action received in SnapshotReview.js."); |
| @@ -694,6 +722,59 @@ function takeSnapshot() { · 23 unmodified lines | |||
| 694 | } | 722 | } |
| 695 | } | 723 | } |
| 696 | 724 | ||
| 725 | function isPrintDisabled() { | ||
| 726 | var printElement = document.getElementById('print-icon'); | ||
| 727 | |||
| 728 | return printElement.classList.contains("print-icon") && | ||
| 729 | printElement.classList.contains("print-icon-default") && | ||
| 730 | document.getElementById('print-button').disabled; | ||
| 731 | } | ||
| 732 | function isPrintProcessing() { | ||
| 733 | var printElement = document.getElementById('print-icon'); | ||
| 734 | |||
| 735 | return printElement.classList.contains("print-icon") && | ||
| 736 | printElement.classList.contains("print-icon-loading") && | ||
| 737 | document.getElementById('print-button').disabled; | ||
| 738 | } | ||
| 739 | function isPrintEnabled() { | ||
| 740 | var printElement = document.getElementById('print-icon'); | ||
| 741 | |||
| 742 | return printElement.classList.contains("print-icon") && | ||
| 743 | printElement.classList.contains("print-icon-default") && | ||
| 744 | !document.getElementById('print-button').disabled; | ||
| 745 | } | ||
| 746 | |||
| 747 | function setPrintButtonLoading() { | ||
| 748 | document.getElementById('print-icon').className = "print-icon print-icon-loading"; | ||
| 749 | document.getElementById('print-button').disabled = true; | ||
| 750 | } | ||
| 751 | function setPrintButtonDisabled() { | ||
| 752 | document.getElementById('print-icon').className = "print-icon print-icon-default"; | ||
| 753 | document.getElementById('print-button').disabled = true; | ||
| 754 | } | ||
| 755 | function setPrintButtonEnabled() { | ||
| 756 | document.getElementById('print-button').disabled = false; | ||
| 757 | document.getElementById('print-icon').className = "print-icon print-icon-default"; | ||
| 758 | } | ||
| 759 | |||
| 760 | function requestPrintButtonUpdate() { | ||
| 761 | EventBridge.emitWebEvent(JSON.stringify({ | ||
| 762 | type: "snapshot", | ||
| 763 | action: "requestPrintButtonUpdate" | ||
| 764 | })); | ||
| 765 | } | ||
| 766 | |||
| 767 | function printToPolaroid() { | ||
| 768 | if (isPrintEnabled()) { | ||
| 769 | EventBridge.emitWebEvent(JSON.stringify({ | ||
| 770 | type: "snapshot", | ||
| 771 | action: "printToPolaroid" | ||
| 772 | })); | ||
| 773 | } else { | ||
| 774 | setPrintButtonLoading(); | ||
| 775 | } | ||
| 776 | } | ||
| 777 | |||
| 697 | function testInBrowser(test) { | 778 | function testInBrowser(test) { |
| 698 | if (test === 0) { | 779 | if (test === 0) { |
| 699 | showSetupInstructions(); | 780 | showSetupInstructions(); |
| @@ -20,7 +20,8 @@ var ICON_FOR_TYPE = { · 19 unmodified lines | |||
| 20 | Light: "p", | 20 | Light: "p", |
| 21 | Zone: "o", | 21 | Zone: "o", |
| 22 | PolyVox: "", | 22 | PolyVox: "", |
| 23 | Multiple: "" | 23 | Multiple: "", |
| 24 | PolyLine: "" | ||
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | var EDITOR_TIMEOUT_DURATION = 1500; | 27 | var EDITOR_TIMEOUT_DURATION = 1500; |
| @@ -82,11 +83,23 @@ function showElements(els, show) { · 55 unmodified lines | |||
| 82 | } | 83 | } |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 86 | function updateProperty(propertyName, propertyValue) { | ||
| 87 | var properties = {}; | ||
| 88 | properties[propertyName] = propertyValue; | ||
| 89 | updateProperties(properties); | ||
| 90 | } | ||
| 91 | |||
| 92 | function updateProperties(properties) { | ||
| 93 | EventBridge.emitWebEvent(JSON.stringify({ | ||
| 94 | id: lastEntityID, | ||
| 95 | type: "update", | ||
| 96 | properties: properties | ||
| 97 | })); | ||
| 98 | } | ||
| 99 | |||
| 85 | function createEmitCheckedPropertyUpdateFunction(propertyName) { | 100 | function createEmitCheckedPropertyUpdateFunction(propertyName) { |
| 86 | return function() { | 101 | return function() { |
| 87 | EventBridge.emitWebEvent( | 102 | updateProperty(propertyName, this.checked); |
| 88 | '{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + this.checked + '}}' | ||
| 89 | ); | ||
| 90 | }; | 103 | }; |
| 91 | } | 104 | } |
| 92 | 105 | ||
| @@ -105,13 +118,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { · 12 unmodified lines | |||
| 105 | var properties = {}; | 118 | var properties = {}; |
| 106 | properties[group] = {}; | 119 | properties[group] = {}; |
| 107 | properties[group][propertyName] = this.checked; | 120 | properties[group][propertyName] = this.checked; |
| 108 | EventBridge.emitWebEvent( | 121 | updateProperties(properties); |
| 109 | JSON.stringify({ | ||
| 110 | id: lastEntityID, | ||
| 111 | type: "update", | ||
| 112 | properties: properties | ||
| 113 | }) | ||
| 114 | ); | ||
| 115 | }; | 122 | }; |
| 116 | } | 123 | } |
| 117 | 124 | ||
| @@ -119,10 +126,7 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { · 1 unmodified line | |||
| 119 | decimals = decimals == undefined ? 4 : decimals; | 126 | decimals = decimals == undefined ? 4 : decimals; |
| 120 | return function() { | 127 | return function() { |
| 121 | var value = parseFloat(this.value).toFixed(decimals); | 128 | var value = parseFloat(this.value).toFixed(decimals); |
| 122 | 129 | updateProperty(propertyName, value); | |
| 123 | EventBridge.emitWebEvent( | ||
| 124 | '{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + value + '}}' | ||
| 125 | ); | ||
| 126 | }; | 130 | }; |
| 127 | } | 131 | } |
| 128 | 132 | ||
| @@ -131,28 +135,14 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { · 2 unmodified lines | |||
| 131 | var properties = {}; | 135 | var properties = {}; |
| 132 | properties[group] = {}; | 136 | properties[group] = {}; |
| 133 | properties[group][propertyName] = this.value; | 137 | properties[group][propertyName] = this.value; |
| 134 | EventBridge.emitWebEvent( | 138 | updateProperties(properties); |
| 135 | JSON.stringify({ | ||
| 136 | id: lastEntityID, | ||
| 137 | type: "update", | ||
| 138 | properties: properties, | ||
| 139 | }) | ||
| 140 | ); | ||
| 141 | }; | 139 | }; |
| 142 | } | 140 | } |
| 143 | 141 | ||
| 144 | 142 | ||
| 145 | function createEmitTextPropertyUpdateFunction(propertyName) { | 143 | function createEmitTextPropertyUpdateFunction(propertyName) { |
| 146 | return function() { | 144 | return function() { |
| 147 | var properties = {}; | 145 | updateProperty(propertyName, this.value); |
| 148 | properties[propertyName] = this.value; | ||
| 149 | EventBridge.emitWebEvent( | ||
| 150 | JSON.stringify({ | ||
| 151 | id: lastEntityID, | ||
| 152 | type: "update", | ||
| 153 | properties: properties, | ||
| 154 | }) | ||
| 155 | ); | ||
| 156 | }; | 146 | }; |
| 157 | } | 147 | } |
| 158 | 148 | ||
| @@ -161,62 +151,44 @@ function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { · 2 unmodified lines | |||
| 161 | var properties = {}; | 151 | var properties = {}; |
| 162 | properties[group] = {}; | 152 | properties[group] = {}; |
| 163 | properties[group][propertyName] = this.value; | 153 | properties[group][propertyName] = this.value; |
| 164 | EventBridge.emitWebEvent( | 154 | updateProperties(properties); |
| 165 | JSON.stringify({ | ||
| 166 | id: lastEntityID, | ||
| 167 | type: "update", | ||
| 168 | properties: properties, | ||
| 169 | }) | ||
| 170 | ); | ||
| 171 | }; | 155 | }; |
| 172 | } | 156 | } |
| 173 | 157 | ||
| 174 | function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { | 158 | function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { |
| 175 | return function() { | 159 | return function() { |
| 176 | var data = { | 160 | var properties = {}; |
| 177 | id: lastEntityID, | 161 | properties[property] = { |
| 178 | type: "update", | ||
| 179 | properties: {} | ||
| 180 | }; | ||
| 181 | data.properties[property] = { | ||
| 182 | x: elX.value, | 162 | x: elX.value, |
| 183 | y: elY.value, | 163 | y: elY.value, |
| 184 | z: elZ.value, | 164 | z: elZ.value, |
| 185 | }; | 165 | }; |
| 186 | EventBridge.emitWebEvent(JSON.stringify(data)); | 166 | updateProperties(properties); |
| 187 | } | 167 | } |
| 188 | }; | 168 | }; |
| 189 | 169 | ||
| 190 | function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { | 170 | function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { |
| 191 | return function() { | 171 | return function() { |
| 192 | var data = { | 172 | var properties = {}; |
| 193 | id: lastEntityID, | 173 | properties[group] = {}; |
| 194 | type: "update", | 174 | properties[group][property] = { |
| 195 | properties: {} | ||
| 196 | }; | ||
| 197 | data.properties[group] = {}; | ||
| 198 | data.properties[group][property] = { | ||
| 199 | x: elX.value, | 175 | x: elX.value, |
| 200 | y: elY.value, | 176 | y: elY.value, |
| 201 | z: elZ ? elZ.value : 0, | 177 | z: elZ ? elZ.value : 0, |
| 202 | }; | 178 | }; |
| 203 | EventBridge.emitWebEvent(JSON.stringify(data)); | 179 | updateProperties(properties); |
| 204 | } | 180 | } |
| 205 | }; | 181 | }; |
| 206 | 182 | ||
| 207 | function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { | 183 | function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { |
| 208 | return function() { | 184 | return function() { |
| 209 | var data = { | 185 | var properties = {}; |
| 210 | id: lastEntityID, | 186 | properties[property] = { |
| 211 | type: "update", | ||
| 212 | properties: {} | ||
| 213 | }; | ||
| 214 | data.properties[property] = { | ||
| 215 | x: elX.value * multiplier, | 187 | x: elX.value * multiplier, |
| 216 | y: elY.value * multiplier, | 188 | y: elY.value * multiplier, |
| 217 | z: elZ.value * multiplier, | 189 | z: elZ.value * multiplier, |
| 218 | }; | 190 | }; |
| 219 | EventBridge.emitWebEvent(JSON.stringify(data)); | 191 | updateProperties(properties); |
| 220 | } | 192 | } |
| 221 | }; | 193 | }; |
| 222 | 194 | ||
| @@ -227,44 +199,35 @@ function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) · 4 unmodified lines | |||
| 227 | }; | 199 | }; |
| 228 | 200 | ||
| 229 | function emitColorPropertyUpdate(property, red, green, blue, group) { | 201 | function emitColorPropertyUpdate(property, red, green, blue, group) { |
| 230 | var data = { | 202 | var properties = {}; |
| 231 | id: lastEntityID, | ||
| 232 | type: "update", | ||
| 233 | properties: {} | ||
| 234 | }; | ||
| 235 | if (group) { | 203 | if (group) { |
| 236 | data.properties[group] = {}; | 204 | properties[group] = {}; |
| 237 | data.properties[group][property] = { | 205 | properties[group][property] = { |
| 238 | red: red, | 206 | red: red, |
| 239 | green: green, | 207 | green: green, |
| 240 | blue: blue, | 208 | blue: blue, |
| 241 | }; | 209 | }; |
| 242 | } else { | 210 | } else { |
| 243 | data.properties[property] = { | 211 | properties[property] = { |
| 244 | red: red, | 212 | red: red, |
| 245 | green: green, | 213 | green: green, |
| 246 | blue: blue, | 214 | blue: blue, |
| 247 | }; | 215 | }; |
| 248 | } | 216 | } |
| 249 | EventBridge.emitWebEvent(JSON.stringify(data)); | 217 | updateProperties(properties); |
| 250 | }; | 218 | }; |
| 251 | 219 | ||
| 252 | 220 | ||
| 253 | function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { | 221 | function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { |
| 254 | return function() { | 222 | return function() { |
| 255 | var data = { | 223 | var properties = {}; |
| 256 | id: lastEntityID, | 224 | properties[group] = {}; |
| 257 | type: "update", | 225 | properties[group][property] = { |
| 258 | properties: {} | ||
| 259 | }; | ||
| 260 | data.properties[group] = {}; | ||
| 261 | |||
| 262 | data.properties[group][property] = { | ||
| 263 | red: elRed.value, | 226 | red: elRed.value, |
| 264 | green: elGreen.value, | 227 | green: elGreen.value, |
| 265 | blue: elBlue.value, | 228 | blue: elBlue.value, |
| 266 | }; | 229 | }; |
| 267 | EventBridge.emitWebEvent(JSON.stringify(data)); | 230 | updateProperties(properties); |
| 268 | } | 231 | } |
| 269 | }; | 232 | }; |
| 270 | 233 | ||
| @@ -277,18 +240,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen · 6 unmodified lines | |||
| 277 | // We've unchecked, so remove | 240 | // We've unchecked, so remove |
| 278 | propertyValue = propertyValue.replace(subPropertyString + ",", ""); | 241 | propertyValue = propertyValue.replace(subPropertyString + ",", ""); |
| 279 | } | 242 | } |
| 280 | 243 | updateProperty(propertyName, propertyValue); | |
| 281 | var _properties = {} | ||
| 282 | _properties[propertyName] = propertyValue; | ||
| 283 | |||
| 284 | EventBridge.emitWebEvent( | ||
| 285 | JSON.stringify({ | ||
| 286 | id: lastEntityID, | ||
| 287 | type: "update", | ||
| 288 | properties: _properties | ||
| 289 | }) | ||
| 290 | ); | ||
| 291 | |||
| 292 | } | 244 | } |
| 293 | 245 | ||
| 294 | function setUserDataFromEditor(noUpdate) { | 246 | function setUserDataFromEditor(noUpdate) { |
| @@ -314,18 +266,11 @@ function setUserDataFromEditor(noUpdate) { · 19 unmodified lines | |||
| 314 | ); | 266 | ); |
| 315 | return; | 267 | return; |
| 316 | } else { | 268 | } else { |
| 317 | EventBridge.emitWebEvent( | 269 | updateProperty('userData', text); |
| 318 | JSON.stringify({ | ||
| 319 | id: lastEntityID, | ||
| 320 | type: "update", | ||
| 321 | properties: { | ||
| 322 | userData: text | ||
| 323 | }, | ||
| 324 | }) | ||
| 325 | ); | ||
| 326 | } | 270 | } |
| 327 | } | 271 | } |
| 328 | } | 272 | } |
| 273 | |||
| 329 | function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { | 274 | function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { |
| 330 | var properties = {}; | 275 | var properties = {}; |
| 331 | var parsedData = {}; | 276 | var parsedData = {}; |
| @@ -372,13 +317,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { · 40 unmodified lines | |||
| 372 | 317 | ||
| 373 | userDataElement.value = properties['userData']; | 318 | userDataElement.value = properties['userData']; |
| 374 | 319 | ||
| 375 | EventBridge.emitWebEvent( | 320 | updateProperties(properties); |
| 376 | JSON.stringify({ | ||
| 377 | id: lastEntityID, | ||
| 378 | type: "update", | ||
| 379 | properties: properties, | ||
| 380 | }) | ||
| 381 | ); | ||
| 382 | } | 321 | } |
| 383 | function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { | 322 | function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { |
| 384 | var val = {}, def = {}; | 323 | var val = {}, def = {}; |
| @@ -900,7 +839,6 @@ function loaded() { · 515 unmodified lines | |||
| 900 | elCloneable.checked = parsedUserData["grabbableKey"].cloneable; | 839 | elCloneable.checked = parsedUserData["grabbableKey"].cloneable; |
| 901 | elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; | 840 | elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; |
| 902 | elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic; | 841 | elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic; |
| 903 | elDynamic.checked = elCloneable.checked ? false: properties.dynamic; | ||
| 904 | if (elCloneable.checked) { | 842 | if (elCloneable.checked) { |
| 905 | if ("cloneLifetime" in parsedUserData["grabbableKey"]) { | 843 | if ("cloneLifetime" in parsedUserData["grabbableKey"]) { |
| 906 | elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300; | 844 | elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300; |
| @@ -1202,8 +1140,8 @@ function loaded() { · 295 unmodified lines | |||
| 1202 | }); | 1140 | }); |
| 1203 | 1141 | ||
| 1204 | elGrabbable.addEventListener('change', function() { | 1142 | elGrabbable.addEventListener('change', function() { |
| 1205 | if(elCloneable.checked) { | 1143 | if (elCloneable.checked) { |
| 1206 | elGrabbable.checked = false; | 1144 | elGrabbable.checked = false; |
| 1207 | } | 1145 | } |
| 1208 | userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); | 1146 | userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); |
| 1209 | }); | 1147 | }); |
| @@ -1213,17 +1151,22 @@ function loaded() { · 3 unmodified lines | |||
| 1213 | elCloneable.addEventListener('change', function (event) { | 1151 | elCloneable.addEventListener('change', function (event) { |
| 1214 | var checked = event.target.checked; | 1152 | var checked = event.target.checked; |
| 1215 | if (checked) { | 1153 | if (checked) { |
| 1216 | multiDataUpdater("grabbableKey", | 1154 | multiDataUpdater("grabbableKey", { |
| 1217 | {cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target}, | 1155 | cloneLifetime: elCloneableLifetime, |
| 1218 | elUserData, {}); | 1156 | cloneLimit: elCloneableLimit, |
| 1157 | cloneDynamic: elCloneableDynamic, | ||
| 1158 | cloneable: event.target, | ||
| 1159 | grabbable: null | ||
| 1160 | }, elUserData, {}); | ||
| 1219 | elCloneableGroup.style.display = "block"; | 1161 | elCloneableGroup.style.display = "block"; |
| 1220 | EventBridge.emitWebEvent( | 1162 | updateProperty('dynamic', false); |
| 1221 | '{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false, "grabbable": false}}' | ||
| 1222 | ); | ||
| 1223 | } else { | 1163 | } else { |
| 1224 | multiDataUpdater("grabbableKey", | 1164 | multiDataUpdater("grabbableKey", { |
| 1225 | {cloneLifetime: null, cloneLimit: null, cloneDynamic: null, cloneable: false}, | 1165 | cloneLifetime: null, |
| 1226 | elUserData, {}); | 1166 | cloneLimit: null, |
| 1167 | cloneDynamic: null, | ||
| 1168 | cloneable: false | ||
| 1169 | }, elUserData, {}); | ||
| 1227 | elCloneableGroup.style.display = "none"; | 1170 | elCloneableGroup.style.display = "none"; |
| 1228 | } | 1171 | } |
| 1229 | }); | 1172 | }); |
| @@ -1258,15 +1201,7 @@ function loaded() { · 28 unmodified lines | |||
| 1258 | showUserDataTextArea(); | 1201 | showUserDataTextArea(); |
| 1259 | showNewJSONEditorButton(); | 1202 | showNewJSONEditorButton(); |
| 1260 | hideSaveUserDataButton(); | 1203 | hideSaveUserDataButton(); |
| 1261 | var properties = {}; | 1204 | updateProperty('userData', elUserData.value) |
| 1262 | properties['userData'] = elUserData.value; | ||
| 1263 | EventBridge.emitWebEvent( | ||
| 1264 | JSON.stringify({ | ||
| 1265 | id: lastEntityID, | ||
| 1266 | type: "update", | ||
| 1267 | properties: properties, | ||
| 1268 | }) | ||
| 1269 | ); | ||
| 1270 | }); | 1205 | }); |
| 1271 | 1206 | ||
| 1272 | elSaveUserData.addEventListener("click", function() { | 1207 | elSaveUserData.addEventListener("click", function() { |
| @@ -13,7 +13,7 @@ var WebChannel; · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | openEventBridge = function(callback) { | 14 | openEventBridge = function(callback) { |
| 15 | WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { | 15 | WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { |
| 16 | EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge; | 16 | EventBridge = WebChannel.objects.eventBridge; |
| 17 | callback(EventBridge); | 17 | callback(EventBridge); |
| 18 | }); | 18 | }); |
| 19 | } | 19 | } |
| @@ -25,9 +25,7 @@ · 24 unmodified lines | |||
| 25 | var canWriteAssets = false; | 25 | var canWriteAssets = false; |
| 26 | var xmlHttpRequest = null; | 26 | var xmlHttpRequest = null; |
| 27 | var isPreparing = false; // Explicitly track download request status. | 27 | var isPreparing = false; // Explicitly track download request status. |
| 28 | 28 | ||
| 29 | var lastPage = "https://metaverse.highfidelity.com/marketplace?"; | ||
| 30 | |||
| 31 | function injectCommonCode(isDirectoryPage) { | 29 | function injectCommonCode(isDirectoryPage) { |
| 32 | 30 | ||
| 33 | // Supporting styles from marketplaces.css. | 31 | // Supporting styles from marketplaces.css. |
| @@ -67,7 +65,7 @@ · 33 unmodified lines | |||
| 67 | 65 | ||
| 68 | // Footer actions. | 66 | // Footer actions. |
| 69 | $("#back-button").on("click", function () { | 67 | $("#back-button").on("click", function () { |
| 70 | window.location = lastPage; | 68 | window.history.back(); |
| 71 | }); | 69 | }); |
| 72 | $("#all-markets").on("click", function () { | 70 | $("#all-markets").on("click", function () { |
| 73 | EventBridge.emitWebEvent(GOTO_DIRECTORY); | 71 | EventBridge.emitWebEvent(GOTO_DIRECTORY); |
| @@ -344,12 +342,7 @@ · 270 unmodified lines | |||
| 344 | } | 342 | } |
| 345 | } | 343 | } |
| 346 | 344 | ||
| 347 | function locationChanged() { | ||
| 348 | lastPage = location.href; | ||
| 349 | } | ||
| 350 | |||
| 351 | // Load / unload. | 345 | // Load / unload. |
| 352 | window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). | 346 | window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). |
| 353 | window.addEventListener("hashchange", locationChanged); | ||
| 354 | 347 | ||
| 355 | }()); | 348 | }()); |
| @@ -22,7 +22,9 @@ var DEFAULT_WIDTH = 0.4375; · 21 unmodified lines | |||
| 22 | var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees | 22 | var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees |
| 23 | var SENSOR_TO_ROOM_MATRIX = -2; | 23 | var SENSOR_TO_ROOM_MATRIX = -2; |
| 24 | var CAMERA_MATRIX = -7; | 24 | var CAMERA_MATRIX = -7; |
| 25 | var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0}; | 25 | var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0}; |
| 26 | var ROT_LANDSCAPE = {x: 1.0, y: 1.0, z: 0, w: 0}; | ||
| 27 | var ROT_LANDSCAPE_WINDOW = {x: 0.0, y: 0.0, z: 0.0, w: 0}; | ||
| 26 | var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1}; | 28 | var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1}; |
| 27 | var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 }; | 29 | var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 }; |
| 28 | var INCHES_TO_METERS = 1 / 39.3701; | 30 | var INCHES_TO_METERS = 1 / 39.3701; |
| @@ -41,7 +43,7 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home- · 12 unmodified lines | |||
| 41 | // returns object with two fields: | 43 | // returns object with two fields: |
| 42 | // * position - position in front of the user | 44 | // * position - position in front of the user |
| 43 | // * rotation - rotation of entity so it faces the user. | 45 | // * rotation - rotation of entity so it faces the user. |
| 44 | function calcSpawnInfo(hand, height) { | 46 | function calcSpawnInfo(hand, tabletHeight) { |
| 45 | var finalPosition; | 47 | var finalPosition; |
| 46 | 48 | ||
| 47 | var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; | 49 | var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; |
| @@ -51,30 +53,35 @@ function calcSpawnInfo(hand, height) { · 3 unmodified lines | |||
| 51 | hand = NO_HANDS; | 53 | hand = NO_HANDS; |
| 52 | } | 54 | } |
| 53 | 55 | ||
| 56 | var handController = null; | ||
| 54 | if (HMD.active && hand !== NO_HANDS) { | 57 | if (HMD.active && hand !== NO_HANDS) { |
| 55 | var handController = getControllerWorldLocation(hand, true); | 58 | handController = getControllerWorldLocation(hand, true); |
| 56 | 59 | } | |
| 57 | var TABLET_UP_OFFSET = 0.1; | 60 | |
| 58 | var TABLET_FORWARD_OFFSET = 0.1; | 61 | if (handController && handController.valid) { |
| 59 | var normal = Vec3.multiplyQbyV(handController.rotation, {x: 0, y: -1, z: 0}); | 62 | // Orient tablet per hand pitch and yaw. |
| 60 | var pitch = Math.asin(normal.y); | 63 | // Angle it back similar to holding it like a book. |
| 61 | var MAX_PITCH = Math.PI / 4; | 64 | // Move tablet up so that hand is at bottom. |
| 62 | if (pitch < -MAX_PITCH) { | 65 | // Move tablet back so that hand is in front. |
| 63 | pitch = -MAX_PITCH; | ||
| 64 | } else if (pitch > MAX_PITCH) { | ||
| 65 | pitch = MAX_PITCH; | ||
| 66 | } | ||
| 67 | 66 | ||
| 68 | // rebuild normal from pitch and heading. | 67 | var position = handController.position; |
| 69 | var heading = Math.atan2(normal.z, normal.x); | 68 | var rotation = handController.rotation; |
| 70 | normal = {x: Math.cos(heading), y: Math.sin(pitch), z: Math.sin(heading)}; | 69 | |
| 70 | if (hand === Controller.Standard.LeftHand) { | ||
| 71 | rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)); | ||
| 72 | } else { | ||
| 73 | rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, -90, 0)); | ||
| 74 | } | ||
| 75 | var normal = Vec3.multiplyQbyV(rotation, Vec3.UNIT_NEG_Y); | ||
| 76 | var lookAt = Quat.lookAt(Vec3.ZERO, normal, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); | ||
| 77 | var TABLET_RAKE_ANGLE = 30; | ||
| 78 | rotation = Quat.multiply(Quat.angleAxis(TABLET_RAKE_ANGLE, Vec3.multiplyQbyV(lookAt, Vec3.UNIT_X)), lookAt); | ||
| 71 | 79 | ||
| 72 | var position = Vec3.sum(handController.position, {x: 0, y: TABLET_UP_OFFSET, z: 0}); | 80 | var RELATIVE_SPAWN_OFFSET = { x: 0, y: 0.4, z: 0.05 }; |
| 73 | var rotation = Quat.lookAt({x: 0, y: 0, z: 0}, normal, Y_AXIS); | 81 | position = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiply(tabletHeight, RELATIVE_SPAWN_OFFSET))); |
| 74 | var offset = Vec3.multiplyQbyV(rotation, {x: 0, y: height / 2, z: TABLET_FORWARD_OFFSET}); | ||
| 75 | 82 | ||
| 76 | return { | 83 | return { |
| 77 | position: Vec3.sum(offset, position), | 84 | position: position, |
| 78 | rotation: rotation | 85 | rotation: rotation |
| 79 | }; | 86 | }; |
| 80 | } else { | 87 | } else { |
| @@ -243,29 +250,29 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { · 162 unmodified lines | |||
| 243 | }; | 250 | }; |
| 244 | 251 | ||
| 245 | WebTablet.prototype.getDimensions = function() { | 252 | WebTablet.prototype.getDimensions = function() { |
| 246 | if (this.landscape) { | 253 | return { x: this.width, y: this.height, z: this.depth }; |
| 247 | return { x: this.width * 2, y: this.height, z: this.depth }; | ||
| 248 | } else { | ||
| 249 | return { x: this.width, y: this.height, z: this.depth }; | ||
| 250 | } | ||
| 251 | }; | 254 | }; |
| 252 | 255 | ||
| 253 | WebTablet.prototype.getTabletTextureResolution = function() { | 256 | WebTablet.prototype.getTabletTextureResolution = function() { |
| 254 | if (this.landscape) { | 257 | if (this.landscape) { |
| 255 | return { x: TABLET_TEXTURE_RESOLUTION.x * 2, y: TABLET_TEXTURE_RESOLUTION.y }; | 258 | return { x: TABLET_TEXTURE_RESOLUTION.y , y: TABLET_TEXTURE_RESOLUTION.x }; |
| 256 | } else { | 259 | } else { |
| 257 | return TABLET_TEXTURE_RESOLUTION; | 260 | return TABLET_TEXTURE_RESOLUTION; |
| 258 | } | 261 | } |
| 259 | }; | 262 | }; |
| 260 | 263 | ||
| 261 | WebTablet.prototype.setLandscape = function(newLandscapeValue) { | 264 | WebTablet.prototype.setLandscape = function(newLandscapeValue) { |
| 262 | if (this.landscape == newLandscapeValue) { | 265 | if (this.landscape === newLandscapeValue) { |
| 263 | return; | 266 | return; |
| 264 | } | 267 | } |
| 268 | |||
| 265 | this.landscape = newLandscapeValue; | 269 | this.landscape = newLandscapeValue; |
| 266 | Overlays.editOverlay(this.tabletEntityID, { dimensions: this.getDimensions() }); | 270 | Overlays.editOverlay(this.tabletEntityID, |
| 271 | { rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : | ||
| 272 | Quat.multiply(Camera.orientation, ROT_Y_180) }); | ||
| 267 | Overlays.editOverlay(this.webOverlayID, { | 273 | Overlays.editOverlay(this.webOverlayID, { |
| 268 | resolution: this.getTabletTextureResolution() | 274 | resolution: this.getTabletTextureResolution(), |
| 275 | rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW) | ||
| 269 | }); | 276 | }); |
| 270 | }; | 277 | }; |
| 271 | 278 | ||
| @@ -407,7 +414,7 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos · 135 unmodified lines | |||
| 407 | 414 | ||
| 408 | return { | 415 | return { |
| 409 | position: worldMousePosition, | 416 | position: worldMousePosition, |
| 410 | rotation: Quat.multiply(Camera.orientation, ROT_Y_180) | 417 | rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : Quat.multiply(Camera.orientation, ROT_Y_180) |
| 411 | }; | 418 | }; |
| 412 | }; | 419 | }; |
| 413 | 420 | ||
| @@ -109,7 +109,13 @@ EntityListTool = function(opts) { · 108 unmodified lines | |||
| 109 | }; | 109 | }; |
| 110 | 110 | ||
| 111 | webView.webEventReceived.connect(function(data) { | 111 | webView.webEventReceived.connect(function(data) { |
| 112 | data = JSON.parse(data); | 112 | try { |
| 113 | data = JSON.parse(data); | ||
| 114 | } catch(e) { | ||
| 115 | print("entityList.js: Error parsing JSON: " + e.name + " data " + data) | ||
| 116 | return; | ||
| 117 | } | ||
| 118 | |||
| 113 | if (data.type == "selectionUpdate") { | 119 | if (data.type == "selectionUpdate") { |
| 114 | var ids = data.entityIds; | 120 | var ids = data.entityIds; |
| 115 | var entityIDs = []; | 121 | var entityIDs = []; |
| @@ -79,7 +79,7 @@ Grid = function(opts) { · 78 unmodified lines | |||
| 79 | } | 79 | } |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | that.snapToSurface = function(position, dimensions) { | 82 | that.snapToSurface = function(position, dimensions, registration) { |
| 83 | if (!snapToGrid) { | 83 | if (!snapToGrid) { |
| 84 | return position; | 84 | return position; |
| 85 | } | 85 | } |
| @@ -88,14 +88,18 @@ Grid = function(opts) { · 2 unmodified lines | |||
| 88 | dimensions = { x: 0, y: 0, z: 0 }; | 88 | dimensions = { x: 0, y: 0, z: 0 }; |
| 89 | } | 89 | } |
| 90 | 90 | ||
| 91 | if (registration === undefined) { | ||
| 92 | registration = { x: 0.5, y: 0.5, z: 0.5 }; | ||
| 93 | } | ||
| 94 | |||
| 91 | return { | 95 | return { |
| 92 | x: position.x, | 96 | x: position.x, |
| 93 | y: origin.y + (dimensions.y / 2), | 97 | y: origin.y + (registration.y * dimensions.y), |
| 94 | z: position.z | 98 | z: position.z |
| 95 | }; | 99 | }; |
| 96 | } | 100 | } |
| 97 | 101 | ||
| 98 | that.snapToGrid = function(position, majorOnly, dimensions) { | 102 | that.snapToGrid = function(position, majorOnly, dimensions, registration) { |
| 99 | if (!snapToGrid) { | 103 | if (!snapToGrid) { |
| 100 | return position; | 104 | return position; |
| 101 | } | 105 | } |
| @@ -104,6 +108,10 @@ Grid = function(opts) { · 2 unmodified lines | |||
| 104 | dimensions = { x: 0, y: 0, z: 0 }; | 108 | dimensions = { x: 0, y: 0, z: 0 }; |
| 105 | } | 109 | } |
| 106 | 110 | ||
| 111 | if (registration === undefined) { | ||
| 112 | registration = { x: 0.5, y: 0.5, z: 0.5 }; | ||
| 113 | } | ||
| 114 | |||
| 107 | var spacing = majorOnly ? majorGridEvery : minorGridEvery; | 115 | var spacing = majorOnly ? majorGridEvery : minorGridEvery; |
| 108 | 116 | ||
| 109 | position = Vec3.subtract(position, origin); | 117 | position = Vec3.subtract(position, origin); |
| @@ -112,7 +120,7 @@ Grid = function(opts) { · 2 unmodified lines | |||
| 112 | position.y = Math.round(position.y / spacing) * spacing; | 120 | position.y = Math.round(position.y / spacing) * spacing; |
| 113 | position.z = Math.round(position.z / spacing) * spacing; | 121 | position.z = Math.round(position.z / spacing) * spacing; |
| 114 | 122 | ||
| 115 | return Vec3.sum(Vec3.sum(position, Vec3.multiply(0.5, dimensions)), origin); | 123 | return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin); |
| 116 | } | 124 | } |
| 117 | 125 | ||
| 118 | that.snapToSpacing = function(delta, majorOnly) { | 126 | that.snapToSpacing = function(delta, majorOnly) { |
| @@ -161,9 +169,9 @@ Grid = function(opts) { · 42 unmodified lines | |||
| 161 | 169 | ||
| 162 | if (data.origin) { | 170 | if (data.origin) { |
| 163 | var pos = data.origin; | 171 | var pos = data.origin; |
| 164 | pos.x = pos.x === undefined ? origin.x : pos.x; | 172 | pos.x = pos.x === undefined ? origin.x : parseFloat(pos.x); |
| 165 | pos.y = pos.y === undefined ? origin.y : pos.y; | 173 | pos.y = pos.y === undefined ? origin.y : parseFloat(pos.y); |
| 166 | pos.z = pos.z === undefined ? origin.z : pos.z; | 174 | pos.z = pos.z === undefined ? origin.z : parseFloat(pos.z); |
| 167 | that.setPosition(pos, true); | 175 | that.setPosition(pos, true); |
| 168 | } | 176 | } |
| 169 | 177 | ||
| @@ -238,7 +246,13 @@ GridTool = function(opts) { · 68 unmodified lines | |||
| 238 | }); | 246 | }); |
| 239 | 247 | ||
| 240 | webView.webEventReceived.connect(function(data) { | 248 | webView.webEventReceived.connect(function(data) { |
| 241 | data = JSON.parse(data); | 249 | try { |
| 250 | data = JSON.parse(data); | ||
| 251 | } catch(e) { | ||
| 252 | print("gridTool.js: Error parsing JSON: " + e.name + " data " + data) | ||
| 253 | return; | ||
| 254 | } | ||
| 255 | |||
| 242 | if (data.type == "init") { | 256 | if (data.type == "init") { |
| 243 | horizontalGrid.emitUpdate(); | 257 | horizontalGrid.emitUpdate(); |
| 244 | } else if (data.type == "update") { | 258 | } else if (data.type == "update") { |
| @@ -613,7 +613,6 @@ · 612 unmodified lines | |||
| 613 | error = "All participants must be logged in to connect."; | 613 | error = "All participants must be logged in to connect."; |
| 614 | } | 614 | } |
| 615 | result = error ? {status: 'error', connection: error} : response; | 615 | result = error ? {status: 'error', connection: error} : response; |
| 616 | UserActivityLogger.makeUserConnection(connectingId, false, error || response); | ||
| 617 | connectionRequestCompleted(); | 616 | connectionRequestCompleted(); |
| 618 | } else { | 617 | } else { |
| 619 | result = response; | 618 | result = response; |
| @@ -668,8 +667,8 @@ · 48 unmodified lines | |||
| 668 | // to be sure the hand is still close enough. If not, we terminate | 667 | // to be sure the hand is still close enough. If not, we terminate |
| 669 | // the interval, go back to the waiting state. If we make it | 668 | // the interval, go back to the waiting state. If we make it |
| 670 | // the entire CONNECTING_TIME, we make the connection. We pass in | 669 | // the entire CONNECTING_TIME, we make the connection. We pass in |
| 671 | // whether or not the connecting id is actually logged in, as now we | 670 | // whether or not the connecting id is actually logged in, as now we |
| 672 | // will allow to start the connection process but have it stop with a | 671 | // will allow to start the connection process but have it stop with a |
| 673 | // fail message before trying to call the backend if the other guy isn't | 672 | // fail message before trying to call the backend if the other guy isn't |
| 674 | // logged in. | 673 | // logged in. |
| 675 | function startConnecting(id, jointIndex, isLoggedIn) { | 674 | function startConnecting(id, jointIndex, isLoggedIn) { |
| @@ -1,95 +0,0 @@ | |||
| 1 | /** | ||
| 2 | * dat-gui JavaScript Controller Library | ||
| 3 | * http://code.google.com/p/dat-gui | ||
| 4 | * | ||
| 5 | * Copyright 2011 Data Arts Team, Google Creative Lab | ||
| 6 | * | ||
| 7 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 8 | * you may not use this file except in compliance with the License. | ||
| 9 | * You may obtain a copy of the License at | ||
| 10 | * | ||
| 11 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 12 | */ | ||
| 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); | ||
| 14 | dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, | ||
| 15 | each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;b<p&&!(b in a&&e.call(c,a[b],b)===this.BREAK);b++);else for(b in a)if(e.call(c,a[b],b)===this.BREAK)break},defer:function(a){setTimeout(a,0)},toArray:function(d){return d.toArray?d.toArray():a.call(d)},isUndefined:function(a){return void 0===a},isNull:function(a){return null===a},isNaN:function(a){return a!==a},isArray:Array.isArray||function(a){return a.constructor===Array}, | ||
| 16 | isObject:function(a){return a===Object(a)},isNumber:function(a){return a===a+0},isString:function(a){return a===a+""},isBoolean:function(a){return!1===a||!0===a},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)}}}(); | ||
| 17 | dat.controllers.Controller=function(f){var a=function(a,e){this.initialValue=a[e];this.domElement=document.createElement("div");this.object=a;this.property=e;this.__onFinishChange=this.__onChange=void 0};f.extend(a.prototype,{onChange:function(a){this.__onChange=a;return this},onFinishChange:function(a){this.__onFinishChange=a;return this},setValue:function(a){this.object[this.property]=a;this.__onChange&&this.__onChange.call(this,a);this.updateDisplay();return this},getValue:function(){return this.object[this.property]}, | ||
| 18 | updateDisplay:function(){return this},isModified:function(){return this.initialValue!==this.getValue()}});return a}(dat.utils.common); | ||
| 19 | dat.dom.dom=function(f){function a(b){if("0"===b||f.isUndefined(b))return 0;b=b.match(e);return f.isNull(b)?0:parseFloat(b[1])}var d={};f.each({HTMLEvents:["change"],MouseEvents:["click","mousemove","mousedown","mouseup","mouseover"],KeyboardEvents:["keydown"]},function(b,a){f.each(b,function(b){d[b]=a})});var e=/(\d+(\.\d+)?)px/,c={makeSelectable:function(b,a){void 0!==b&&void 0!==b.style&&(b.onselectstart=a?function(){return!1}:function(){},b.style.MozUserSelect=a?"auto":"none",b.style.KhtmlUserSelect= | ||
| 20 | a?"auto":"none",b.unselectable=a?"on":"off")},makeFullscreen:function(b,a,c){f.isUndefined(a)&&(a=!0);f.isUndefined(c)&&(c=!0);b.style.position="absolute";a&&(b.style.left=0,b.style.right=0);c&&(b.style.top=0,b.style.bottom=0)},fakeEvent:function(b,a,c,e){c=c||{};var r=d[a];if(!r)throw Error("Event type "+a+" not supported.");var n=document.createEvent(r);switch(r){case "MouseEvents":n.initMouseEvent(a,c.bubbles||!1,c.cancelable||!0,window,c.clickCount||1,0,0,c.x||c.clientX||0,c.y||c.clientY||0,!1, | ||
| 21 | !1,!1,!1,0,null);break;case "KeyboardEvents":r=n.initKeyboardEvent||n.initKeyEvent;f.defaults(c,{cancelable:!0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,keyCode:void 0,charCode:void 0});r(a,c.bubbles||!1,c.cancelable,window,c.ctrlKey,c.altKey,c.shiftKey,c.metaKey,c.keyCode,c.charCode);break;default:n.initEvent(a,c.bubbles||!1,c.cancelable||!0)}f.defaults(n,e);b.dispatchEvent(n)},bind:function(a,e,d,f){a.addEventListener?a.addEventListener(e,d,f||!1):a.attachEvent&&a.attachEvent("on"+e,d);return c}, | ||
| 22 | unbind:function(a,e,d,f){a.removeEventListener?a.removeEventListener(e,d,f||!1):a.detachEvent&&a.detachEvent("on"+e,d);return c},addClass:function(a,e){if(void 0===a.className)a.className=e;else if(a.className!==e){var d=a.className.split(/ +/);-1==d.indexOf(e)&&(d.push(e),a.className=d.join(" ").replace(/^\s+/,"").replace(/\s+$/,""))}return c},removeClass:function(a,e){if(e){if(void 0!==a.className)if(a.className===e)a.removeAttribute("class");else{var d=a.className.split(/ +/),f=d.indexOf(e);-1!= | ||
| 23 | f&&(d.splice(f,1),a.className=d.join(" "))}}else a.className=void 0;return c},hasClass:function(a,c){return(new RegExp("(?:^|\\s+)"+c+"(?:\\s+|$)")).test(a.className)||!1},getWidth:function(b){b=getComputedStyle(b);return a(b["border-left-width"])+a(b["border-right-width"])+a(b["padding-left"])+a(b["padding-right"])+a(b.width)},getHeight:function(b){b=getComputedStyle(b);return a(b["border-top-width"])+a(b["border-bottom-width"])+a(b["padding-top"])+a(b["padding-bottom"])+a(b.height)},getOffset:function(a){var c= | ||
| 24 | {left:0,top:0};if(a.offsetParent){do c.left+=a.offsetLeft,c.top+=a.offsetTop;while(a=a.offsetParent)}return c},isActive:function(a){return a===document.activeElement&&(a.type||a.href)}};return c}(dat.utils.common); | ||
| 25 | dat.controllers.OptionController=function(f,a,d){var e=function(c,b,f){e.superclass.call(this,c,b);var q=this;this.__select=document.createElement("select");if(d.isArray(f)){var l={};d.each(f,function(a){l[a]=a});f=l}d.each(f,function(a,b){var c=document.createElement("option");c.innerHTML=b;c.setAttribute("value",a);q.__select.appendChild(c)});this.updateDisplay();a.bind(this.__select,"change",function(){q.setValue(this.options[this.selectedIndex].value)});this.domElement.appendChild(this.__select)}; | ||
| 26 | e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue());return a},updateDisplay:function(){this.__select.value=this.getValue();return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); | ||
| 27 | dat.controllers.NumberController=function(f,a){function d(a){a=a.toString();return-1<a.indexOf(".")?a.length-a.indexOf(".")-1:0}var e=function(c,b,f){e.superclass.call(this,c,b);f=f||{};this.__min=f.min;this.__max=f.max;this.__step=f.step;a.isUndefined(this.__step)?this.__impliedStep=0==this.initialValue?1:Math.pow(10,Math.floor(Math.log(Math.abs(this.initialValue))/Math.LN10))/10:this.__impliedStep=this.__step;this.__precision=d(this.__impliedStep)};e.superclass=f;a.extend(e.prototype,f.prototype, | ||
| 28 | {setValue:function(a){void 0!==this.__min&&a<this.__min?a=this.__min:void 0!==this.__max&&a>this.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); | ||
| 29 | dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, | ||
| 30 | "blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= | ||
| 31 | this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); | ||
| 32 | dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= | ||
| 33 | document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= | ||
| 34 | (this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); | ||
| 35 | dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); | ||
| 36 | this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); | ||
| 37 | dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& | ||
| 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); | ||
| 39 | dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); | ||
| 40 | dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); | ||
| 41 | return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= | ||
| 42 | a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& | ||
| 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; | ||
| 44 | var b=1<arguments.length?a.toArray(arguments):arguments[0];a.each(c,function(c){if(c.litmus(b))return a.each(c.conversions,function(c,f){d=c.read(b);if(!1===e&&!1!==d)return e=d,d.conversionName=f,d.conversion=c,a.BREAK}),a.BREAK});return e}}(dat.color.toString,dat.utils.common); | ||
| 45 | dat.GUI=dat.gui.GUI=function(f,a,d,e,c,b,p,q,l,r,n,u,A,g,k){function v(a,b,h,d){if(void 0===b[h])throw Error("Object "+b+' has no property "'+h+'"');d.color?b=new n(b,h):(b=[b,h].concat(d.factoryArgs),b=e.apply(a,b));d.before instanceof c&&(d.before=d.before.__li);y(a,b);g.addClass(b.domElement,"c");h=document.createElement("span");g.addClass(h,"property-name");h.innerHTML=b.property;var f=document.createElement("div");f.appendChild(h);f.appendChild(b.domElement);d=w(a,f,d.before);g.addClass(d,m.CLASS_CONTROLLER_ROW); | ||
| 46 | g.addClass(d,typeof b.getValue());t(a,d,b);a.__controllers.push(b);return b}function w(a,b,h){var c=document.createElement("li");b&&c.appendChild(b);h?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function t(a,c,h){h.__li=c;h.__gui=a;k.extend(h,{options:function(b){if(1<arguments.length)return h.remove(),v(a,h.object,h.property,{before:h.__li.nextElementSibling,factoryArgs:[k.toArray(arguments)]});if(k.isArray(b)||k.isObject(b))return h.remove(),v(a,h.object,h.property, | ||
| 47 | {before:h.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){h.__li.firstElementChild.firstElementChild.innerHTML=a;return h},listen:function(){h.__gui.listen(h);return h},remove:function(){h.__gui.remove(h);return h}});if(h instanceof l){var d=new q(h.object,h.property,{min:h.__min,max:h.__max,step:h.__step});k.each(["updateDisplay","onChange","onFinishChange"],function(a){var K=h[a],b=d[a];h[a]=d[a]=function(){var a=Array.prototype.slice.call(arguments);K.apply(h,a);return b.apply(d,a)}}); | ||
| 48 | g.addClass(c,"has-slider");h.domElement.insertBefore(d.domElement,h.domElement.firstElementChild)}else if(h instanceof q){var e=function(b){return k.isNumber(h.__min)&&k.isNumber(h.__max)?(h.remove(),v(a,h.object,h.property,{before:h.__li.nextElementSibling,factoryArgs:[h.__min,h.__max,h.__step]})):b};h.min=k.compose(e,h.min);h.max=k.compose(e,h.max)}else h instanceof b?(g.bind(c,"click",function(){g.fakeEvent(h.__checkbox,"click")}),g.bind(h.__checkbox,"click",function(a){a.stopPropagation()})): | ||
| 49 | h instanceof p?(g.bind(c,"click",function(){g.fakeEvent(h.__button,"click")}),g.bind(c,"mouseover",function(){g.addClass(h.__button,"hover")}),g.bind(c,"mouseout",function(){g.removeClass(h.__button,"hover")})):h instanceof n&&(g.addClass(c,"color"),h.updateDisplay=k.compose(function(a){c.style.borderLeftColor=h.__color.toString();return a},h.updateDisplay),h.updateDisplay());h.setValue=k.compose(function(b){a.getRoot().__preset_select&&h.isModified()&&E(a.getRoot(),!0);return b},h.setValue)}function y(a, | ||
| 50 | b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(-1!=d){var e=c.__rememberedObjectIndecesToControllers[d];void 0===e&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c.Default)c=c.Default;else return;c[d]&&void 0!==c[d][b.property]&&(d=c[d][b.property],b.initialValue=d,b.setValue(d))}}}function L(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement,"has-save"); | ||
| 51 | a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var r=a.__preset_select=document.createElement("select"); | ||
| 52 | a.load&&a.load.remembered?k.each(a.load.remembered,function(b,c){F(a,c,c==a.preset)}):F(a,"Default",!1);g.bind(r,"change",function(){for(var b=0;b<a.__preset_select.length;b++)a.__preset_select[b].innerHTML=a.__preset_select[b].value;a.preset=this.value});b.appendChild(r);b.appendChild(c);b.appendChild(d);b.appendChild(e);b.appendChild(f);if(x){var n=function(){u.style.display=a.useLocalStorage?"block":"none"},b=document.getElementById("dg-save-locally"),u=document.getElementById("dg-local-explain"); | ||
| 53 | b.style.display="block";b=document.getElementById("dg-local-storage");"true"===localStorage.getItem(document.location.href+".isLocal")&&b.setAttribute("checked","checked");n();g.bind(b,"change",function(){a.useLocalStorage=!a.useLocalStorage;n()})}var m=document.getElementById("dg-new-constructor");g.bind(m,"keydown",function(a){!a.metaKey||67!==a.which&&67!=a.keyCode||B.hide()});g.bind(c,"click",function(){m.innerHTML=JSON.stringify(a.getSaveObject(),void 0,2);B.show();m.focus();m.select()});g.bind(d, | ||
| 54 | "click",function(){a.save()});g.bind(e,"click",function(){var b=prompt("Enter a new preset name.");b&&a.saveAs(b)});g.bind(f,"click",function(){a.revert()})}function M(a){function b(f){f.preventDefault();e=f.clientX;g.addClass(a.__closeButton,m.CLASS_DRAG);g.bind(window,"mousemove",c);g.bind(window,"mouseup",d);return!1}function c(b){b.preventDefault();a.width+=e-b.clientX;a.onResize();e=b.clientX;return!1}function d(){g.removeClass(a.__closeButton,m.CLASS_DRAG);g.unbind(window,"mousemove",c);g.unbind(window, | ||
| 55 | "mouseup",d)}a.__resize_handle=document.createElement("div");k.extend(a.__resize_handle.style,{width:"6px",marginLeft:"-3px",height:"200px",cursor:"ew-resize",position:"absolute"});var e;g.bind(a.__resize_handle,"mousedown",b);g.bind(a.__closeButton,"mousedown",b);a.domElement.insertBefore(a.__resize_handle,a.domElement.firstElementChild)}function G(a,b){a.domElement.style.width=b+"px";a.__save_row&&a.autoPlace&&(a.__save_row.style.width=b+"px");a.__closeButton&&(a.__closeButton.style.width=b+"px")} | ||
| 56 | function C(a,b){var c={};k.each(a.__rememberedObjects,function(d,e){var f={};k.each(a.__rememberedObjectIndecesToControllers[e],function(a,c){f[c]=b?a.initialValue:a.getValue()});c[e]=f});return c}function F(a,b,c){var d=document.createElement("option");d.innerHTML=b;d.value=b;a.__preset_select.appendChild(d);c&&(a.__preset_select.selectedIndex=a.__preset_select.length-1)}function E(a,b){var c=a.__preset_select[a.__preset_select.selectedIndex];c.innerHTML=b?c.value+"*":c.value}function H(a){0!=a.length&& | ||
| 57 | u(function(){H(a)});k.each(a,function(a){a.updateDisplay()})}f.inject(d);var x;try{x="localStorage"in window&&null!==window.localStorage}catch(N){x=!1}var B,I=!0,z,D=!1,J=[],m=function(a){function b(){var a=c.getRoot();a.width+=1;k.defer(function(){--a.width})}var c=this;this.domElement=document.createElement("div");this.__ul=document.createElement("ul");this.domElement.appendChild(this.__ul);g.addClass(this.domElement,"dg");this.__folders={};this.__controllers=[];this.__rememberedObjects=[];this.__rememberedObjectIndecesToControllers= | ||
| 58 | [];this.__listening=[];a=a||{};a=k.defaults(a,{autoPlace:!0,width:m.DEFAULT_WIDTH});a=k.defaults(a,{resizable:a.autoPlace,hideable:a.autoPlace});k.isUndefined(a.load)?a.load={preset:"Default"}:a.preset&&(a.load.preset=a.preset);k.isUndefined(a.parent)&&a.hideable&&J.push(this);a.resizable=k.isUndefined(a.parent)&&a.resizable;a.autoPlace&&k.isUndefined(a.scrollable)&&(a.scrollable=!0);var d=x&&"true"===localStorage.getItem(document.location.href+".isLocal"),e;Object.defineProperties(this,{parent:{get:function(){return a.parent}}, | ||
| 59 | scrollable:{get:function(){return a.scrollable}},autoPlace:{get:function(){return a.autoPlace}},preset:{get:function(){return c.parent?c.getRoot().preset:a.load.preset},set:function(b){c.parent?c.getRoot().preset=b:a.load.preset=b;for(b=0;b<this.__preset_select.length;b++)this.__preset_select[b].value==this.preset&&(this.__preset_select.selectedIndex=b);c.revert()}},width:{get:function(){return a.width},set:function(b){a.width=b;G(c,b)}},name:{get:function(){return a.name},set:function(b){a.name= | ||
| 60 | b;r&&(r.innerHTML=a.name)}},closed:{get:function(){return a.closed},set:function(b){a.closed=b;a.closed?g.addClass(c.__ul,m.CLASS_CLOSED):g.removeClass(c.__ul,m.CLASS_CLOSED);this.onResize();c.__closeButton&&(c.__closeButton.innerHTML=b?m.TEXT_OPEN:m.TEXT_CLOSED)}},load:{get:function(){return a.load}},useLocalStorage:{get:function(){return d},set:function(a){x&&((d=a)?g.bind(window,"unload",e):g.unbind(window,"unload",e),localStorage.setItem(document.location.href+".isLocal",a))}}});if(k.isUndefined(a.parent)){a.closed= | ||
| 61 | !1;g.addClass(this.domElement,m.CLASS_MAIN);g.makeSelectable(this.domElement,!1);if(x&&d){c.useLocalStorage=!0;var f=localStorage.getItem(document.location.href+".gui");f&&(a.load=JSON.parse(f))}this.__closeButton=document.createElement("div");this.__closeButton.innerHTML=m.TEXT_CLOSED;g.addClass(this.__closeButton,m.CLASS_CLOSE_BUTTON);this.domElement.appendChild(this.__closeButton);g.bind(this.__closeButton,"click",function(){c.closed=!c.closed})}else{void 0===a.closed&&(a.closed=!0);var r=document.createTextNode(a.name); | ||
| 62 | g.addClass(r,"controller-name");f=w(c,r);g.addClass(this.__ul,m.CLASS_CLOSED);g.addClass(f,"title");g.bind(f,"click",function(a){a.preventDefault();c.closed=!c.closed;return!1});a.closed||(this.closed=!1)}a.autoPlace&&(k.isUndefined(a.parent)&&(I&&(z=document.createElement("div"),g.addClass(z,"dg"),g.addClass(z,m.CLASS_AUTO_PLACE_CONTAINER),document.body.appendChild(z),I=!1),z.appendChild(this.domElement),g.addClass(this.domElement,m.CLASS_AUTO_PLACE)),this.parent||G(c,a.width));g.bind(window,"resize", | ||
| 63 | function(){c.onResize()});g.bind(this.__ul,"webkitTransitionEnd",function(){c.onResize()});g.bind(this.__ul,"transitionend",function(){c.onResize()});g.bind(this.__ul,"oTransitionEnd",function(){c.onResize()});this.onResize();a.resizable&&M(this);this.saveToLocalStorageIfPossible=e=function(){x&&"true"===localStorage.getItem(document.location.href+".isLocal")&&localStorage.setItem(document.location.href+".gui",JSON.stringify(c.getSaveObject()))};c.getRoot();a.parent||b()};m.toggleHide=function(){D= | ||
| 64 | !D;k.each(J,function(a){a.domElement.style.zIndex=D?-999:999;a.domElement.style.opacity=D?0:1})};m.CLASS_AUTO_PLACE="a";m.CLASS_AUTO_PLACE_CONTAINER="ac";m.CLASS_MAIN="main";m.CLASS_CONTROLLER_ROW="cr";m.CLASS_TOO_TALL="taller-than-window";m.CLASS_CLOSED="closed";m.CLASS_CLOSE_BUTTON="close-button";m.CLASS_DRAG="drag";m.DEFAULT_WIDTH=245;m.TEXT_CLOSED="Close Controls";m.TEXT_OPEN="Open Controls";g.bind(window,"keydown",function(a){"text"===document.activeElement.type||72!==a.which&&72!=a.keyCode|| | ||
| 65 | m.toggleHide()},!1);k.extend(m.prototype,{add:function(a,b){return v(this,a,b,{factoryArgs:Array.prototype.slice.call(arguments,2)})},addColor:function(a,b){return v(this,a,b,{color:!0})},remove:function(a){this.__ul.removeChild(a.__li);this.__controllers.splice(this.__controllers.indexOf(a),1);var b=this;k.defer(function(){b.onResize()})},destroy:function(){this.autoPlace&&z.removeChild(this.domElement)},addFolder:function(a){if(void 0!==this.__folders[a])throw Error('You already have a folder in this GUI by the name "'+ | ||
| 66 | a+'"');var b={name:a,parent:this};b.autoPlace=this.autoPlace;this.load&&this.load.folders&&this.load.folders[a]&&(b.closed=this.load.folders[a].closed,b.load=this.load.folders[a]);b=new m(b);this.__folders[a]=b;a=w(this,b.domElement);g.addClass(a,"folder");return b},open:function(){this.closed=!1},close:function(){this.closed=!0},onResize:function(){var a=this.getRoot();if(a.scrollable){var b=g.getOffset(a.__ul).top,c=0;k.each(a.__ul.childNodes,function(b){a.autoPlace&&b===a.__save_row||(c+=g.getHeight(b))}); | ||
| 67 | window.innerHeight-b-20<c?(g.addClass(a.domElement,m.CLASS_TOO_TALL),a.__ul.style.height=window.innerHeight-b-20+"px"):(g.removeClass(a.domElement,m.CLASS_TOO_TALL),a.__ul.style.height="auto")}a.__resize_handle&&k.defer(function(){a.__resize_handle.style.height=a.__ul.offsetHeight+"px"});a.__closeButton&&(a.__closeButton.style.width=a.width+"px")},remember:function(){k.isUndefined(B)&&(B=new A,B.domElement.innerHTML=a);if(this.parent)throw Error("You can only call remember on a top level GUI.");var b= | ||
| 68 | this;k.each(Array.prototype.slice.call(arguments),function(a){0==b.__rememberedObjects.length&&L(b);-1==b.__rememberedObjects.indexOf(a)&&b.__rememberedObjects.push(a)});this.autoPlace&&G(this,this.width)},getRoot:function(){for(var a=this;a.parent;)a=a.parent;return a},getSaveObject:function(){var a=this.load;a.closed=this.closed;0<this.__rememberedObjects.length&&(a.preset=this.preset,a.remembered||(a.remembered={}),a.remembered[this.preset]=C(this));a.folders={};k.each(this.__folders,function(b, | ||
| 69 | c){a.folders[c]=b.getSaveObject()});return a},save:function(){this.load.remembered||(this.load.remembered={});this.load.remembered[this.preset]=C(this);E(this,!1);this.saveToLocalStorageIfPossible()},saveAs:function(a){this.load.remembered||(this.load.remembered={},this.load.remembered.Default=C(this,!0));this.load.remembered[a]=C(this);this.preset=a;F(this,a,!0);this.saveToLocalStorageIfPossible()},revert:function(a){k.each(this.__controllers,function(b){this.getRoot().load.remembered?y(a||this.getRoot(), | ||
| 70 | b):b.setValue(b.initialValue)},this);k.each(this.__folders,function(a){a.revert(a)});a||E(this.getRoot(),!1)},listen:function(a){var b=0==this.__listening.length;this.__listening.push(a);b&&H(this.__listening)}});return m}(dat.utils.css,'<div id="dg-save" class="dg dialogue">\n\n Here\'s the new load parameter for your <code>GUI</code>\'s constructor:\n\n <textarea id="dg-new-constructor"></textarea>\n\n <div id="dg-save-locally">\n\n <input id="dg-local-storage" type="checkbox"/> Automatically save\n values to <code>localStorage</code> on exit.\n\n <div id="dg-local-explain">The values saved to <code>localStorage</code> will\n override those passed to <code>dat.GUI</code>\'s constructor. This makes it\n easier to work incrementally, but <code>localStorage</code> is fragile,\n and your friends may not see the same values you do.\n \n </div>\n \n </div>\n\n</div>', | ||
| 71 | ".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row, <li> */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", | ||
| 72 | dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= | ||
| 73 | function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, | ||
| 74 | f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, | ||
| 75 | dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; | ||
| 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, | ||
| 77 | "mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1<b?b=1:0> | ||
| 78 | b&&(b=0);1<e?e=1:0>e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1<b?b=1:0>b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); | ||
| 79 | this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); | ||
| 80 | this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, | ||
| 81 | {position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", | ||
| 82 | background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, | ||
| 83 | "mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; | ||
| 84 | c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5<this.__color.s?255:0,p=255-l;c.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+"rgb("+l+ | ||
| 85 | ","+l+","+l+")"});this.__hue_knob.style.marginTop=100*(1-this.__color.h/360)+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());c.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+l+","+l+","+l+")",textShadow:this.__input_textShadow+"rgba("+p+","+p+","+p+",.7)"})}});var l=["-moz-","-o-","-webkit-","-ms-",""];return q}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(f,a,d,e){function c(a, | ||
| 86 | b,c){Object.defineProperty(a,b,{get:function(){if("RGB"===this.__state.space)return this.__state[b];p(this,b,c);return this.__state[b]},set:function(a){"RGB"!==this.__state.space&&(p(this,b,c),this.__state.space="RGB");this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if("HSV"===this.__state.space)return this.__state[b];q(this);return this.__state[b]},set:function(a){"HSV"!==this.__state.space&&(q(this),this.__state.space="HSV");this.__state[b]=a}})}function p(b,c,d){if("HEX"=== | ||
| 87 | b.__state.space)b.__state[c]=a.component_from_hex(b.__state.hex,d);else if("HSV"===b.__state.space)e.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function q(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);e.extend(b.__state,{s:c.s,v:c.v});e.isNaN(c.h)?e.isUndefined(b.__state.h)&&(b.__state.h=0):b.__state.h=c.h}var l=function(){this.__state=f.apply(this,arguments);if(!1===this.__state)throw"Failed to interpret color arguments";this.__state.a=this.__state.a|| | ||
| 88 | 1};l.COMPONENTS="r g b h s v hex a".split(" ");e.extend(l.prototype,{toString:function(){return d(this)},toOriginal:function(){return this.__state.conversion.write(this)}});c(l.prototype,"r",2);c(l.prototype,"g",1);c(l.prototype,"b",0);b(l.prototype,"h");b(l.prototype,"s");b(l.prototype,"v");Object.defineProperty(l.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(l.prototype,"hex",{get:function(){"HEX"!==!this.__state.space&&(this.__state.hex= | ||
| 89 | a.rgb_to_hex(this.r,this.g,this.b));return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return l}(dat.color.interpret,dat.color.math=function(){var f;return{hsv_to_rgb:function(a,d,e){var c=a/60-Math.floor(a/60),b=e*(1-d),f=e*(1-c*d);d=e*(1-(1-c)*d);a=[[e,d,b],[f,e,b],[b,e,d],[b,f,e],[d,b,e],[e,b,f]][Math.floor(a/60)%6];return{r:255*a[0],g:255*a[1],b:255*a[2]}},rgb_to_hsv:function(a,d,e){var c=Math.min(a,d,e),b=Math.max(a,d,e),c=b-c;if(0==b)return{h:NaN,s:0,v:0}; | ||
| 90 | a=(a==b?(d-e)/c:d==b?2+(e-a)/c:4+(a-d)/c)/6;0>a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255<<f)}}}(),dat.color.toString,dat.utils.common),dat.color.interpret,dat.utils.common),dat.utils.requestAnimationFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame|| | ||
| 91 | window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(f,a){window.setTimeout(f,1E3/60)}}(),dat.dom.CenteredDiv=function(f,a){var d=function(){this.backgroundElement=document.createElement("div");a.extend(this.backgroundElement.style,{backgroundColor:"rgba(0,0,0,0.8)",top:0,left:0,display:"none",zIndex:"1000",opacity:0,WebkitTransition:"opacity 0.2s linear",transition:"opacity 0.2s linear"});f.makeFullscreen(this.backgroundElement);this.backgroundElement.style.position= | ||
| 92 | "fixed";this.domElement=document.createElement("div");a.extend(this.domElement.style,{position:"fixed",display:"none",zIndex:"1001",opacity:0,WebkitTransition:"-webkit-transform 0.2s ease-out, opacity 0.2s linear",transition:"transform 0.2s ease-out, opacity 0.2s linear"});document.body.appendChild(this.backgroundElement);document.body.appendChild(this.domElement);var d=this;f.bind(this.backgroundElement,"click",function(){d.hide()})};d.prototype.show=function(){var d=this;this.backgroundElement.style.display= | ||
| 93 | "block";this.domElement.style.display="block";this.domElement.style.opacity=0;this.domElement.style.webkitTransform="scale(1.1)";this.layout();a.defer(function(){d.backgroundElement.style.opacity=1;d.domElement.style.opacity=1;d.domElement.style.webkitTransform="scale(1)"})};d.prototype.hide=function(){var a=this,c=function(){a.domElement.style.display="none";a.backgroundElement.style.display="none";f.unbind(a.domElement,"webkitTransitionEnd",c);f.unbind(a.domElement,"transitionend",c);f.unbind(a.domElement, | ||
| 94 | "oTransitionEnd",c)};f.bind(this.domElement,"webkitTransitionEnd",c);f.bind(this.domElement,"transitionend",c);f.bind(this.domElement,"oTransitionEnd",c);this.backgroundElement.style.opacity=0;this.domElement.style.opacity=0;this.domElement.style.webkitTransform="scale(1.1)"};d.prototype.layout=function(){this.domElement.style.left=window.innerWidth/2-f.getWidth(this.domElement)/2+"px";this.domElement.style.top=window.innerHeight/2-f.getHeight(this.domElement)/2+"px"};return d}(dat.dom.dom,dat.utils.common), | ||
| 95 | dat.dom.dom,dat.utils.common); | ||
| @@ -0,0 +1,680 @@ | |||
| 1 | /* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */ | ||
| 2 | /* eslint no-console: 0 */ | ||
| 3 | |||
| 4 | /** | ||
| 5 | UI Builder V1.0 | ||
| 6 | |||
| 7 | Created by Matti 'Menithal' Lahtinen | ||
| 8 | 24/5/2017 | ||
| 9 | Copyright 2017 High Fidelity, Inc. | ||
| 10 | |||
| 11 | This can eventually be expanded to all of Edit, for now, starting | ||
| 12 | with Particles Only. | ||
| 13 | |||
| 14 | This is created for the sole purpose of streamliming the bridge, and to simplify | ||
| 15 | the logic between an inputfield in WebView and Entities in High Fidelity. | ||
| 16 | |||
| 17 | We also do not need anything as heavy as jquery or any other platform, | ||
| 18 | as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling) | ||
| 19 | |||
| 20 | Available Types: | ||
| 21 | |||
| 22 | JSONInputField - Accepts JSON input, once one presses Save, it will be propegated. | ||
| 23 | Button- A Button that listens for a custom event as defined by callback | ||
| 24 | Boolean - Creates a checkbox that the user can either check or uncheck | ||
| 25 | SliderFloat - Creates a slider (with input) that has Float values from min to max. | ||
| 26 | Default is min 0, max 1 | ||
| 27 | SliderInteger - Creates a slider (with input) that has a Integer value from min to max. | ||
| 28 | Default is min 1, max 10000 | ||
| 29 | SliderRadian - Creates a slider (with input) that has Float values in degrees, | ||
| 30 | that are converted to radians. default is min 0, max Math.PI. | ||
| 31 | Texture - Creates a Image with an url input field that points to texture. | ||
| 32 | If image cannot form, show "cannot find image" | ||
| 33 | VecQuaternion - Creates a 3D Vector field that converts to quaternions. | ||
| 34 | Checkbox exists to show quaternions instead. | ||
| 35 | Color - Create field color button, that when pressed, opens the color picker. | ||
| 36 | Vector - Create a 3D Vector field that has one to one correspondence. | ||
| 37 | |||
| 38 | The script will use this structure to build a UI that is connected The | ||
| 39 | id fields within High Fidelity | ||
| 40 | |||
| 41 | This should make editing, and everything related much more simpler to maintain, | ||
| 42 | and If there is any changes to either the Entities or properties of | ||
| 43 | |||
| 44 | **/ | ||
| 45 | |||
| 46 | var RADIANS_PER_DEGREE = Math.PI / 180; | ||
| 47 | |||
| 48 | var roundFloat = function (input, round) { | ||
| 49 | round = round ? round : 1000; | ||
| 50 | var sanitizedInput; | ||
| 51 | if (typeof input === "string") { | ||
| 52 | sanitizedInput = parseFloat(input); | ||
| 53 | } else { | ||
| 54 | sanitizedInput = input; | ||
| 55 | } | ||
| 56 | return Math.round(sanitizedInput * round) / round; | ||
| 57 | }; | ||
| 58 | |||
| 59 | function HifiEntityUI(parent) { | ||
| 60 | this.parent = parent; | ||
| 61 | |||
| 62 | var self = this; | ||
| 63 | this.webBridgeSync = _.debounce(function (id, val) { | ||
| 64 | if (self.EventBridge) { | ||
| 65 | var sendPackage = {}; | ||
| 66 | sendPackage[id] = val; | ||
| 67 | self.submitChanges(sendPackage); | ||
| 68 | } | ||
| 69 | }, 125); | ||
| 70 | } | ||
| 71 | |||
| 72 | HifiEntityUI.prototype = { | ||
| 73 | setOnSelect: function (callback) { | ||
| 74 | this.onSelect = callback; | ||
| 75 | }, | ||
| 76 | submitChanges: function (structure) { | ||
| 77 | var message = { | ||
| 78 | messageType: "settings_update", | ||
| 79 | updatedSettings: structure | ||
| 80 | }; | ||
| 81 | this.EventBridge.emitWebEvent(JSON.stringify(message)); | ||
| 82 | }, | ||
| 83 | setUI: function (structure) { | ||
| 84 | this.structure = structure; | ||
| 85 | }, | ||
| 86 | disableFields: function () { | ||
| 87 | var fields = document.getElementsByTagName("input"); | ||
| 88 | for (var i = 0; i < fields.length; i++) { | ||
| 89 | if (fields[i].getAttribute("type") !== "button") { | ||
| 90 | fields[i].value = ""; | ||
| 91 | } | ||
| 92 | |||
| 93 | fields[i].setAttribute("disabled", true); | ||
| 94 | } | ||
| 95 | var textures = document.getElementsByTagName("img"); | ||
| 96 | for (i = 0; i < textures.length; i++) { | ||
| 97 | textures[i].src = ""; | ||
| 98 | } | ||
| 99 | |||
| 100 | textures = document.getElementsByClassName("with-texture"); | ||
| 101 | for (i = 0; i < textures.length; i++) { | ||
| 102 | textures[i].classList.remove("with-textures"); | ||
| 103 | textures[i].classList.add("no-texture"); | ||
| 104 | } | ||
| 105 | |||
| 106 | var textareas = document.getElementsByTagName("textarea"); | ||
| 107 | for (var x = 0; x < textareas.length; x++) { | ||
| 108 | textareas[x].remove(); | ||
| 109 | } | ||
| 110 | }, | ||
| 111 | getSettings: function () { | ||
| 112 | var self = this; | ||
| 113 | var json = {}; | ||
| 114 | var keys = Object.keys(self.builtRows); | ||
| 115 | |||
| 116 | for (var i = 0; i < keys.length; i++) { | ||
| 117 | var key = keys[i]; | ||
| 118 | var el = self.builtRows[key]; | ||
| 119 | if (el.className.indexOf("checkbox") !== -1) { | ||
| 120 | json[key] = document.getElementById(key) | ||
| 121 | .checked ? true : false; | ||
| 122 | } else if (el.className.indexOf("vector-section") !== -1) { | ||
| 123 | var vector = {}; | ||
| 124 | if (el.className.indexOf("rgb") !== -1) { | ||
| 125 | var red = document.getElementById(key + "-red"); | ||
| 126 | var blue = document.getElementById(key + "-blue"); | ||
| 127 | var green = document.getElementById(key + "-green"); | ||
| 128 | vector.red = red.value; | ||
| 129 | vector.blue = blue.value; | ||
| 130 | vector.green = green.value; | ||
| 131 | } else if (el.className.indexOf("pyr") !== -1) { | ||
| 132 | var p = document.getElementById(key + "-Pitch"); | ||
| 133 | var y = document.getElementById(key + "-Yaw"); | ||
| 134 | var r = document.getElementById(key + "-Roll"); | ||
| 135 | vector.x = p.value; | ||
| 136 | vector.y = y.value; | ||
| 137 | vector.z = r.value; | ||
| 138 | } else { | ||
| 139 | var x = document.getElementById(key + "-x"); | ||
| 140 | var ey = document.getElementById(key + "-y"); | ||
| 141 | var z = document.getElementById(key + "-z"); | ||
| 142 | vector.x = x.value; | ||
| 143 | vector.y = ey.value; | ||
| 144 | vector.z = z.value; | ||
| 145 | } | ||
| 146 | json[key] = vector; | ||
| 147 | } else if (el.className.length > 0) { | ||
| 148 | json[key] = document.getElementById(key) | ||
| 149 | .value; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | return json; | ||
| 154 | }, | ||
| 155 | fillFields: function (currentProperties) { | ||
| 156 | var self = this; | ||
| 157 | var fields = document.getElementsByTagName("input"); | ||
| 158 | |||
| 159 | if (!currentProperties.locked) { | ||
| 160 | for (var i = 0; i < fields.length; i++) { | ||
| 161 | fields[i].removeAttribute("disabled"); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | if (self.onSelect) { | ||
| 165 | self.onSelect(); | ||
| 166 | } | ||
| 167 | var keys = Object.keys(currentProperties); | ||
| 168 | |||
| 169 | |||
| 170 | for (var e in keys) { | ||
| 171 | if (keys.hasOwnProperty(e)) { | ||
| 172 | var value = keys[e]; | ||
| 173 | |||
| 174 | var property = currentProperties[value]; | ||
| 175 | var field = self.builtRows[value]; | ||
| 176 | if (field) { | ||
| 177 | var el = document.getElementById(value); | ||
| 178 | |||
| 179 | if (field.className.indexOf("radian") !== -1) { | ||
| 180 | el.value = property / RADIANS_PER_DEGREE; | ||
| 181 | el.onchange({ | ||
| 182 | target: el | ||
| 183 | }); | ||
| 184 | } else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) { | ||
| 185 | el.value = property; | ||
| 186 | el.onchange({ | ||
| 187 | target: el | ||
| 188 | }); | ||
| 189 | } else if (field.className.indexOf("checkbox") !== -1) { | ||
| 190 | if (property) { | ||
| 191 | el.setAttribute("checked", property); | ||
| 192 | } else { | ||
| 193 | el.removeAttribute("checked"); | ||
| 194 | } | ||
| 195 | } else if (field.className.indexOf("vector-section") !== -1) { | ||
| 196 | if (field.className.indexOf("rgb") !== -1) { | ||
| 197 | var red = document.getElementById(value + "-red"); | ||
| 198 | var blue = document.getElementById(value + "-blue"); | ||
| 199 | var green = document.getElementById(value + "-green"); | ||
| 200 | red.value = parseInt(property.red); | ||
| 201 | blue.value = parseInt(property.blue); | ||
| 202 | green.value = parseInt(property.green); | ||
| 203 | |||
| 204 | red.oninput({ | ||
| 205 | target: red | ||
| 206 | }); | ||
| 207 | } else if (field.className.indexOf("xyz") !== -1) { | ||
| 208 | var x = document.getElementById(value + "-x"); | ||
| 209 | var y = document.getElementById(value + "-y"); | ||
| 210 | var z = document.getElementById(value + "-z"); | ||
| 211 | |||
| 212 | x.value = roundFloat(property.x, 100); | ||
| 213 | y.value = roundFloat(property.y, 100); | ||
| 214 | z.value = roundFloat(property.z, 100); | ||
| 215 | } else if (field.className.indexOf("pyr") !== -1) { | ||
| 216 | var pitch = document.getElementById(value + "-Pitch"); | ||
| 217 | var yaw = document.getElementById(value + "-Yaw"); | ||
| 218 | var roll = document.getElementById(value + "-Roll"); | ||
| 219 | |||
| 220 | pitch.value = roundFloat(property.x, 100); | ||
| 221 | yaw.value = roundFloat(property.y, 100); | ||
| 222 | roll.value = roundFloat(property.z, 100); | ||
| 223 | |||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | }, | ||
| 230 | connect: function (EventBridge) { | ||
| 231 | this.EventBridge = EventBridge; | ||
| 232 | |||
| 233 | var self = this; | ||
| 234 | |||
| 235 | EventBridge.emitWebEvent(JSON.stringify({ | ||
| 236 | messageType: 'page_loaded' | ||
| 237 | })); | ||
| 238 | |||
| 239 | EventBridge.scriptEventReceived.connect(function (data) { | ||
| 240 | data = JSON.parse(data); | ||
| 241 | |||
| 242 | if (data.messageType === 'particle_settings') { | ||
| 243 | // Update settings | ||
| 244 | var currentProperties = data.currentProperties; | ||
| 245 | self.fillFields(currentProperties); | ||
| 246 | // Do expected property match with structure; | ||
| 247 | } else if (data.messageType === 'particle_close') { | ||
| 248 | self.disableFields(); | ||
| 249 | } | ||
| 250 | }); | ||
| 251 | }, | ||
| 252 | build: function () { | ||
| 253 | var self = this; | ||
| 254 | var sections = Object.keys(this.structure); | ||
| 255 | this.builtRows = {}; | ||
| 256 | sections.forEach(function (section, index) { | ||
| 257 | var properties = self.structure[section]; | ||
| 258 | self.addSection(self.parent, section, properties, index); | ||
| 259 | }); | ||
| 260 | }, | ||
| 261 | addSection: function (parent, section, properties, index) { | ||
| 262 | var self = this; | ||
| 263 | |||
| 264 | var sectionDivHeader = document.createElement("div"); | ||
| 265 | var title = document.createElement("label"); | ||
| 266 | var dropDown = document.createElement("span"); | ||
| 267 | |||
| 268 | dropDown.className = "arrow"; | ||
| 269 | sectionDivHeader.className = "section-header"; | ||
| 270 | title.innerHTML = section; | ||
| 271 | sectionDivHeader.appendChild(title); | ||
| 272 | sectionDivHeader.appendChild(dropDown); | ||
| 273 | var collapsed = index !== 0; | ||
| 274 | |||
| 275 | dropDown.innerHTML = collapsed ? "L" : "M"; | ||
| 276 | sectionDivHeader.setAttribute("collapsed", collapsed); | ||
| 277 | parent.appendChild(sectionDivHeader); | ||
| 278 | |||
| 279 | var sectionDivBody = document.createElement("div"); | ||
| 280 | sectionDivBody.className = "property-group"; | ||
| 281 | |||
| 282 | var animationWrapper = document.createElement("div"); | ||
| 283 | animationWrapper.className = "section-wrap"; | ||
| 284 | |||
| 285 | for (var property in properties) { | ||
| 286 | if (properties.hasOwnProperty(property)) { | ||
| 287 | var builtRow = self.addElement(animationWrapper, properties[property]); | ||
| 288 | var id = properties[property].id; | ||
| 289 | if (id) { | ||
| 290 | self.builtRows[id] = builtRow; | ||
| 291 | } | ||
| 292 | } | ||
| 293 | } | ||
| 294 | sectionDivBody.appendChild(animationWrapper); | ||
| 295 | parent.appendChild(sectionDivBody); | ||
| 296 | _.defer(function () { | ||
| 297 | var height = (animationWrapper.clientHeight) + "px"; | ||
| 298 | if (collapsed) { | ||
| 299 | sectionDivBody.classList.remove("visible"); | ||
| 300 | sectionDivBody.style.maxHeight = "0px"; | ||
| 301 | } else { | ||
| 302 | sectionDivBody.classList.add("visible"); | ||
| 303 | sectionDivBody.style.maxHeight = height; | ||
| 304 | } | ||
| 305 | |||
| 306 | sectionDivHeader.onclick = function () { | ||
| 307 | collapsed = !collapsed; | ||
| 308 | if (collapsed) { | ||
| 309 | sectionDivBody.classList.remove("visible"); | ||
| 310 | sectionDivBody.style.maxHeight = "0px"; | ||
| 311 | } else { | ||
| 312 | sectionDivBody.classList.add("visible"); | ||
| 313 | sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px"; | ||
| 314 | } | ||
| 315 | // sectionDivBody.style.display = collapsed ? "none": "block"; | ||
| 316 | dropDown.innerHTML = collapsed ? "L" : "M"; | ||
| 317 | sectionDivHeader.setAttribute("collapsed", collapsed); | ||
| 318 | }; | ||
| 319 | }); | ||
| 320 | }, | ||
| 321 | addLabel: function (parent, group) { | ||
| 322 | var label = document.createElement("label"); | ||
| 323 | label.innerHTML = group.name; | ||
| 324 | parent.appendChild(label); | ||
| 325 | if (group.unit) { | ||
| 326 | var span = document.createElement("span"); | ||
| 327 | span.innerHTML = group.unit; | ||
| 328 | span.className = "unit"; | ||
| 329 | label.appendChild(span); | ||
| 330 | } | ||
| 331 | return label; | ||
| 332 | }, | ||
| 333 | addVector: function (parent, group, labels, domArray) { | ||
| 334 | var self = this; | ||
| 335 | var inputs = labels ? labels : ["x", "y", "z"]; | ||
| 336 | domArray = domArray ? domArray : []; | ||
| 337 | parent.id = group.id; | ||
| 338 | for (var index in inputs) { | ||
| 339 | var element = document.createElement("input"); | ||
| 340 | |||
| 341 | element.setAttribute("type", "number"); | ||
| 342 | element.className = inputs[index]; | ||
| 343 | element.id = group.id + "-" + inputs[index]; | ||
| 344 | |||
| 345 | if (group.defaultRange) { | ||
| 346 | if (group.defaultRange.min) { | ||
| 347 | element.setAttribute("min", group.defaultRange.min); | ||
| 348 | } | ||
| 349 | if (group.defaultRange.max) { | ||
| 350 | element.setAttribute("max", group.defaultRange.max); | ||
| 351 | } | ||
| 352 | if (group.defaultRange.step) { | ||
| 353 | element.setAttribute("step", group.defaultRange.step); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | if (group.oninput) { | ||
| 357 | element.oninput = group.oninput; | ||
| 358 | } else { | ||
| 359 | element.oninput = function (event) { | ||
| 360 | self.webBridgeSync(group.id, { | ||
| 361 | x: domArray[0].value, | ||
| 362 | y: domArray[1].value, | ||
| 363 | z: domArray[2].value | ||
| 364 | }); | ||
| 365 | }; | ||
| 366 | } | ||
| 367 | element.onchange = element.oninput; | ||
| 368 | domArray.push(element); | ||
| 369 | } | ||
| 370 | |||
| 371 | this.addLabel(parent, group); | ||
| 372 | var className = ""; | ||
| 373 | for (var i = 0; i < inputs.length; i++) { | ||
| 374 | className += inputs[i].charAt(0) | ||
| 375 | .toLowerCase(); | ||
| 376 | } | ||
| 377 | parent.className += " property vector-section " + className; | ||
| 378 | |||
| 379 | // Add Tuple and the rest | ||
| 380 | var tupleContainer = document.createElement("div"); | ||
| 381 | tupleContainer.className = "tuple"; | ||
| 382 | for (var domIndex in domArray) { | ||
| 383 | var container = domArray[domIndex]; | ||
| 384 | var div = document.createElement("div"); | ||
| 385 | var label = document.createElement("label"); | ||
| 386 | label.innerHTML = inputs[domIndex] + ":"; | ||
| 387 | label.setAttribute("for", container.id); | ||
| 388 | div.appendChild(container); | ||
| 389 | div.appendChild(label); | ||
| 390 | tupleContainer.appendChild(div); | ||
| 391 | } | ||
| 392 | parent.appendChild(tupleContainer); | ||
| 393 | }, | ||
| 394 | addVectorQuaternion: function (parent, group) { | ||
| 395 | this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]); | ||
| 396 | }, | ||
| 397 | addColorPicker: function (parent, group) { | ||
| 398 | var self = this; | ||
| 399 | var $colPickContainer = $('<div>', { | ||
| 400 | id: group.id, | ||
| 401 | class: "color-picker" | ||
| 402 | }); | ||
| 403 | var updateColors = function (red, green, blue) { | ||
| 404 | $colPickContainer.css('background-color', "rgb(" + | ||
| 405 | red + "," + | ||
| 406 | green + "," + | ||
| 407 | blue + ")"); | ||
| 408 | }; | ||
| 409 | |||
| 410 | var inputs = ["red", "green", "blue"]; | ||
| 411 | var domArray = []; | ||
| 412 | group.oninput = function (event) { | ||
| 413 | $colPickContainer.colpickSetColor( | ||
| 414 | { | ||
| 415 | r: domArray[0].value, | ||
| 416 | g: domArray[1].value, | ||
| 417 | b: domArray[2].value | ||
| 418 | }, | ||
| 419 | true); | ||
| 420 | }; | ||
| 421 | group.defaultRange = { | ||
| 422 | min: 0, | ||
| 423 | max: 255, | ||
| 424 | step: 1 | ||
| 425 | }; | ||
| 426 | |||
| 427 | parent.appendChild($colPickContainer[0]); | ||
| 428 | self.addVector(parent, group, inputs, domArray); | ||
| 429 | |||
| 430 | updateColors(domArray[0].value, domArray[1].value, domArray[2].value); | ||
| 431 | |||
| 432 | // Could probably write a custom one for this to completely write out jquery, | ||
| 433 | // but for now, using the same as earlier. | ||
| 434 | |||
| 435 | /* Color Picker Logic Here */ | ||
| 436 | |||
| 437 | |||
| 438 | $colPickContainer.colpick({ | ||
| 439 | colorScheme: 'dark', | ||
| 440 | layout: 'hex', | ||
| 441 | color: { | ||
| 442 | r: domArray[0].value, | ||
| 443 | g: domArray[1].value, | ||
| 444 | b: domArray[2].value | ||
| 445 | }, | ||
| 446 | onChange: function (hsb, hex, rgb, el) { | ||
| 447 | updateColors(rgb.r, rgb.g, rgb.b); | ||
| 448 | |||
| 449 | domArray[0].value = rgb.r; | ||
| 450 | domArray[1].value = rgb.g; | ||
| 451 | domArray[2].value = rgb.b; | ||
| 452 | self.webBridgeSync(group.id, { | ||
| 453 | red: rgb.r, | ||
| 454 | green: rgb.g, | ||
| 455 | blue: rgb.b | ||
| 456 | }); | ||
| 457 | }, | ||
| 458 | onSubmit: function (hsb, hex, rgb, el) { | ||
| 459 | $(el) | ||
| 460 | .css('background-color', '#' + hex); | ||
| 461 | $(el) | ||
| 462 | .colpickHide(); | ||
| 463 | domArray[0].value = rgb.r; | ||
| 464 | domArray[1].value = rgb.g; | ||
| 465 | domArray[2].value = rgb.b; | ||
| 466 | self.webBridgeSync(group.id, { | ||
| 467 | red: rgb.r, | ||
| 468 | green: rgb.g, | ||
| 469 | blue: rgb.b | ||
| 470 | }); | ||
| 471 | } | ||
| 472 | }); | ||
| 473 | }, | ||
| 474 | addTextureField: function (parent, group) { | ||
| 475 | var self = this; | ||
| 476 | this.addLabel(parent, group); | ||
| 477 | parent.className += " property texture"; | ||
| 478 | var textureImage = document.createElement("div"); | ||
| 479 | var textureUrl = document.createElement("input"); | ||
| 480 | textureUrl.setAttribute("type", "text"); | ||
| 481 | textureUrl.id = group.id; | ||
| 482 | textureImage.className = "texture-image no-texture"; | ||
| 483 | var image = document.createElement("img"); | ||
| 484 | var imageLoad = _.debounce(function (url) { | ||
| 485 | if (url.length > 0) { | ||
| 486 | textureImage.classList.remove("no-texture"); | ||
| 487 | textureImage.classList.add("with-texture"); | ||
| 488 | image.src = url; | ||
| 489 | image.style.display = "block"; | ||
| 490 | } else { | ||
| 491 | image.src = ""; | ||
| 492 | image.style.display = "none"; | ||
| 493 | textureImage.classList.add("no-texture"); | ||
| 494 | } | ||
| 495 | self.webBridgeSync(group.id, url); | ||
| 496 | }, 250); | ||
| 497 | |||
| 498 | textureUrl.oninput = function (event) { | ||
| 499 | // Add throttle | ||
| 500 | var url = event.target.value; | ||
| 501 | imageLoad(url); | ||
| 502 | }; | ||
| 503 | textureUrl.onchange = textureUrl.oninput; | ||
| 504 | textureImage.appendChild(image); | ||
| 505 | parent.appendChild(textureImage); | ||
| 506 | parent.appendChild(textureUrl); | ||
| 507 | }, | ||
| 508 | addSlider: function (parent, group) { | ||
| 509 | var self = this; | ||
| 510 | this.addLabel(parent, group); | ||
| 511 | parent.className += " property range"; | ||
| 512 | var container = document.createElement("div"); | ||
| 513 | container.className = "slider-wrapper"; | ||
| 514 | var slider = document.createElement("input"); | ||
| 515 | slider.setAttribute("type", "range"); | ||
| 516 | |||
| 517 | var inputField = document.createElement("input"); | ||
| 518 | inputField.setAttribute("type", "number"); | ||
| 519 | |||
| 520 | container.appendChild(slider); | ||
| 521 | container.appendChild(inputField); | ||
| 522 | parent.appendChild(container); | ||
| 523 | |||
| 524 | if (group.type === "SliderInteger") { | ||
| 525 | inputField.setAttribute("min", group.min !== undefined ? group.min : 0); | ||
| 526 | inputField.setAttribute("step", 1); | ||
| 527 | |||
| 528 | slider.setAttribute("min", group.min !== undefined ? group.min : 0); | ||
| 529 | slider.setAttribute("max", group.max !== undefined ? group.max : 10000); | ||
| 530 | slider.setAttribute("step", 1); | ||
| 531 | |||
| 532 | inputField.oninput = function (event) { | ||
| 533 | |||
| 534 | if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) { | ||
| 535 | slider.setAttribute("max", event.target.value); | ||
| 536 | } | ||
| 537 | slider.value = event.target.value; | ||
| 538 | |||
| 539 | self.webBridgeSync(group.id, slider.value); | ||
| 540 | }; | ||
| 541 | inputField.onchange = inputField.oninput; | ||
| 542 | slider.oninput = function (event) { | ||
| 543 | inputField.value = event.target.value; | ||
| 544 | self.webBridgeSync(group.id, slider.value); | ||
| 545 | }; | ||
| 546 | |||
| 547 | inputField.id = group.id; | ||
| 548 | } else if (group.type === "SliderRadian") { | ||
| 549 | slider.setAttribute("min", group.min !== undefined ? group.min : 0); | ||
| 550 | slider.setAttribute("max", group.max !== undefined ? group.max : 180); | ||
| 551 | slider.setAttribute("step", 1); | ||
| 552 | parent.className += " radian"; | ||
| 553 | inputField.setAttribute("min", (group.min !== undefined ? group.min : 0)); | ||
| 554 | inputField.setAttribute("max", (group.max !== undefined ? group.max : 180)); | ||
| 555 | |||
| 556 | inputField.oninput = function (event) { | ||
| 557 | slider.value = event.target.value; | ||
| 558 | self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); | ||
| 559 | }; | ||
| 560 | inputField.onchange = inputField.oninput; | ||
| 561 | |||
| 562 | inputField.id = group.id; | ||
| 563 | slider.oninput = function (event) { | ||
| 564 | if (event.target.value > 0) { | ||
| 565 | inputField.value = Math.floor(event.target.value); | ||
| 566 | } else { | ||
| 567 | inputField.value = Math.ceil(event.target.value); | ||
| 568 | } | ||
| 569 | self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); | ||
| 570 | }; | ||
| 571 | var degrees = document.createElement("label"); | ||
| 572 | degrees.innerHTML = "°"; | ||
| 573 | degrees.style.fontSize = "1.4rem"; | ||
| 574 | degrees.style.display = "inline"; | ||
| 575 | degrees.style.verticalAlign = "top"; | ||
| 576 | degrees.style.paddingLeft = "0.4rem"; | ||
| 577 | container.appendChild(degrees); | ||
| 578 | |||
| 579 | } else { | ||
| 580 | // Must then be Float | ||
| 581 | inputField.setAttribute("min", group.min !== undefined ? group.min : 0); | ||
| 582 | slider.setAttribute("step", 0.01); | ||
| 583 | |||
| 584 | slider.setAttribute("min", group.min !== undefined ? group.min : 0); | ||
| 585 | slider.setAttribute("max", group.max !== undefined ? group.max : 1); | ||
| 586 | slider.setAttribute("step", 0.01); | ||
| 587 | |||
| 588 | inputField.oninput = function (event) { | ||
| 589 | if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) { | ||
| 590 | slider.setAttribute("max", event.target.value); | ||
| 591 | } | ||
| 592 | |||
| 593 | slider.value = event.target.value; | ||
| 594 | self.webBridgeSync(group.id, slider.value); | ||
| 595 | // bind web sock update here. | ||
| 596 | }; | ||
| 597 | inputField.onchange = inputField.oninput; | ||
| 598 | slider.oninput = function (event) { | ||
| 599 | inputField.value = event.target.value; | ||
| 600 | self.webBridgeSync(group.id, inputField.value); | ||
| 601 | }; | ||
| 602 | |||
| 603 | inputField.id = group.id; | ||
| 604 | } | ||
| 605 | |||
| 606 | // UpdateBinding | ||
| 607 | }, | ||
| 608 | addCheckBox: function (parent, group) { | ||
| 609 | var checkBox = document.createElement("input"); | ||
| 610 | checkBox.setAttribute("type", "checkbox"); | ||
| 611 | var self = this; | ||
| 612 | checkBox.onchange = function (event) { | ||
| 613 | self.webBridgeSync(group.id, event.target.checked); | ||
| 614 | }; | ||
| 615 | checkBox.id = group.id; | ||
| 616 | parent.appendChild(checkBox); | ||
| 617 | var label = this.addLabel(parent, group); | ||
| 618 | label.setAttribute("for", checkBox.id); | ||
| 619 | parent.className += " property checkbox"; | ||
| 620 | }, | ||
| 621 | addElement: function (parent, group) { | ||
| 622 | var self = this; | ||
| 623 | var property = document.createElement("div"); | ||
| 624 | property.id = group.id; | ||
| 625 | |||
| 626 | var row = document.createElement("div"); | ||
| 627 | switch (group.type) { | ||
| 628 | case "Button": | ||
| 629 | var button = document.createElement("input"); | ||
| 630 | button.setAttribute("type", "button"); | ||
| 631 | button.id = group.id; | ||
| 632 | if (group.disabled) { | ||
| 633 | button.disabled = group.disabled; | ||
| 634 | } | ||
| 635 | button.className = group.class; | ||
| 636 | button.value = group.name; | ||
| 637 | |||
| 638 | button.onclick = group.callback; | ||
| 639 | parent.appendChild(button); | ||
| 640 | break; | ||
| 641 | case "Row": | ||
| 642 | var hr = document.createElement("hr"); | ||
| 643 | hr.className = "splitter"; | ||
| 644 | if (group.id) { | ||
| 645 | hr.id = group.id; | ||
| 646 | } | ||
| 647 | parent.appendChild(hr); | ||
| 648 | break; | ||
| 649 | case "Boolean": | ||
| 650 | self.addCheckBox(row, group); | ||
| 651 | parent.appendChild(row); | ||
| 652 | break; | ||
| 653 | case "SliderFloat": | ||
| 654 | case "SliderInteger": | ||
| 655 | case "SliderRadian": | ||
| 656 | self.addSlider(row, group); | ||
| 657 | parent.appendChild(row); | ||
| 658 | break; | ||
| 659 | case "Texture": | ||
| 660 | self.addTextureField(row, group); | ||
| 661 | parent.appendChild(row); | ||
| 662 | break; | ||
| 663 | case "Color": | ||
| 664 | self.addColorPicker(row, group); | ||
| 665 | parent.appendChild(row); | ||
| 666 | break; | ||
| 667 | case "Vector": | ||
| 668 | self.addVector(row, group); | ||
| 669 | parent.appendChild(row); | ||
| 670 | break; | ||
| 671 | case "VectorQuaternion": | ||
| 672 | self.addVectorQuaternion(row, group); | ||
| 673 | parent.appendChild(row); | ||
| 674 | break; | ||
| 675 | default: | ||
| 676 | console.log("not defined"); | ||
| 677 | } | ||
| 678 | return row; | ||
| 679 | } | ||
| 680 | }; | ||
| @@ -0,0 +1,124 @@ | |||
| 1 | /* | ||
| 2 | // particle-style.css | ||
| 3 | // | ||
| 4 | // Created by Matti 'Menithal' Lahtinen on 21 May 2017 | ||
| 5 | // Copyright 2017 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | */ | ||
| 10 | |||
| 11 | |||
| 12 | .property-group { | ||
| 13 | max-height: 0; | ||
| 14 | -webkit-transition: max-height 0.15s ease-out; | ||
| 15 | transition: max-height 0.15s ease-out; | ||
| 16 | overflow: hidden; | ||
| 17 | } | ||
| 18 | .property-group.visible { | ||
| 19 | transition: max-height 0.25s ease-in; | ||
| 20 | } | ||
| 21 | .section-wrap { | ||
| 22 | width: 100%; | ||
| 23 | } | ||
| 24 | .property { | ||
| 25 | padding: 0.4rem 0; | ||
| 26 | margin: 0; | ||
| 27 | } | ||
| 28 | .property.checkbox { | ||
| 29 | margin: 0; | ||
| 30 | } | ||
| 31 | .property.range label{ | ||
| 32 | display: block; | ||
| 33 | } | ||
| 34 | |||
| 35 | input[type="button"] { | ||
| 36 | margin: 0.4rem; | ||
| 37 | min-width: 6rem; | ||
| 38 | } | ||
| 39 | input[type="text"] { | ||
| 40 | margin: 0; | ||
| 41 | } | ||
| 42 | .property.range input[type=number]{ | ||
| 43 | margin-left: 0.8rem; | ||
| 44 | width: 5.4rem; | ||
| 45 | height: 1.8rem; | ||
| 46 | } | ||
| 47 | input[type=range] { | ||
| 48 | -webkit-appearance: none; | ||
| 49 | background: #2e2e2e; | ||
| 50 | height: 1.8rem; | ||
| 51 | border-radius: 1rem; | ||
| 52 | } | ||
| 53 | input[type=range]::-webkit-slider-thumb { | ||
| 54 | -webkit-appearance:none; | ||
| 55 | width: 0.6rem; | ||
| 56 | height: 1.8rem; | ||
| 57 | padding:0; | ||
| 58 | margin: 0; | ||
| 59 | background-color: #696969; | ||
| 60 | border-radius: 1rem; | ||
| 61 | } | ||
| 62 | input[type=range]::-webkit-slider-thumb:hover { | ||
| 63 | background-color: white; | ||
| 64 | } | ||
| 65 | input[type=range]:focus { /*#252525*/ | ||
| 66 | outline: none; | ||
| 67 | } | ||
| 68 | .tuple label { | ||
| 69 | text-transform: capitalize; | ||
| 70 | } | ||
| 71 | .slider-wrapper { | ||
| 72 | display: table; | ||
| 73 | padding: 0.4rem 0; | ||
| 74 | } | ||
| 75 | hr.splitter{ | ||
| 76 | width: 100%; | ||
| 77 | padding: 0.2rem 0 0 0; | ||
| 78 | margin: 0; | ||
| 79 | position: relative; | ||
| 80 | clear: both; | ||
| 81 | } | ||
| 82 | hr.splitter:last-of-type{ | ||
| 83 | padding:0; | ||
| 84 | } | ||
| 85 | #rem { | ||
| 86 | height: 1rem; | ||
| 87 | width: 1rem; | ||
| 88 | } | ||
| 89 | .property { | ||
| 90 | min-height: 2rem; | ||
| 91 | } | ||
| 92 | .property.vector-section{ | ||
| 93 | |||
| 94 | width: 24rem; | ||
| 95 | } | ||
| 96 | |||
| 97 | .property.texture { | ||
| 98 | display: block; | ||
| 99 | } | ||
| 100 | .property.texture input{ | ||
| 101 | margin: 0.4rem 0; | ||
| 102 | } | ||
| 103 | .texture-image img{ | ||
| 104 | padding: 0; | ||
| 105 | margin: 0; | ||
| 106 | width: 100%; | ||
| 107 | height: 100%; | ||
| 108 | display: none; | ||
| 109 | } | ||
| 110 | .texture-image { | ||
| 111 | display: block; | ||
| 112 | position: relative; | ||
| 113 | background-repeat: no-repeat; | ||
| 114 | background-position: center; | ||
| 115 | background-size: 100% 100%; | ||
| 116 | margin-top: 0.4rem; | ||
| 117 | height:128px; | ||
| 118 | width: 128px; | ||
| 119 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); | ||
| 120 | } | ||
| 121 | |||
| 122 | .texture-image.no-texture{ | ||
| 123 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); | ||
| 124 | } | ||
| @@ -1,95 +1,42 @@ | |||
| 1 | <!-- | 1 | <!-- |
| 2 | // particleExplorer.hml | 2 | // particleExplorer.hml |
| 3 | // | 3 | // |
| 4 | // | 4 | // |
| 5 | // Created by James B. Pollack @imgntn on 9/26/2015 | 5 | // Created by James B. Pollack @imgntn on 9/26/2015 |
| 6 | // Copyright 2015 High Fidelity, Inc. | 6 | // Copyright 2015 High Fidelity, Inc. |
| 7 | // | 7 | // |
| 8 | // Loads dat.gui, underscore, and the app | 8 | |
| 9 | // Quickly edit the aesthetics of a particle system. | 9 | // Reworked by Menithal on 20/5/2017 |
| 10 | // Using a custom built system for High Fidelity | ||
| 10 | // | 11 | // |
| 11 | // Distributed under the Apache License, Version 2.0. | 12 | // Distributed under the Apache License, Version 2.0. |
| 12 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 13 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 13 | // | 14 | // |
| 14 | --> | 15 | --> |
| 15 | <html> | 16 | <html> |
| 16 | <head> | 17 | <head> |
| 17 | <link rel="stylesheet" type="text/css" href="../html/css/colpick.css"> | 18 | <link rel="stylesheet" type="text/css" href="../html/css/colpick.css"> |
| 18 | <script type="text/javascript" src="dat.gui.min.js"></script> | 19 | <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> |
| 19 | <script type="text/javascript" src="underscore-min.js"></script> | 20 | <script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script> |
| 20 | <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> | 21 | <!----> |
| 21 | <script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script> | 22 | <script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script> |
| 22 | <script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script> | 23 | <script type="text/javascript" src="../html/js/colpick.js"></script> |
| 23 | <script type="text/javascript" src="../html/js/colpick.js"></script> | 24 | |
| 24 | <script type="text/javascript" src="particleExplorer.js"></script> | 25 | <script type="text/javascript" src="underscore-min.js"></script> |
| 25 | <script> | 26 | <script type="text/javascript" src="hifi-entity-ui.js?v1"></script> |
| 26 | function loaded() { | 27 | |
| 27 | // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked | 28 | <link rel="stylesheet" type="text/css" href="../html/css/hifi-style.css"> |
| 28 | document.addEventListener("contextmenu", function (event) { | 29 | <link rel="stylesheet" type="text/css" href="../html/css/edit-style.css"> |
| 29 | event.preventDefault(); | 30 | <link rel="stylesheet" type="text/css" href="particle-style.css"> |
| 30 | }, false); | ||
| 31 | } | ||
| 32 | </script> | ||
| 33 | <style> | ||
| 34 | |||
| 35 | body{ | ||
| 36 | background-color:black; | ||
| 37 | overflow-x: hidden; | ||
| 38 | } | ||
| 39 | |||
| 40 | #my-gui-container{ | ||
| 41 | |||
| 42 | } | ||
| 43 | |||
| 44 | .importer{ | ||
| 45 | margin-bottom:4px; | ||
| 46 | } | ||
| 47 | .exported-props-section { | ||
| 48 | width: 50%; | ||
| 49 | margin: 0 auto; | ||
| 50 | |||
| 51 | } | ||
| 52 | #exported-props { | ||
| 53 | /* Set the margin-left and margin-right automatically set */ | ||
| 54 | color: white; | ||
| 55 | white-space: pre-wrap; /* css-3 */ | ||
| 56 | } | ||
| 57 | |||
| 58 | .color-box { | ||
| 59 | display: block; | ||
| 60 | width: 60%; | ||
| 61 | height: 21px; | ||
| 62 | margin-top: 4px; | ||
| 63 | float: left; | ||
| 64 | } | ||
| 65 | |||
| 66 | .color-box.highlight { | ||
| 67 | width: 13.5pt; | ||
| 68 | height: 13.5pt; | ||
| 69 | border: 1.5pt solid black; | ||
| 70 | } | ||
| 71 | |||
| 72 | ::-webkit-input-placeholder { | ||
| 73 | text-align: center; | ||
| 74 | font-family: Helvetica | ||
| 75 | } | ||
| 76 | |||
| 77 | #importer-input{ | ||
| 78 | width:90%; | ||
| 79 | line-height: 2; | ||
| 80 | margin-left:5%; | ||
| 81 | } | ||
| 82 | |||
| 83 | </style> | ||
| 84 | </head> | 31 | </head> |
| 85 | <body onload="loaded();"> | 32 | <body> |
| 86 | <div class="importer"> | 33 | <div id="particle-explorer"> |
| 87 | <input type='text' id="importer-input" placeholder="Import: Paste JSON here." onkeypress="handleInputKeyPress(event)"> | 34 | <div class="section-header"> |
| 88 | <div class = "exported-props-section"> | 35 | <label> Particle Explorer </label> |
| 89 | <div id = "exported-props"></div> | 36 | </div> |
| 90 | </div> | 37 | <!-- This will be filled by the script! --> |
| 91 | <div id="my-gui-container"> | 38 | </div> |
| 92 | </div> | 39 | <div id="rem"></div> |
| 93 | 40 | <script type="text/javascript" src="particleExplorer.js"></script> | |
| 94 | </body> | 41 | </body> |
| 95 | </html> | 42 | </html> |
| @@ -2,550 +2,410 @@ · 1 unmodified line | |||
| 2 | // particleExplorer.js | 2 | // particleExplorer.js |
| 3 | // | 3 | // |
| 4 | // Created by James B. Pollack @imgntn on 9/26/2015 | 4 | // Created by James B. Pollack @imgntn on 9/26/2015 |
| 5 | // Copyright 2015 High Fidelity, Inc. | 5 | // Copyright 2017 High Fidelity, Inc. |
| 6 | // | ||
| 7 | // Reworked by Menithal on 20/5/2017 | ||
| 8 | // | ||
| 6 | // Web app side of the App - contains GUI. | 9 | // Web app side of the App - contains GUI. |
| 7 | // This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. | 10 | // This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. |
| 8 | // | 11 | // |
| 9 | // Distributed under the Apache License, Version 2.0. | 12 | // Distributed under the Apache License, Version 2.0. |
| 10 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 13 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 11 | // | 14 | // |
| 12 | /* global window, alert, EventBridge, dat, listenForSettingsUpdates, createVec3Folder, createQuatFolder, writeVec3ToInterface, writeDataToInterface, | 15 | /* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */ |
| 13 | $, document, _, openEventBridge */ | 16 | /* eslint no-console: 0, no-global-assign: 0 */ |
| 14 | 17 | ||
| 15 | var Settings = function () { | 18 | (function () { |
| 16 | this.exportSettings = function () { | 19 | |
| 17 | // copyExportSettingsToClipboard(); | 20 | var root = document.getElementById("particle-explorer"); |
| 18 | showPreselectedPrompt(); | 21 | |
| 19 | }; | 22 | window.onload = function () { |
| 20 | this.importSettings = function () { | 23 | var ui = new HifiEntityUI(root); |
| 21 | importSettings(); | 24 | var textarea = document.createElement("textarea"); |
| 22 | }; | 25 | var properties = ""; |
| 23 | }; | 26 | var menuStructure = { |
| 24 | 27 | General: [ | |
| 25 | // 2-way bindings-aren't quite ready yet. see bottom of file. | 28 | { |
| 26 | var AUTO_UPDATE = false; | 29 | type: "Row", |
| 27 | var UPDATE_ALL_FREQUENCY = 100; | 30 | id: "export-import-field" |
| 28 | 31 | }, | |
| 29 | var controllers = []; | 32 | { |
| 30 | var colpickKeys = []; | 33 | id: "show-properties-button", |
| 31 | var folders = []; | 34 | name: "Show Properties", |
| 32 | var gui = null; | 35 | type: "Button", |
| 33 | var settings = new Settings(); | 36 | class: "blue", |
| 34 | var updateInterval; | 37 | disabled: true, |
| 35 | 38 | callback: function (event) { | |
| 36 | var active = false; | 39 | var insertZone = document.getElementById("export-import-field"); |
| 37 | 40 | var json = ui.getSettings(); | |
| 38 | var currentInputField; | 41 | properties = JSON.stringify(json); |
| 39 | var storedController; | 42 | textarea.value = properties; |
| 40 | // CHANGE TO WHITELIST | 43 | if (!insertZone.contains(textarea)) { |
| 41 | var keysToAllow = [ | 44 | insertZone.appendChild(textarea); |
| 42 | 'isEmitting', | 45 | insertZone.parentNode.parentNode.style.maxHeight = |
| 43 | 'maxParticles', | 46 | insertZone.parentNode.clientHeight + "px"; |
| 44 | 'lifespan', | 47 | document.getElementById("export-properties-button").removeAttribute("disabled"); |
| 45 | 'emitRate', | 48 | textarea.onchange = function (e) { |
| 46 | 'emitSpeed', | 49 | if (e.target.value !== properties) { |
| 47 | 'speedSpread', | 50 | document.getElementById("import-properties-button").removeAttribute("disabled"); |
| 48 | 'emitOrientation', | 51 | } |
| 49 | 'emitDimensions', | 52 | }; |
| 50 | 'polarStart', | 53 | textarea.oninput = textarea.onchange; |
| 51 | 'polarFinish', | 54 | } else { |
| 52 | 'azimuthStart', | 55 | textarea.onchange = function () {}; |
| 53 | 'azimuthFinish', | 56 | textarea.oninput = textarea.onchange; |
| 54 | 'emitAcceleration', | 57 | textarea.value = ""; |
| 55 | 'accelerationSpread', | 58 | textarea.remove(); |
| 56 | 'particleRadius', | 59 | insertZone.parentNode.parentNode.style.maxHeight = |
| 57 | 'radiusSpread', | 60 | insertZone.parentNode.clientHeight + "px"; |
| 58 | 'radiusStart', | 61 | document.getElementById("export-properties-button").setAttribute("disabled", true); |
| 59 | 'radiusFinish', | 62 | document.getElementById("import-properties-button").setAttribute("disabled", true); |
| 60 | 'color', | 63 | } |
| 61 | 'colorSpread', | 64 | } |
| 62 | 'colorStart', | 65 | }, |
| 63 | 'colorFinish', | 66 | { |
| 64 | 'alpha', | 67 | id: "import-properties-button", |
| 65 | 'alphaSpread', | 68 | name: "Import", |
| 66 | 'alphaStart', | 69 | type: "Button", |
| 67 | 'alphaFinish', | 70 | class: "blue", |
| 68 | 'emitterShouldTrail', | 71 | disabled: true, |
| 69 | 'textures' | 72 | callback: function (event) { |
| 70 | ]; | 73 | ui.fillFields(JSON.parse(textarea.value)); |
| 71 | 74 | ui.submitChanges(JSON.parse(textarea.value)); | |
| 72 | var individualKeys = []; | 75 | } |
| 73 | var vec3Keys = []; | 76 | }, |
| 74 | var quatKeys = []; | 77 | { |
| 75 | var colorKeys = []; | 78 | id: "export-properties-button", |
| 76 | 79 | name: "Export", | |
| 77 | window.onload = function () { | 80 | type: "Button", |
| 78 | openEventBridge(function () { | 81 | class: "red", |
| 79 | var stringifiedData = JSON.stringify({ | 82 | disabled: true, |
| 80 | messageType: 'page_loaded' | 83 | callback: function (event) { |
| 84 | textarea.select(); | ||
| 85 | try { | ||
| 86 | var success = document.execCommand('copy'); | ||
| 87 | if (!success) { | ||
| 88 | throw "Not success :("; | ||
| 89 | } | ||
| 90 | } catch (e) { | ||
| 91 | print("couldnt copy field"); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | }, | ||
| 95 | { | ||
| 96 | type: "Row" | ||
| 97 | }, | ||
| 98 | { | ||
| 99 | id: "isEmitting", | ||
| 100 | name: "Is Emitting", | ||
| 101 | type: "Boolean" | ||
| 102 | }, | ||
| 103 | { | ||
| 104 | type: "Row" | ||
| 105 | }, | ||
| 106 | { | ||
| 107 | id: "lifespan", | ||
| 108 | name: "Lifespan", | ||
| 109 | type: "SliderFloat", | ||
| 110 | min: 0.01, | ||
| 111 | max: 10 | ||
| 112 | }, | ||
| 113 | { | ||
| 114 | type: "Row" | ||
| 115 | }, | ||
| 116 | { | ||
| 117 | id: "maxParticles", | ||
| 118 | name: "Max Particles", | ||
| 119 | type: "SliderInteger", | ||
| 120 | min: 1, | ||
| 121 | max: 10000 | ||
| 122 | }, | ||
| 123 | { | ||
| 124 | type: "Row" | ||
| 125 | }, | ||
| 126 | { | ||
| 127 | id: "textures", | ||
| 128 | name: "Textures", | ||
| 129 | type: "Texture" | ||
| 130 | }, | ||
| 131 | { | ||
| 132 | type: "Row" | ||
| 133 | } | ||
| 134 | ], | ||
| 135 | Emit: [ | ||
| 136 | { | ||
| 137 | id: "emitRate", | ||
| 138 | name: "Emit Rate", | ||
| 139 | type: "SliderInteger", | ||
| 140 | max: 1000, | ||
| 141 | min: 1 | ||
| 142 | }, | ||
| 143 | { | ||
| 144 | type: "Row" | ||
| 145 | }, | ||
| 146 | { | ||
| 147 | id: "emitSpeed", | ||
| 148 | name: "Emit Speed", | ||
| 149 | type: "SliderFloat", | ||
| 150 | max: 5 | ||
| 151 | }, | ||
| 152 | { | ||
| 153 | type: "Row" | ||
| 154 | }, | ||
| 155 | { | ||
| 156 | id: "emitDimensions", | ||
| 157 | name: "Emit Dimension", | ||
| 158 | type: "Vector", | ||
| 159 | defaultRange: { | ||
| 160 | min: 0, | ||
| 161 | step: 0.01 | ||
| 162 | } | ||
| 163 | }, | ||
| 164 | { | ||
| 165 | type: "Row" | ||
| 166 | }, | ||
| 167 | { | ||
| 168 | id: "emitOrientation", | ||
| 169 | unit: "deg", | ||
| 170 | name: "Emit Orientation", | ||
| 171 | type: "VectorQuaternion", | ||
| 172 | defaultRange: { | ||
| 173 | min: 0, | ||
| 174 | step: 0.01 | ||
| 175 | } | ||
| 176 | }, | ||
| 177 | { | ||
| 178 | type: "Row" | ||
| 179 | }, | ||
| 180 | { | ||
| 181 | id: "emitShouldTrail", | ||
| 182 | name: "Emit Should Trail", | ||
| 183 | type: "Boolean" | ||
| 184 | }, | ||
| 185 | { | ||
| 186 | type: "Row" | ||
| 187 | } | ||
| 188 | ], | ||
| 189 | Radius: [ | ||
| 190 | { | ||
| 191 | id: "particleRadius", | ||
| 192 | name: "Particle Radius", | ||
| 193 | type: "SliderFloat", | ||
| 194 | max: 4.0 | ||
| 195 | }, | ||
| 196 | { | ||
| 197 | type: "Row" | ||
| 198 | }, | ||
| 199 | { | ||
| 200 | id: "radiusSpread", | ||
| 201 | name: "Radius Spread", | ||
| 202 | type: "SliderFloat", | ||
| 203 | max: 4.0 | ||
| 204 | }, | ||
| 205 | { | ||
| 206 | type: "Row" | ||
| 207 | }, | ||
| 208 | { | ||
| 209 | id: "radiusStart", | ||
| 210 | name: "Radius Start", | ||
| 211 | type: "SliderFloat", | ||
| 212 | max: 4.0 | ||
| 213 | }, | ||
| 214 | { | ||
| 215 | type: "Row" | ||
| 216 | }, | ||
| 217 | { | ||
| 218 | id: "radiusFinish", | ||
| 219 | name: "Radius Finish", | ||
| 220 | type: "SliderFloat", | ||
| 221 | max: 4.0 | ||
| 222 | }, | ||
| 223 | { | ||
| 224 | type: "Row" | ||
| 225 | } | ||
| 226 | ], | ||
| 227 | Color: [ | ||
| 228 | { | ||
| 229 | id: "color", | ||
| 230 | name: "Color", | ||
| 231 | type: "Color", | ||
| 232 | defaultColor: { | ||
| 233 | red: 255, | ||
| 234 | green: 255, | ||
| 235 | blue: 255 | ||
| 236 | } | ||
| 237 | }, | ||
| 238 | { | ||
| 239 | type: "Row" | ||
| 240 | }, | ||
| 241 | { | ||
| 242 | id: "colorSpread", | ||
| 243 | name: "Color Spread", | ||
| 244 | type: "Color", | ||
| 245 | defaultColor: { | ||
| 246 | red: 0, | ||
| 247 | green: 0, | ||
| 248 | blue: 0 | ||
| 249 | } | ||
| 250 | }, | ||
| 251 | { | ||
| 252 | type: "Row" | ||
| 253 | }, | ||
| 254 | { | ||
| 255 | id: "colorStart", | ||
| 256 | name: "Color Start", | ||
| 257 | type: "Color", | ||
| 258 | defaultColor: { | ||
| 259 | red: 255, | ||
| 260 | green: 255, | ||
| 261 | blue: 255 | ||
| 262 | } | ||
| 263 | }, | ||
| 264 | { | ||
| 265 | type: "Row" | ||
| 266 | }, | ||
| 267 | { | ||
| 268 | id: "colorFinish", | ||
| 269 | name: "Color Finish", | ||
| 270 | type: "Color", | ||
| 271 | defaultColor: { | ||
| 272 | red: 255, | ||
| 273 | green: 255, | ||
| 274 | blue: 255 | ||
| 275 | } | ||
| 276 | }, | ||
| 277 | { | ||
| 278 | type: "Row" | ||
| 279 | } | ||
| 280 | ], | ||
| 281 | Acceleration: [ | ||
| 282 | { | ||
| 283 | id: "emitAcceleration", | ||
| 284 | name: "Emit Acceleration", | ||
| 285 | type: "Vector", | ||
| 286 | defaultRange: { | ||
| 287 | step: 0.01 | ||
| 288 | } | ||
| 289 | }, | ||
| 290 | { | ||
| 291 | type: "Row" | ||
| 292 | }, | ||
| 293 | { | ||
| 294 | id: "accelerationSpread", | ||
| 295 | name: "Acceleration Spread", | ||
| 296 | type: "Vector", | ||
| 297 | defaultRange: { | ||
| 298 | step: 0.01 | ||
| 299 | } | ||
| 300 | }, | ||
| 301 | { | ||
| 302 | type: "Row" | ||
| 303 | } | ||
| 304 | ], | ||
| 305 | Alpha: [ | ||
| 306 | { | ||
| 307 | id: "alpha", | ||
| 308 | name: "Alpha", | ||
| 309 | type: "SliderFloat" | ||
| 310 | }, | ||
| 311 | { | ||
| 312 | type: "Row" | ||
| 313 | }, | ||
| 314 | { | ||
| 315 | id: "alphaSpread", | ||
| 316 | name: "Alpha Spread", | ||
| 317 | type: "SliderFloat" | ||
| 318 | }, | ||
| 319 | { | ||
| 320 | type: "Row" | ||
| 321 | }, | ||
| 322 | { | ||
| 323 | id: "alphaStart", | ||
| 324 | name: "Alpha Start", | ||
| 325 | type: "SliderFloat" | ||
| 326 | }, | ||
| 327 | { | ||
| 328 | type: "Row" | ||
| 329 | }, | ||
| 330 | { | ||
| 331 | id: "alphaFinish", | ||
| 332 | name: "Alpha Finish", | ||
| 333 | type: "SliderFloat" | ||
| 334 | }, | ||
| 335 | { | ||
| 336 | type: "Row" | ||
| 337 | } | ||
| 338 | ], | ||
| 339 | Polar: [ | ||
| 340 | { | ||
| 341 | id: "polarStart", | ||
| 342 | name: "Polar Start", | ||
| 343 | unit: "deg", | ||
| 344 | type: "SliderRadian" | ||
| 345 | }, | ||
| 346 | { | ||
| 347 | type: "Row" | ||
| 348 | }, | ||
| 349 | { | ||
| 350 | id: "polarFinish", | ||
| 351 | name: "Polar Finish", | ||
| 352 | unit: "deg", | ||
| 353 | type: "SliderRadian" | ||
| 354 | }, | ||
| 355 | { | ||
| 356 | type: "Row" | ||
| 357 | } | ||
| 358 | ], | ||
| 359 | Azimuth: [ | ||
| 360 | { | ||
| 361 | id: "azimuthStart", | ||
| 362 | name: "Azimuth Start", | ||
| 363 | unit: "deg", | ||
| 364 | type: "SliderRadian", | ||
| 365 | min: -180, | ||
| 366 | max: 0 | ||
| 367 | }, | ||
| 368 | { | ||
| 369 | type: "Row" | ||
| 370 | }, | ||
| 371 | { | ||
| 372 | id: "azimuthFinish", | ||
| 373 | name: "Azimuth Finish", | ||
| 374 | unit: "deg", | ||
| 375 | type: "SliderRadian" | ||
| 376 | }, | ||
| 377 | { | ||
| 378 | type: "Row" | ||
| 379 | } | ||
| 380 | ] | ||
| 381 | }; | ||
| 382 | ui.setUI(menuStructure); | ||
| 383 | ui.setOnSelect(function () { | ||
| 384 | document.getElementById("show-properties-button").removeAttribute("disabled"); | ||
| 385 | document.getElementById("export-properties-button").setAttribute("disabled", true); | ||
| 386 | document.getElementById("import-properties-button").setAttribute("disabled", true); | ||
| 81 | }); | 387 | }); |
| 82 | 388 | ui.build(); | |
| 83 | EventBridge.emitWebEvent( | 389 | var overrideLoad = false; |
| 84 | stringifiedData | 390 | if (openEventBridge === undefined) { |
| 85 | ); | 391 | overrideLoad = true, |
| 86 | 392 | openEventBridge = function (callback) { | |
| 87 | listenForSettingsUpdates(); | 393 | callback({ |
| 88 | window.onresize = setGUIWidthToWindowWidth; | 394 | emitWebEvent: function () {}, |
| 89 | }); | 395 | submitChanges: function () {}, |
| 90 | 396 | scriptEventReceived: { | |
| 91 | }; | 397 | connect: function () { |
| 92 | 398 | ||
| 93 | function loadGUI() { | 399 | } |
| 94 | // whether or not to autoplace | 400 | } |
| 95 | gui = new dat.GUI({ | 401 | }); |
| 96 | autoPlace: false | 402 | }; |
| 97 | }); | ||
| 98 | |||
| 99 | // if not autoplacing, put gui in a custom container | ||
| 100 | if (gui.autoPlace === false) { | ||
| 101 | var customContainer = document.getElementById('my-gui-container'); | ||
| 102 | customContainer.appendChild(gui.domElement); | ||
| 103 | } | ||
| 104 | |||
| 105 | // presets for the GUI itself. a little confusing and import/export is mostly what we want to do at the moment. | ||
| 106 | // gui.remember(settings); | ||
| 107 | |||
| 108 | colpickKeys = []; | ||
| 109 | var keys = _.keys(settings); | ||
| 110 | |||
| 111 | _.each(keys, function(key) { | ||
| 112 | var shouldAllow = _.contains(keysToAllow, key); | ||
| 113 | |||
| 114 | if (shouldAllow) { | ||
| 115 | var subKeys = _.keys(settings[key]); | ||
| 116 | var hasX = _.contains(subKeys, 'x'); | ||
| 117 | var hasY = _.contains(subKeys, 'y'); | ||
| 118 | var hasZ = _.contains(subKeys, 'z'); | ||
| 119 | var hasW = _.contains(subKeys, 'w'); | ||
| 120 | var hasRed = _.contains(subKeys, 'red'); | ||
| 121 | var hasGreen = _.contains(subKeys, 'green'); | ||
| 122 | var hasBlue = _.contains(subKeys, 'blue'); | ||
| 123 | |||
| 124 | if ((hasX && hasY && hasZ) && hasW === false) { | ||
| 125 | vec3Keys.push(key); | ||
| 126 | } else if (hasX && hasY && hasZ && hasW) { | ||
| 127 | quatKeys.push(key); | ||
| 128 | } else if (hasRed || hasGreen || hasBlue) { | ||
| 129 | colorKeys.push(key); | ||
| 130 | |||
| 131 | } else { | ||
| 132 | individualKeys.push(key); | ||
| 133 | } | ||
| 134 | } | 403 | } |
| 135 | }); | 404 | openEventBridge(function (EventBridge) { |
| 136 | 405 | ui.connect(EventBridge); | |
| 137 | // alphabetize our keys | ||
| 138 | individualKeys.sort(); | ||
| 139 | vec3Keys.sort(); | ||
| 140 | quatKeys.sort(); | ||
| 141 | colorKeys.sort(); | ||
| 142 | |||
| 143 | // add to gui in the order they should appear | ||
| 144 | gui.add(settings, 'importSettings'); | ||
| 145 | gui.add(settings, 'exportSettings'); | ||
| 146 | addIndividualKeys(); | ||
| 147 | addFolders(); | ||
| 148 | |||
| 149 | // set the gui width to match the web window width | ||
| 150 | gui.width = window.innerWidth; | ||
| 151 | |||
| 152 | // 2-way binding stuff | ||
| 153 | // if (AUTO_UPDATE) { | ||
| 154 | // setInterval(manuallyUpdateDisplay, UPDATE_ALL_FREQUENCY); | ||
| 155 | // registerDOMElementsForListenerBlocking(); | ||
| 156 | // } | ||
| 157 | |||
| 158 | } | ||
| 159 | |||
| 160 | function addIndividualKeys() { | ||
| 161 | _.each(individualKeys, function(key) { | ||
| 162 | // temporary patch for not crashing when this goes below 0 | ||
| 163 | var controller; | ||
| 164 | |||
| 165 | if (key.indexOf('emitRate') > -1) { | ||
| 166 | controller = gui.add(settings, key).min(0); | ||
| 167 | } else { | ||
| 168 | controller = gui.add(settings, key); | ||
| 169 | } | ||
| 170 | |||
| 171 | // 2-way - need to fix not being able to input exact values if constantly listening | ||
| 172 | // controller.listen(); | ||
| 173 | |||
| 174 | // keep track of our controller | ||
| 175 | controllers.push(controller); | ||
| 176 | |||
| 177 | // hook into change events for this gui controller | ||
| 178 | controller.onChange(function(value) { | ||
| 179 | // Fires on every change, drag, keypress, etc. | ||
| 180 | writeDataToInterface(this.property, value); | ||
| 181 | }); | 406 | }); |
| 182 | 407 | if (overrideLoad) { | |
| 183 | }); | 408 | openEventBridge(); |
| 184 | } | ||
| 185 | |||
| 186 | function addFolders() { | ||
| 187 | _.each(colorKeys, function(key) { | ||
| 188 | createColorPicker(key); | ||
| 189 | }); | ||
| 190 | _.each(vec3Keys, function(key) { | ||
| 191 | createVec3Folder(key); | ||
| 192 | }); | ||
| 193 | _.each(quatKeys, function(key) { | ||
| 194 | createQuatFolder(key); | ||
| 195 | }); | ||
| 196 | } | ||
| 197 | |||
| 198 | function createColorPicker(key) { | ||
| 199 | var colorObject = settings[key]; | ||
| 200 | |||
| 201 | // Embed colpick's color picker into dat.GUI | ||
| 202 | var name = document.createElement('span'); | ||
| 203 | name.className = 'property-name'; | ||
| 204 | name.innerHTML = key; | ||
| 205 | |||
| 206 | var container = document.createElement('div'); | ||
| 207 | container.appendChild(name); | ||
| 208 | |||
| 209 | var $colPickContainer = $('<div>', { | ||
| 210 | id: key.toString(), | ||
| 211 | class: "color-box" | ||
| 212 | }); | ||
| 213 | $colPickContainer.css('background-color', "rgb(" + colorObject.red + "," + colorObject.green + "," + colorObject.blue + ")"); | ||
| 214 | container.appendChild($colPickContainer[0]); | ||
| 215 | |||
| 216 | var $li = $('<li>', { class: 'cr object color' }); | ||
| 217 | $li.append(container); | ||
| 218 | gui.__ul.appendChild($li[0]); | ||
| 219 | gui.onResize(); | ||
| 220 | |||
| 221 | $colPickContainer.colpick({ | ||
| 222 | colorScheme: 'dark', | ||
| 223 | layout: 'hex', | ||
| 224 | color: { r: colorObject.red, g: colorObject.green, b: colorObject.blue }, | ||
| 225 | onSubmit: function (hsb, hex, rgb, el) { | ||
| 226 | $(el).css('background-color', '#' + hex); | ||
| 227 | $(el).colpickHide(); | ||
| 228 | |||
| 229 | var obj = {}; | ||
| 230 | obj[key] = { red: rgb.r, green: rgb.g, blue: rgb.b }; | ||
| 231 | writeVec3ToInterface(obj); | ||
| 232 | } | 409 | } |
| 233 | }); | ||
| 234 | |||
| 235 | colpickKeys.push(key); | ||
| 236 | } | ||
| 237 | |||
| 238 | function createVec3Folder(category) { | ||
| 239 | var folder = gui.addFolder(category); | ||
| 240 | |||
| 241 | folder.add(settings[category], 'x').step(0.1).onChange(function(value) { | ||
| 242 | // Fires when a controller loses focus. | ||
| 243 | var obj = {}; | ||
| 244 | obj[category] = {}; | ||
| 245 | obj[category][this.property] = value; | ||
| 246 | obj[category].y = settings[category].y; | ||
| 247 | obj[category].z = settings[category].z; | ||
| 248 | writeVec3ToInterface(obj); | ||
| 249 | }); | ||
| 250 | |||
| 251 | folder.add(settings[category], 'y').step(0.1).onChange(function(value) { | ||
| 252 | // Fires when a controller loses focus. | ||
| 253 | var obj = {}; | ||
| 254 | obj[category] = {}; | ||
| 255 | obj[category].x = settings[category].x; | ||
| 256 | obj[category][this.property] = value; | ||
| 257 | obj[category].z = settings[category].z; | ||
| 258 | writeVec3ToInterface(obj); | ||
| 259 | }); | ||
| 260 | |||
| 261 | folder.add(settings[category], 'z').step(0.1).onChange(function(value) { | ||
| 262 | // Fires when a controller loses focus. | ||
| 263 | var obj = {}; | ||
| 264 | obj[category] = {}; | ||
| 265 | obj[category].y = settings[category].y; | ||
| 266 | obj[category].x = settings[category].x; | ||
| 267 | obj[category][this.property] = value; | ||
| 268 | writeVec3ToInterface(obj); | ||
| 269 | }); | ||
| 270 | |||
| 271 | folders.push(folder); | ||
| 272 | folder.open(); | ||
| 273 | } | ||
| 274 | |||
| 275 | function createQuatFolder(category) { | ||
| 276 | var folder = gui.addFolder(category); | ||
| 277 | |||
| 278 | folder.add(settings[category], 'x').step(0.1).onChange(function(value) { | ||
| 279 | // Fires when a controller loses focus. | ||
| 280 | var obj = {}; | ||
| 281 | obj[category] = {}; | ||
| 282 | obj[category][this.property] = value; | ||
| 283 | obj[category].y = settings[category].y; | ||
| 284 | obj[category].z = settings[category].z; | ||
| 285 | obj[category].w = settings[category].w; | ||
| 286 | writeVec3ToInterface(obj); | ||
| 287 | }); | ||
| 288 | |||
| 289 | folder.add(settings[category], 'y').step(0.1).onChange(function(value) { | ||
| 290 | // Fires when a controller loses focus. | ||
| 291 | var obj = {}; | ||
| 292 | obj[category] = {}; | ||
| 293 | obj[category].x = settings[category].x; | ||
| 294 | obj[category][this.property] = value; | ||
| 295 | obj[category].z = settings[category].z; | ||
| 296 | obj[category].w = settings[category].w; | ||
| 297 | writeVec3ToInterface(obj); | ||
| 298 | }); | ||
| 299 | |||
| 300 | folder.add(settings[category], 'z').step(0.1).onChange(function(value) { | ||
| 301 | // Fires when a controller loses focus. | ||
| 302 | var obj = {}; | ||
| 303 | obj[category] = {}; | ||
| 304 | obj[category].x = settings[category].x; | ||
| 305 | obj[category].y = settings[category].y; | ||
| 306 | obj[category][this.property] = value; | ||
| 307 | obj[category].w = settings[category].w; | ||
| 308 | writeVec3ToInterface(obj); | ||
| 309 | }); | ||
| 310 | |||
| 311 | folder.add(settings[category], 'w').step(0.1).onChange(function(value) { | ||
| 312 | // Fires when a controller loses focus. | ||
| 313 | var obj = {}; | ||
| 314 | obj[category] = {}; | ||
| 315 | obj[category].x = settings[category].x; | ||
| 316 | obj[category].y = settings[category].y; | ||
| 317 | obj[category].z = settings[category].z; | ||
| 318 | obj[category][this.property] = value; | ||
| 319 | writeVec3ToInterface(obj); | ||
| 320 | }); | ||
| 321 | |||
| 322 | folders.push(folder); | ||
| 323 | folder.open(); | ||
| 324 | } | ||
| 325 | |||
| 326 | function convertColorObjectToArray(colorObject) { | ||
| 327 | var colorArray = []; | ||
| 328 | |||
| 329 | _.each(colorObject, function(singleColor) { | ||
| 330 | colorArray.push(singleColor); | ||
| 331 | }); | ||
| 332 | |||
| 333 | return colorArray; | ||
| 334 | } | ||
| 335 | |||
| 336 | function convertColorArrayToObject(colorArray) { | ||
| 337 | var colorObject = { | ||
| 338 | red: colorArray[0], | ||
| 339 | green: colorArray[1], | ||
| 340 | blue: colorArray[2] | ||
| 341 | }; | ||
| 342 | |||
| 343 | return colorObject; | ||
| 344 | } | ||
| 345 | |||
| 346 | function writeDataToInterface(property, value) { | ||
| 347 | var data = {}; | ||
| 348 | data[property] = value; | ||
| 349 | |||
| 350 | var sendData = { | ||
| 351 | messageType: "settings_update", | ||
| 352 | updatedSettings: data | ||
| 353 | }; | ||
| 354 | |||
| 355 | var stringifiedData = JSON.stringify(sendData); | ||
| 356 | |||
| 357 | EventBridge.emitWebEvent(stringifiedData); | ||
| 358 | } | ||
| 359 | |||
| 360 | function writeVec3ToInterface(obj) { | ||
| 361 | var sendData = { | ||
| 362 | messageType: "settings_update", | ||
| 363 | updatedSettings: obj | ||
| 364 | }; | ||
| 365 | |||
| 366 | var stringifiedData = JSON.stringify(sendData); | ||
| 367 | |||
| 368 | EventBridge.emitWebEvent(stringifiedData); | ||
| 369 | } | ||
| 370 | |||
| 371 | function listenForSettingsUpdates() { | ||
| 372 | EventBridge.scriptEventReceived.connect(function(data) { | ||
| 373 | data = JSON.parse(data); | ||
| 374 | if (data.messageType === 'particle_settings') { | ||
| 375 | _.each(data.currentProperties, function(value, key) { | ||
| 376 | settings[key] = {}; | ||
| 377 | settings[key] = value; | ||
| 378 | }); | ||
| 379 | |||
| 380 | if (gui) { | ||
| 381 | manuallyUpdateDisplay(); | ||
| 382 | } else { | ||
| 383 | loadGUI(); | ||
| 384 | } | ||
| 385 | if (!active) { | ||
| 386 | // gui.toggleHide(); | ||
| 387 | gui.closed = false; | ||
| 388 | } | ||
| 389 | active = true; | ||
| 390 | |||
| 391 | } else if (data.messageType === "particle_close") { | ||
| 392 | // none of this seems to work. | ||
| 393 | // if (active) { | ||
| 394 | // gui.toggleHide(); | ||
| 395 | // } | ||
| 396 | active = false; | ||
| 397 | gui.closed = true; | ||
| 398 | } | ||
| 399 | }); | ||
| 400 | } | ||
| 401 | |||
| 402 | function manuallyUpdateDisplay() { | ||
| 403 | // Iterate over all controllers | ||
| 404 | // this is expensive, write a method for indiviudal controllers and use it when the value is different than a cached value, perhaps. | ||
| 405 | var i; | ||
| 406 | for (i in gui.__controllers) { | ||
| 407 | gui.__controllers[i].updateDisplay(); | ||
| 408 | } | ||
| 409 | |||
| 410 | // Update color pickers | ||
| 411 | for (i in colpickKeys) { | ||
| 412 | var color = settings[colpickKeys[i]]; | ||
| 413 | var $object = $('#' + colpickKeys[i]); | ||
| 414 | $object.css('background-color', "rgb(" + color.red + "," + color.green + "," + color.blue + ")"); | ||
| 415 | $object.colpickSetColor({ r: color.red, g: color.green, b: color.blue }, true); | ||
| 416 | } | ||
| 417 | } | ||
| 418 | |||
| 419 | function setGUIWidthToWindowWidth() { | ||
| 420 | if (gui !== null) { | ||
| 421 | gui.width = window.innerWidth; | ||
| 422 | } | ||
| 423 | } | ||
| 424 | |||
| 425 | function handleInputKeyPress(e) { | ||
| 426 | if (e.keyCode === 13) { | ||
| 427 | importSettings(); | ||
| 428 | } | ||
| 429 | return false; | ||
| 430 | } | ||
| 431 | |||
| 432 | function importSettings() { | ||
| 433 | var importInput = document.getElementById('importer-input'); | ||
| 434 | |||
| 435 | try { | ||
| 436 | var importedSettings = JSON.parse(importInput.value); | ||
| 437 | |||
| 438 | var keys = _.keys(importedSettings); | ||
| 439 | |||
| 440 | _.each(keys, function(key) { | ||
| 441 | var shouldAllow = _.contains(keysToAllow, key); | ||
| 442 | |||
| 443 | if (!shouldAllow) { | ||
| 444 | return; | ||
| 445 | } | ||
| 446 | |||
| 447 | settings[key] = importedSettings[key]; | ||
| 448 | }); | ||
| 449 | |||
| 450 | writeVec3ToInterface(settings); | ||
| 451 | |||
| 452 | manuallyUpdateDisplay(); | ||
| 453 | } catch (e) { | ||
| 454 | alert('Not properly formatted JSON'); | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | function prepareSettingsForExport() { | ||
| 459 | var keys = _.keys(settings); | ||
| 460 | |||
| 461 | var exportSettings = {}; | ||
| 462 | |||
| 463 | _.each(keys, function(key) { | ||
| 464 | var shouldAllow = _.contains(keysToAllow, key); | ||
| 465 | |||
| 466 | if (!shouldAllow) { | ||
| 467 | return; | ||
| 468 | } | ||
| 469 | |||
| 470 | if (key.indexOf('color') > -1) { | ||
| 471 | var colorObject = convertColorArrayToObject(settings[key]); | ||
| 472 | settings[key] = colorObject; | ||
| 473 | } | ||
| 474 | |||
| 475 | exportSettings[key] = settings[key]; | ||
| 476 | }); | ||
| 477 | |||
| 478 | return JSON.stringify(exportSettings, null, 4); | ||
| 479 | } | ||
| 480 | |||
| 481 | function showPreselectedPrompt() { | ||
| 482 | var elem = document.getElementById("exported-props"); | ||
| 483 | var exportSettings = prepareSettingsForExport(); | ||
| 484 | elem.innerHTML = ""; | ||
| 485 | var buttonnode = document.createElement('input'); | ||
| 486 | buttonnode.setAttribute('type', 'button'); | ||
| 487 | buttonnode.setAttribute('value', 'close'); | ||
| 488 | elem.appendChild(document.createTextNode("COPY THE BELOW FIELD TO CLIPBOARD:")); | ||
| 489 | elem.appendChild(document.createElement("br")); | ||
| 490 | var textAreaNode = document.createElement("textarea"); | ||
| 491 | textAreaNode.value = exportSettings; | ||
| 492 | elem.appendChild(textAreaNode); | ||
| 493 | elem.appendChild(document.createElement("br")); | ||
| 494 | elem.appendChild(buttonnode); | ||
| 495 | |||
| 496 | buttonnode.onclick = function() { | ||
| 497 | console.log("click"); | ||
| 498 | elem.innerHTML = ""; | ||
| 499 | }; | 410 | }; |
| 500 | 411 | })(); | |
| 501 | // window.alert("Ctrl-C to copy, then Enter.", prepareSettingsForExport()); | ||
| 502 | } | ||
| 503 | |||
| 504 | function removeContainerDomElement() { | ||
| 505 | var elem = document.getElementById("my-gui-container"); | ||
| 506 | elem.parentNode.removeChild(elem); | ||
| 507 | } | ||
| 508 | |||
| 509 | function removeListenerFromGUI(key) { | ||
| 510 | _.each(gui.__listening, function(controller, index) { | ||
| 511 | if (controller.property === key) { | ||
| 512 | storedController = controller; | ||
| 513 | gui.__listening.splice(index, 1); | ||
| 514 | } | ||
| 515 | }); | ||
| 516 | } | ||
| 517 | |||
| 518 | // the section below is to try to work at achieving two way bindings; | ||
| 519 | |||
| 520 | function addListenersBackToGUI() { | ||
| 521 | gui.__listening.push(storedController); | ||
| 522 | storedController = null; | ||
| 523 | } | ||
| 524 | |||
| 525 | function registerDOMElementsForListenerBlocking() { | ||
| 526 | _.each(gui.__controllers, function(controller) { | ||
| 527 | var input = controller.domElement.childNodes[0]; | ||
| 528 | input.addEventListener('focus', function() { | ||
| 529 | console.log('INPUT ELEMENT GOT FOCUS!' + controller.property); | ||
| 530 | removeListenerFromGUI(controller.property); | ||
| 531 | }); | ||
| 532 | }); | ||
| 533 | |||
| 534 | _.each(gui.__controllers, function(controller) { | ||
| 535 | var input = controller.domElement.childNodes[0]; | ||
| 536 | input.addEventListener('blur', function() { | ||
| 537 | console.log('INPUT ELEMENT GOT BLUR!' + controller.property); | ||
| 538 | addListenersBackToGUI(); | ||
| 539 | }); | ||
| 540 | }); | ||
| 541 | |||
| 542 | // also listen to inputs inside of folders | ||
| 543 | _.each(gui.__folders, function(folder) { | ||
| 544 | _.each(folder.__controllers, function(controller) { | ||
| 545 | var input = controller.__input; | ||
| 546 | input.addEventListener('focus', function() { | ||
| 547 | console.log('FOLDER ELEMENT GOT FOCUS!' + controller.property); | ||
| 548 | }); | ||
| 549 | }); | ||
| 550 | }); | ||
| 551 | } | ||
| @@ -4,23 +4,22 @@ · 3 unmodified lines | |||
| 4 | // Created by Eric Levin on 2/15/16 | 4 | // Created by Eric Levin on 2/15/16 |
| 5 | // Copyright 2016 High Fidelity, Inc. | 5 | // Copyright 2016 High Fidelity, Inc. |
| 6 | // Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window | 6 | // Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window |
| 7 | // This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. | 7 | // This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. |
| 8 | // | 8 | // |
| 9 | // Distributed under the Apache License, Version 2.0. | 9 | // Distributed under the Apache License, Version 2.0. |
| 10 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 10 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 11 | // | 11 | // |
| 12 | /*global window, alert, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ | 12 | /* global window, alert, ParticleExplorerTool, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ |
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); | 15 | var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); |
| 16 | 16 | ||
| 17 | ParticleExplorerTool = function() { | 17 | ParticleExplorerTool = function() { |
| 18 | var that = {}; | 18 | var that = {}; |
| 19 | |||
| 20 | that.createWebView = function() { | 19 | that.createWebView = function() { |
| 21 | that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); | 20 | that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); |
| 22 | that.webView.setVisible = function(value) {}; | 21 | that.webView.setVisible = function(value) {}; |
| 23 | that.webView.webEventReceived.connect(that.webEventReceived); | 22 | that.webView.webEventReceived.connect(that.webEventReceived); |
| 24 | } | 23 | } |
| 25 | 24 | ||
| 26 | that.destroyWebView = function() { | 25 | that.destroyWebView = function() { |
| @@ -38,6 +37,9 @@ ParticleExplorerTool = function() { · 11 unmodified lines | |||
| 38 | that.webEventReceived = function(data) { | 37 | that.webEventReceived = function(data) { |
| 39 | var data = JSON.parse(data); | 38 | var data = JSON.parse(data); |
| 40 | if (data.messageType === "settings_update") { | 39 | if (data.messageType === "settings_update") { |
| 40 | if (data.updatedSettings.emitOrientation) { | ||
| 41 | data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation); | ||
| 42 | } | ||
| 41 | Entities.editEntity(that.activeParticleEntity, data.updatedSettings); | 43 | Entities.editEntity(that.activeParticleEntity, data.updatedSettings); |
| 42 | } | 44 | } |
| 43 | } | 45 | } |
| @@ -261,7 +261,7 @@ · 260 unmodified lines | |||
| 261 | } | 261 | } |
| 262 | } | 262 | } |
| 263 | 263 | ||
| 264 | gpuTextures = Render.getConfig("Stats").textureGPUTransferCount; | 264 | gpuTextures = Render.getConfig("Stats").texturePendingGPUTransferCount; |
| 265 | 265 | ||
| 266 | // Update state | 266 | // Update state |
| 267 | if (!visible) { // Not visible because no recent downloads | 267 | if (!visible) { // Not visible because no recent downloads |
| @@ -290,7 +290,7 @@ · 22 unmodified lines | |||
| 290 | }, FADE_OUT_WAIT); | 290 | }, FADE_OUT_WAIT); |
| 291 | } | 291 | } |
| 292 | } else { | 292 | } else { |
| 293 | if (displayProgress < 100 || gpuTextures > 0) { // Was finished and waiting to fade out but have resumed so | 293 | if (displayProgress < 100 || gpuTextures > 0) { // Was finished and waiting to fade out but have resumed so |
| 294 | // don't fade out | 294 | // don't fade out |
| 295 | Script.clearInterval(fadeWaitTimer); | 295 | Script.clearInterval(fadeWaitTimer); |
| 296 | fadeWaitTimer = null; | 296 | fadeWaitTimer = null; |
| @@ -36,6 +36,8 @@ var shareAfterLogin = false; · 35 unmodified lines | |||
| 36 | var snapshotToShareAfterLogin = []; | 36 | var snapshotToShareAfterLogin = []; |
| 37 | var METAVERSE_BASE = location.metaverseServerUrl; | 37 | var METAVERSE_BASE = location.metaverseServerUrl; |
| 38 | var isLoggedIn; | 38 | var isLoggedIn; |
| 39 | var numGifSnapshotUploadsPending = 0; | ||
| 40 | var numStillSnapshotUploadsPending = 0; | ||
| 39 | 41 | ||
| 40 | // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, | 42 | // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, |
| 41 | // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS | 43 | // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS |
| @@ -56,6 +58,10 @@ function removeFromStoryIDsToMaybeDelete(story_id) { · 14 unmodified lines | |||
| 56 | print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); | 58 | print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); |
| 57 | } | 59 | } |
| 58 | 60 | ||
| 61 | function fileExtensionMatches(filePath, extension) { | ||
| 62 | return filePath.split('.').pop().toLowerCase() === extension; | ||
| 63 | } | ||
| 64 | |||
| 59 | function onMessage(message) { | 65 | function onMessage(message) { |
| 60 | // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: | 66 | // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: |
| 61 | // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) | 67 | // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) |
| @@ -93,11 +99,13 @@ function onMessage(message) { · 31 unmodified lines | |||
| 93 | Settings.setValue("previousStillSnapStoryID", ""); | 99 | Settings.setValue("previousStillSnapStoryID", ""); |
| 94 | Settings.setValue("previousStillSnapBlastingDisabled", false); | 100 | Settings.setValue("previousStillSnapBlastingDisabled", false); |
| 95 | Settings.setValue("previousStillSnapHifiSharingDisabled", false); | 101 | Settings.setValue("previousStillSnapHifiSharingDisabled", false); |
| 102 | Settings.setValue("previousStillSnapUrl", ""); | ||
| 96 | Settings.setValue("previousAnimatedSnapPath", ""); | 103 | Settings.setValue("previousAnimatedSnapPath", ""); |
| 97 | Settings.setValue("previousAnimatedSnapStoryID", ""); | 104 | Settings.setValue("previousAnimatedSnapStoryID", ""); |
| 98 | Settings.setValue("previousAnimatedSnapBlastingDisabled", false); | 105 | Settings.setValue("previousAnimatedSnapBlastingDisabled", false); |
| 99 | Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); | 106 | Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); |
| 100 | } | 107 | } |
| 108 | updatePrintPermissions(); | ||
| 101 | break; | 109 | break; |
| 102 | case 'login': | 110 | case 'login': |
| 103 | openLoginWindow(); | 111 | openLoginWindow(); |
| @@ -134,16 +142,24 @@ function onMessage(message) { · 30 unmodified lines | |||
| 134 | break; | 142 | break; |
| 135 | case 'shareSnapshotForUrl': | 143 | case 'shareSnapshotForUrl': |
| 136 | isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { | 144 | isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { |
| 137 | if (canShare) { | 145 | var isGif = fileExtensionMatches(message.data, "gif"); |
| 138 | isLoggedIn = Account.isLoggedIn(); | 146 | isLoggedIn = Account.isLoggedIn(); |
| 147 | isUploadingPrintableStill = canShare && Account.isLoggedIn() && !isGif; | ||
| 148 | if (canShare) { | ||
| 139 | if (isLoggedIn) { | 149 | if (isLoggedIn) { |
| 140 | print('Sharing snapshot with audience "for_url":', message.data); | 150 | print('Sharing snapshot with audience "for_url":', message.data); |
| 141 | Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref")); | 151 | Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref")); |
| 152 | if (isGif) { | ||
| 153 | numGifSnapshotUploadsPending++; | ||
| 154 | } else { | ||
| 155 | numStillSnapshotUploadsPending++; | ||
| 156 | } | ||
| 142 | } else { | 157 | } else { |
| 143 | shareAfterLogin = true; | 158 | shareAfterLogin = true; |
| 144 | snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") }); | 159 | snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") }); |
| 145 | } | 160 | } |
| 146 | } | 161 | } |
| 162 | updatePrintPermissions(); | ||
| 147 | }); | 163 | }); |
| 148 | break; | 164 | break; |
| 149 | case 'blastToConnections': | 165 | case 'blastToConnections': |
| @@ -203,6 +219,18 @@ function onMessage(message) { · 53 unmodified lines | |||
| 203 | }); | 219 | }); |
| 204 | } | 220 | } |
| 205 | break; | 221 | break; |
| 222 | case 'requestPrintButtonUpdate': | ||
| 223 | updatePrintPermissions(); | ||
| 224 | break; | ||
| 225 | case 'printToPolaroid': | ||
| 226 | if (Entities.canRez() || Entities.canRezTmp()) { | ||
| 227 | printToPolaroid(Settings.getValue("previousStillSnapUrl")); | ||
| 228 | removeFromStoryIDsToMaybeDelete(Settings.getValue("previousStillSnapStoryID")); | ||
| 229 | } | ||
| 230 | break; | ||
| 231 | case 'alertSnapshotLoadFailed': | ||
| 232 | snapshotFailedToLoad = true; | ||
| 233 | break; | ||
| 206 | case 'shareSnapshotWithEveryone': | 234 | case 'shareSnapshotWithEveryone': |
| 207 | isLoggedIn = Account.isLoggedIn(); | 235 | isLoggedIn = Account.isLoggedIn(); |
| 208 | if (isLoggedIn) { | 236 | if (isLoggedIn) { |
| @@ -250,6 +278,54 @@ function onMessage(message) { · 41 unmodified lines | |||
| 250 | } | 278 | } |
| 251 | } | 279 | } |
| 252 | 280 | ||
| 281 | var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/sound-print-photo.wav")); | ||
| 282 | var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx'; | ||
| 283 | |||
| 284 | function printToPolaroid(image_url) { | ||
| 285 | var polaroid_url = image_url; | ||
| 286 | |||
| 287 | var model_pos = Vec3.sum(MyAvatar.position, Vec3.multiply(1.25, Quat.getForward(MyAvatar.orientation))); | ||
| 288 | |||
| 289 | var model_q1 = MyAvatar.orientation; | ||
| 290 | var model_q2 = Quat.angleAxis(90, Quat.getRight(model_q1)); | ||
| 291 | var model_rot = Quat.multiply(model_q2, model_q1); | ||
| 292 | |||
| 293 | var properties = { | ||
| 294 | "type": 'Model', | ||
| 295 | "shapeType": 'box', | ||
| 296 | |||
| 297 | "name": "New Snapshot", | ||
| 298 | "description": "Printed from Snaps", | ||
| 299 | "modelURL": POLAROID_MODEL_URL, | ||
| 300 | |||
| 301 | "position": model_pos, | ||
| 302 | "rotation": model_rot, | ||
| 303 | |||
| 304 | "textures": JSON.stringify( { "tex.picture": polaroid_url } ), | ||
| 305 | |||
| 306 | "density": 200, | ||
| 307 | "restitution": 0.15, | ||
| 308 | "gravity": { "x": 0, "y": -4.5, "z": 0 }, | ||
| 309 | |||
| 310 | "velocity": { "x": 0, "y": 3.5, "z": 0 }, | ||
| 311 | "angularVelocity": { "x": -1.0, "y": 0, "z": -1.3 }, | ||
| 312 | |||
| 313 | "dynamic": true, | ||
| 314 | "collisionsWillMove": true, | ||
| 315 | |||
| 316 | "userData": { | ||
| 317 | "grabbableKey": { "grabbable" : true } | ||
| 318 | } | ||
| 319 | }; | ||
| 320 | |||
| 321 | var polaroid = Entities.addEntity(properties); | ||
| 322 | Audio.playSound(POLAROID_PRINT_SOUND, { | ||
| 323 | position: model_pos, | ||
| 324 | localOnly: false, | ||
| 325 | volume: 0.2 | ||
| 326 | }); | ||
| 327 | } | ||
| 328 | |||
| 253 | function fillImageDataFromPrevious() { | 329 | function fillImageDataFromPrevious() { |
| 254 | isLoggedIn = Account.isLoggedIn(); | 330 | isLoggedIn = Account.isLoggedIn(); |
| 255 | var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); | 331 | var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); |
| @@ -273,7 +349,8 @@ function fillImageDataFromPrevious() { · 17 unmodified lines | |||
| 273 | localPath: previousStillSnapPath, | 349 | localPath: previousStillSnapPath, |
| 274 | story_id: previousStillSnapStoryID, | 350 | story_id: previousStillSnapStoryID, |
| 275 | blastButtonDisabled: previousStillSnapBlastingDisabled, | 351 | blastButtonDisabled: previousStillSnapBlastingDisabled, |
| 276 | hifiButtonDisabled: previousStillSnapHifiSharingDisabled | 352 | hifiButtonDisabled: previousStillSnapHifiSharingDisabled, |
| 353 | errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg') | ||
| 277 | }); | 354 | }); |
| 278 | } | 355 | } |
| 279 | if (previousAnimatedSnapPath !== "") { | 356 | if (previousAnimatedSnapPath !== "") { |
| @@ -281,7 +358,8 @@ function fillImageDataFromPrevious() { · 1 unmodified line | |||
| 281 | localPath: previousAnimatedSnapPath, | 358 | localPath: previousAnimatedSnapPath, |
| 282 | story_id: previousAnimatedSnapStoryID, | 359 | story_id: previousAnimatedSnapStoryID, |
| 283 | blastButtonDisabled: previousAnimatedSnapBlastingDisabled, | 360 | blastButtonDisabled: previousAnimatedSnapBlastingDisabled, |
| 284 | hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled | 361 | hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled, |
| 362 | errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg') | ||
| 285 | }); | 363 | }); |
| 286 | } | 364 | } |
| 287 | } | 365 | } |
| @@ -305,26 +383,45 @@ function onButtonClicked() { · 17 unmodified lines | |||
| 305 | 383 | ||
| 306 | function snapshotUploaded(isError, reply) { | 384 | function snapshotUploaded(isError, reply) { |
| 307 | if (!isError) { | 385 | if (!isError) { |
| 308 | var replyJson = JSON.parse(reply); | 386 | var replyJson = JSON.parse(reply), |
| 309 | var storyID = replyJson.user_story.id; | 387 | storyID = replyJson.user_story.id, |
| 388 | imageURL = replyJson.user_story.details.image_url, | ||
| 389 | isGif = fileExtensionMatches(imageURL, "gif"), | ||
| 390 | ignoreGifSnapshotData = false, | ||
| 391 | ignoreStillSnapshotData = false; | ||
| 310 | storyIDsToMaybeDelete.push(storyID); | 392 | storyIDsToMaybeDelete.push(storyID); |
| 311 | var imageURL = replyJson.user_story.details.image_url; | ||
| 312 | var isGif = imageURL.split('.').pop().toLowerCase() === "gif"; | ||
| 313 | print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); | ||
| 314 | tablet.emitScriptEvent(JSON.stringify({ | ||
| 315 | type: "snapshot", | ||
| 316 | action: "snapshotUploadComplete", | ||
| 317 | story_id: storyID, | ||
| 318 | image_url: imageURL, | ||
| 319 | })); | ||
| 320 | if (isGif) { | 393 | if (isGif) { |
| 321 | Settings.setValue("previousAnimatedSnapStoryID", storyID); | 394 | numGifSnapshotUploadsPending--; |
| 395 | if (numGifSnapshotUploadsPending !== 0) { | ||
| 396 | ignoreGifSnapshotData = true; | ||
| 397 | } | ||
| 398 | } else { | ||
| 399 | numStillSnapshotUploadsPending--; | ||
| 400 | if (numStillSnapshotUploadsPending !== 0) { | ||
| 401 | ignoreStillSnapshotData = true; | ||
| 402 | } | ||
| 403 | } | ||
| 404 | if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) { | ||
| 405 | print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); | ||
| 406 | tablet.emitScriptEvent(JSON.stringify({ | ||
| 407 | type: "snapshot", | ||
| 408 | action: "snapshotUploadComplete", | ||
| 409 | story_id: storyID, | ||
| 410 | image_url: imageURL, | ||
| 411 | })); | ||
| 412 | if (isGif) { | ||
| 413 | Settings.setValue("previousAnimatedSnapStoryID", storyID); | ||
| 414 | } else { | ||
| 415 | Settings.setValue("previousStillSnapStoryID", storyID); | ||
| 416 | Settings.setValue("previousStillSnapUrl", imageURL); | ||
| 417 | } | ||
| 322 | } else { | 418 | } else { |
| 323 | Settings.setValue("previousStillSnapStoryID", storyID); | 419 | print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID); |
| 324 | } | 420 | } |
| 325 | } else { | 421 | } else { |
| 326 | print(reply); | 422 | print(reply); |
| 327 | } | 423 | } |
| 424 | isUploadingPrintableStill = false; | ||
| 328 | } | 425 | } |
| 329 | var href, domainId; | 426 | var href, domainId; |
| 330 | function takeSnapshot() { | 427 | function takeSnapshot() { |
| @@ -336,10 +433,16 @@ function takeSnapshot() { · 5 unmodified lines | |||
| 336 | Settings.setValue("previousStillSnapStoryID", ""); | 433 | Settings.setValue("previousStillSnapStoryID", ""); |
| 337 | Settings.setValue("previousStillSnapBlastingDisabled", false); | 434 | Settings.setValue("previousStillSnapBlastingDisabled", false); |
| 338 | Settings.setValue("previousStillSnapHifiSharingDisabled", false); | 435 | Settings.setValue("previousStillSnapHifiSharingDisabled", false); |
| 436 | Settings.setValue("previousStillSnapUrl", ""); | ||
| 339 | Settings.setValue("previousAnimatedSnapPath", ""); | 437 | Settings.setValue("previousAnimatedSnapPath", ""); |
| 340 | Settings.setValue("previousAnimatedSnapStoryID", ""); | 438 | Settings.setValue("previousAnimatedSnapStoryID", ""); |
| 341 | Settings.setValue("previousAnimatedSnapBlastingDisabled", false); | 439 | Settings.setValue("previousAnimatedSnapBlastingDisabled", false); |
| 342 | Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); | 440 | Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); |
| 441 | |||
| 442 | // Since we are taking a snapshot, we should make the print button appear to be loading/processing | ||
| 443 | snapshotFailedToLoad = false; | ||
| 444 | isUploadingPrintableStill = true; | ||
| 445 | updatePrintPermissions(); | ||
| 343 | 446 | ||
| 344 | // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. | 447 | // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. |
| 345 | // Turn it off now, before we start futzing with things (and possibly moving). | 448 | // Turn it off now, before we start futzing with things (and possibly moving). |
| @@ -379,8 +482,15 @@ function takeSnapshot() { · 33 unmodified lines | |||
| 379 | Menu.setIsOptionChecked("Overlays", false); | 482 | Menu.setIsOptionChecked("Overlays", false); |
| 380 | } | 483 | } |
| 381 | 484 | ||
| 485 | var snapActivateSound = SoundCache.getSound(Script.resolvePath("../../resources/sounds/snap.wav")); | ||
| 486 | |||
| 382 | // take snapshot (with no notification) | 487 | // take snapshot (with no notification) |
| 383 | Script.setTimeout(function () { | 488 | Script.setTimeout(function () { |
| 489 | Audio.playSound(snapActivateSound, { | ||
| 490 | position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, | ||
| 491 | localOnly: true, | ||
| 492 | volume: 1.0 | ||
| 493 | }); | ||
| 384 | HMD.closeTablet(); | 494 | HMD.closeTablet(); |
| 385 | Script.setTimeout(function () { | 495 | Script.setTimeout(function () { |
| 386 | Window.takeSnapshot(false, includeAnimated, 1.91); | 496 | Window.takeSnapshot(false, includeAnimated, 1.91); |
| @@ -566,8 +676,16 @@ function onUsernameChanged() { · 179 unmodified lines | |||
| 566 | snapshotToShareAfterLogin.forEach(function (element) { | 676 | snapshotToShareAfterLogin.forEach(function (element) { |
| 567 | print('Uploading snapshot after login:', element.path); | 677 | print('Uploading snapshot after login:', element.path); |
| 568 | Window.shareSnapshot(element.path, element.href); | 678 | Window.shareSnapshot(element.path, element.href); |
| 679 | var isGif = fileExtensionMatches(element.path, "gif"); | ||
| 680 | if (isGif) { | ||
| 681 | numGifSnapshotUploadsPending++; | ||
| 682 | } else { | ||
| 683 | numStillSnapshotUploadsPending++; | ||
| 684 | } | ||
| 569 | }); | 685 | }); |
| 570 | } | 686 | } |
| 687 | isUploadingPrintableStill = canShare; | ||
| 688 | updatePrintPermissions(); | ||
| 571 | }); | 689 | }); |
| 572 | 690 | ||
| 573 | shareAfterLogin = false; | 691 | shareAfterLogin = false; |
| @@ -575,6 +693,7 @@ function onUsernameChanged() { · 1 unmodified line | |||
| 575 | } | 693 | } |
| 576 | } | 694 | } |
| 577 | } | 695 | } |
| 696 | |||
| 578 | function snapshotLocationSet(location) { | 697 | function snapshotLocationSet(location) { |
| 579 | if (location !== "") { | 698 | if (location !== "") { |
| 580 | tablet.emitScriptEvent(JSON.stringify({ | 699 | tablet.emitScriptEvent(JSON.stringify({ |
| @@ -584,12 +703,44 @@ function snapshotLocationSet(location) { · 3 unmodified lines | |||
| 584 | } | 703 | } |
| 585 | } | 704 | } |
| 586 | 705 | ||
| 706 | function updatePrintPermissions() { | ||
| 707 | processRezPermissionChange(Entities.canRez() || Entities.canRezTmp()); | ||
| 708 | } | ||
| 709 | |||
| 710 | var snapshotFailedToLoad = false; | ||
| 711 | var isUploadingPrintableStill = false; | ||
| 712 | function processRezPermissionChange(canRez) { | ||
| 713 | var action = ""; | ||
| 714 | |||
| 715 | if (canRez && !snapshotFailedToLoad) { | ||
| 716 | if (Settings.getValue("previousStillSnapUrl")) { | ||
| 717 | action = 'setPrintButtonEnabled'; | ||
| 718 | } else if (isUploadingPrintableStill) { | ||
| 719 | action = 'setPrintButtonLoading'; | ||
| 720 | } else { | ||
| 721 | action = 'setPrintButtonDisabled'; | ||
| 722 | } | ||
| 723 | } else { | ||
| 724 | action = 'setPrintButtonDisabled'; | ||
| 725 | } | ||
| 726 | |||
| 727 | tablet.emitScriptEvent(JSON.stringify({ | ||
| 728 | type: "snapshot", | ||
| 729 | action : action | ||
| 730 | })); | ||
| 731 | } | ||
| 732 | |||
| 587 | button.clicked.connect(onButtonClicked); | 733 | button.clicked.connect(onButtonClicked); |
| 588 | buttonConnected = true; | 734 | buttonConnected = true; |
| 735 | |||
| 589 | Window.snapshotShared.connect(snapshotUploaded); | 736 | Window.snapshotShared.connect(snapshotUploaded); |
| 590 | tablet.screenChanged.connect(onTabletScreenChanged); | 737 | tablet.screenChanged.connect(onTabletScreenChanged); |
| 591 | GlobalServices.myUsernameChanged.connect(onUsernameChanged); | 738 | GlobalServices.myUsernameChanged.connect(onUsernameChanged); |
| 592 | Snapshot.snapshotLocationSet.connect(snapshotLocationSet); | 739 | Snapshot.snapshotLocationSet.connect(snapshotLocationSet); |
| 740 | |||
| 741 | Entities.canRezChanged.connect(updatePrintPermissions); | ||
| 742 | Entities.canRezTmpChanged.connect(updatePrintPermissions); | ||
| 743 | |||
| 593 | Script.scriptEnding.connect(function () { | 744 | Script.scriptEnding.connect(function () { |
| 594 | if (buttonConnected) { | 745 | if (buttonConnected) { |
| 595 | button.clicked.disconnect(onButtonClicked); | 746 | button.clicked.disconnect(onButtonClicked); |
| @@ -601,6 +752,9 @@ Script.scriptEnding.connect(function () { · 5 unmodified lines | |||
| 601 | Window.snapshotShared.disconnect(snapshotUploaded); | 752 | Window.snapshotShared.disconnect(snapshotUploaded); |
| 602 | tablet.screenChanged.disconnect(onTabletScreenChanged); | 753 | tablet.screenChanged.disconnect(onTabletScreenChanged); |
| 603 | Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); | 754 | Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); |
| 755 | |||
| 756 | Entities.canRezChanged.disconnect(processRezPermissionChange); | ||
| 757 | Entities.canRezTmpChanged.disconnect(processRezPermissionChange); | ||
| 604 | }); | 758 | }); |
| 605 | 759 | ||
| 606 | }()); // END LOCAL_SCOPE | 760 | }()); // END LOCAL_SCOPE |
| @@ -188,16 +188,12 @@ · 187 unmodified lines | |||
| 188 | gTablet.updateAudioBar(getMicLevel()); | 188 | gTablet.updateAudioBar(getMicLevel()); |
| 189 | } | 189 | } |
| 190 | 190 | ||
| 191 | if (validCheckTime - now > MSECS_PER_SEC/4) { | 191 | if (now - validCheckTime > MSECS_PER_SEC) { |
| 192 | //each 250ms should be just fine | 192 | validCheckTime = now; |
| 193 | updateTabletWidthFromSettings(); | 193 | updateTabletWidthFromSettings(); |
| 194 | if (UIWebTablet) { | 194 | if (UIWebTablet) { |
| 195 | UIWebTablet.setLandscape(landscape); | 195 | UIWebTablet.setLandscape(landscape); |
| 196 | } | 196 | } |
| 197 | } | ||
| 198 | |||
| 199 | if (validCheckTime - now > MSECS_PER_SEC) { | ||
| 200 | validCheckTime = now; | ||
| 201 | if (tabletRezzed && UIWebTablet && !tabletIsValid()) { | 197 | if (tabletRezzed && UIWebTablet && !tabletIsValid()) { |
| 202 | // when we switch domains, the tablet entity gets destroyed and recreated. this causes | 198 | // when we switch domains, the tablet entity gets destroyed and recreated. this causes |
| 203 | // the overlay to be deleted, but not recreated. If the overlay is deleted for this or any | 199 | // the overlay to be deleted, but not recreated. If the overlay is deleted for this or any |
| @@ -821,6 +821,17 @@ for (var key in trayIcons) { · 820 unmodified lines | |||
| 821 | 821 | ||
| 822 | const notificationIcon = path.join(__dirname, '../resources/console-notification.png'); | 822 | const notificationIcon = path.join(__dirname, '../resources/console-notification.png'); |
| 823 | 823 | ||
| 824 | function isProcessRunning(pid) { | ||
| 825 | try { | ||
| 826 | // Sending a signal of 0 is effectively a NOOP. | ||
| 827 | // If sending the signal is successful, kill will return true. | ||
| 828 | // If the process is not running, an exception will be thrown. | ||
| 829 | return process.kill(pid, 0); | ||
| 830 | } catch (e) { | ||
| 831 | } | ||
| 832 | return false; | ||
| 833 | } | ||
| 834 | |||
| 824 | function onContentLoaded() { | 835 | function onContentLoaded() { |
| 825 | // Disable splash window for now. | 836 | // Disable splash window for now. |
| 826 | // maybeShowSplash(); | 837 | // maybeShowSplash(); |
| @@ -882,31 +893,18 @@ function onContentLoaded() { · 55 unmodified lines | |||
| 882 | startInterface(); | 893 | startInterface(); |
| 883 | } | 894 | } |
| 884 | 895 | ||
| 885 | // If we were launched with the shutdownWatcher option, then we need to watch for the interface app | 896 | // If we were launched with the shutdownWith option, then we need to shutdown when that process (pid) |
| 886 | // shutting down. The interface app will regularly update a running state file which we will check. | 897 | // is no longer running. |
| 887 | // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. | 898 | if (argv.shutdownWith) { |
| 888 | if (argv.shutdownWatcher) { | 899 | let pid = argv.shutdownWith; |
| 889 | log.debug("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher); | 900 | console.log("Shutting down with process: ", pid); |
| 890 | var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates | 901 | let checkProcessInterval = setInterval(function() { |
| 891 | var firstAttemptToCheck = new Date().getTime(); | 902 | let isRunning = isProcessRunning(pid); |
| 892 | var shutdownWatchInterval = setInterval(function(){ | 903 | if (!isRunning) { |
| 893 | var stats = fs.stat(argv.shutdownWatcher, function(err, stats) { | 904 | log.debug("Watched process is no longer running, shutting down"); |
| 894 | if (err) { | 905 | clearTimeout(checkProcessInterval); |
| 895 | var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck; | 906 | forcedShutdown(); |
| 896 | if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) { | 907 | } |
| 897 | log.debug("Running state file is missing, assume interface has shutdown... shutting down snadbox."); | ||
| 898 | forcedShutdown(); | ||
| 899 | clearTimeout(shutdownWatchInterval); | ||
| 900 | } | ||
| 901 | } else { | ||
| 902 | var sinceEdit = new Date().getTime() - stats.mtime.getTime(); | ||
| 903 | if (sinceEdit > MAX_TIME_SINCE_EDIT) { | ||
| 904 | log.debug("Running state of interface hasn't updated in MAX time... shutting down."); | ||
| 905 | forcedShutdown(); | ||
| 906 | clearTimeout(shutdownWatchInterval); | ||
| 907 | } | ||
| 908 | } | ||
| 909 | }); | ||
| 910 | }, 1000); | 908 | }, 1000); |
| 911 | } | 909 | } |
| 912 | } | 910 | } |
| @@ -122,6 +122,10 @@ int main(int argc, char** argv) { · 121 unmodified lines | |||
| 122 | glm::mat4(), | 122 | glm::mat4(), |
| 123 | glm::mat4(), | 123 | glm::mat4(), |
| 124 | glm::mat4(), | 124 | glm::mat4(), |
| 125 | glm::mat4(), | ||
| 126 | glm::mat4(), | ||
| 127 | glm::mat4(), | ||
| 128 | glm::mat4(), | ||
| 125 | glm::mat4() | 129 | glm::mat4() |
| 126 | }; | 130 | }; |
| 127 | 131 | ||
| @@ -144,6 +148,10 @@ int main(int argc, char** argv) { · 16 unmodified lines | |||
| 144 | glm::mat4(), | 148 | glm::mat4(), |
| 145 | glm::mat4(), | 149 | glm::mat4(), |
| 146 | glm::mat4(), | 150 | glm::mat4(), |
| 151 | glm::mat4(), | ||
| 152 | glm::mat4(), | ||
| 153 | glm::mat4(), | ||
| 154 | glm::mat4(), | ||
| 147 | glm::mat4() | 155 | glm::mat4() |
| 148 | }; | 156 | }; |
| 149 | 157 | ||
| @@ -1,15 +1,9 @@ | |||
| 1 | # Declare dependencies | ||
| 2 | macro (SETUP_TESTCASE_DEPENDENCIES) | ||
| 3 | # link in the shared libraries | ||
| 4 | link_hifi_libraries(shared ktx gpu image) | ||
| 1 | 5 | ||
| 2 | set(TARGET_NAME ktx-test) | 6 | package_libraries_for_deployment() |
| 3 | 7 | endmacro () | |
| 4 | if (WIN32) | ||
| 5 | SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") | ||
| 6 | endif() | ||
| 7 | 8 | ||
| 8 | # This is not a testcase -- just set it up as a regular hifi project | 9 | setup_hifi_testcase() |
| 9 | setup_hifi_project(Quick Gui OpenGL) | ||
| 10 | set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") | ||
| 11 | |||
| 12 | # link in the shared libraries | ||
| 13 | link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image) | ||
| 14 | |||
| 15 | package_libraries_for_deployment() | ||
| @@ -0,0 +1,195 @@ | |||
| 1 | // | ||
| 2 | // Created by Bradley Austin Davis on 2016/07/01 | ||
| 3 | // Copyright 2014 High Fidelity, Inc. | ||
| 4 | // | ||
| 5 | // Distributed under the Apache License, Version 2.0. | ||
| 6 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 7 | // | ||
| 8 | |||
| 9 | #include "KtxTests.h" | ||
| 10 | |||
| 11 | #include <mutex> | ||
| 12 | |||
| 13 | #include <QtTest/QtTest> | ||
| 14 | |||
| 15 | #include <ktx/KTX.h> | ||
| 16 | #include <gpu/Texture.h> | ||
| 17 | #include <image/Image.h> | ||
| 18 | |||
| 19 | |||
| 20 | QTEST_GUILESS_MAIN(KtxTests) | ||
| 21 | |||
| 22 | QString getRootPath() { | ||
| 23 | static std::once_flag once; | ||
| 24 | static QString result; | ||
| 25 | std::call_once(once, [&] { | ||
| 26 | QFileInfo file(__FILE__); | ||
| 27 | QDir parent = file.absolutePath(); | ||
| 28 | result = QDir::cleanPath(parent.currentPath() + "/../../.."); | ||
| 29 | }); | ||
| 30 | return result; | ||
| 31 | } | ||
| 32 | |||
| 33 | void KtxTests::initTestCase() { | ||
| 34 | } | ||
| 35 | |||
| 36 | void KtxTests::cleanupTestCase() { | ||
| 37 | } | ||
| 38 | |||
| 39 | void KtxTests::testKhronosCompressionFunctions() { | ||
| 40 | using namespace khronos::gl::texture; | ||
| 41 | QCOMPARE(evalAlignedCompressedBlockCount<4>(0), (uint32_t)0x0); | ||
| 42 | QCOMPARE(evalAlignedCompressedBlockCount<4>(1), (uint32_t)0x1); | ||
| 43 | QCOMPARE(evalAlignedCompressedBlockCount<4>(4), (uint32_t)0x1); | ||
| 44 | QCOMPARE(evalAlignedCompressedBlockCount<4>(5), (uint32_t)0x2); | ||
| 45 | QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x00), (uint32_t)0x00); | ||
| 46 | QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x01), (uint32_t)0x01); | ||
| 47 | QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x04), (uint32_t)0x01); | ||
| 48 | QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x05), (uint32_t)0x02); | ||
| 49 | QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x1000), (uint32_t)0x400); | ||
| 50 | |||
| 51 | QVERIFY_EXCEPTION_THROWN(evalCompressedBlockCount(InternalFormat::RGBA8, 0x00), std::runtime_error); | ||
| 52 | } | ||
| 53 | |||
| 54 | void KtxTests::testKtxEvalFunctions() { | ||
| 55 | QCOMPARE(sizeof(ktx::Header), (size_t)64); | ||
| 56 | QCOMPARE(ktx::evalPadding(0x0), (uint8_t)0); | ||
| 57 | QCOMPARE(ktx::evalPadding(0x1), (uint8_t)3); | ||
| 58 | QCOMPARE(ktx::evalPadding(0x2), (uint8_t)2); | ||
| 59 | QCOMPARE(ktx::evalPadding(0x3), (uint8_t)1); | ||
| 60 | QCOMPARE(ktx::evalPadding(0x4), (uint8_t)0); | ||
| 61 | QCOMPARE(ktx::evalPadding(0x400), (uint8_t)0); | ||
| 62 | QCOMPARE(ktx::evalPadding(0x401), (uint8_t)3); | ||
| 63 | QCOMPARE(ktx::evalPaddedSize(0x0), 0x0); | ||
| 64 | QCOMPARE(ktx::evalPaddedSize(0x1), 0x4); | ||
| 65 | QCOMPARE(ktx::evalPaddedSize(0x2), 0x4); | ||
| 66 | QCOMPARE(ktx::evalPaddedSize(0x3), 0x4); | ||
| 67 | QCOMPARE(ktx::evalPaddedSize(0x4), 0x4); | ||
| 68 | QCOMPARE(ktx::evalPaddedSize(0x400), 0x400); | ||
| 69 | QCOMPARE(ktx::evalPaddedSize(0x401), 0x404); | ||
| 70 | QCOMPARE(ktx::evalAlignedCount((uint32_t)0x0), (uint32_t)0x0); | ||
| 71 | QCOMPARE(ktx::evalAlignedCount((uint32_t)0x1), (uint32_t)0x1); | ||
| 72 | QCOMPARE(ktx::evalAlignedCount((uint32_t)0x4), (uint32_t)0x1); | ||
| 73 | QCOMPARE(ktx::evalAlignedCount((uint32_t)0x5), (uint32_t)0x2); | ||
| 74 | } | ||
| 75 | |||
| 76 | void KtxTests::testKtxSerialization() { | ||
| 77 | const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png"; | ||
| 78 | QImage image(TEST_IMAGE); | ||
| 79 | gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true); | ||
| 80 | auto ktxMemory = gpu::Texture::serialize(*testTexture); | ||
| 81 | QVERIFY(ktxMemory.get()); | ||
| 82 | |||
| 83 | // Serialize the image to a file | ||
| 84 | QTemporaryFile TEST_IMAGE_KTX; | ||
| 85 | { | ||
| 86 | const auto& ktxStorage = ktxMemory->getStorage(); | ||
| 87 | QVERIFY(ktx::KTX::validate(ktxStorage)); | ||
| 88 | QVERIFY(ktxMemory->isValid()); | ||
| 89 | |||
| 90 | auto& outFile = TEST_IMAGE_KTX; | ||
| 91 | if (!outFile.open()) { | ||
| 92 | QFAIL("Unable to open file"); | ||
| 93 | } | ||
| 94 | auto ktxSize = ktxStorage->size(); | ||
| 95 | outFile.resize(ktxSize); | ||
| 96 | auto dest = outFile.map(0, ktxSize); | ||
| 97 | memcpy(dest, ktxStorage->data(), ktxSize); | ||
| 98 | outFile.unmap(dest); | ||
| 99 | outFile.close(); | ||
| 100 | } | ||
| 101 | |||
| 102 | |||
| 103 | { | ||
| 104 | auto ktxStorage = std::make_shared<storage::FileStorage>(TEST_IMAGE_KTX.fileName()); | ||
| 105 | QVERIFY(ktx::KTX::validate(ktxStorage)); | ||
| 106 | auto ktxFile = ktx::KTX::create(ktxStorage); | ||
| 107 | QVERIFY(ktxFile.get()); | ||
| 108 | QVERIFY(ktxFile->isValid()); | ||
| 109 | { | ||
| 110 | const auto& memStorage = ktxMemory->getStorage(); | ||
| 111 | const auto& fileStorage = ktxFile->getStorage(); | ||
| 112 | QVERIFY(memStorage->size() == fileStorage->size()); | ||
| 113 | QVERIFY(memStorage->data() != fileStorage->data()); | ||
| 114 | QVERIFY(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size())); | ||
| 115 | QVERIFY(ktxFile->_images.size() == ktxMemory->_images.size()); | ||
| 116 | auto imageCount = ktxFile->_images.size(); | ||
| 117 | auto startMemory = ktxMemory->_storage->data(); | ||
| 118 | auto startFile = ktxFile->_storage->data(); | ||
| 119 | for (size_t i = 0; i < imageCount; ++i) { | ||
| 120 | auto memImages = ktxMemory->_images[i]; | ||
| 121 | auto fileImages = ktxFile->_images[i]; | ||
| 122 | QVERIFY(memImages._padding == fileImages._padding); | ||
| 123 | QVERIFY(memImages._numFaces == fileImages._numFaces); | ||
| 124 | QVERIFY(memImages._imageSize == fileImages._imageSize); | ||
| 125 | QVERIFY(memImages._faceSize == fileImages._faceSize); | ||
| 126 | QVERIFY(memImages._faceBytes.size() == memImages._numFaces); | ||
| 127 | QVERIFY(fileImages._faceBytes.size() == fileImages._numFaces); | ||
| 128 | auto faceCount = fileImages._numFaces; | ||
| 129 | for (uint32_t face = 0; face < faceCount; ++face) { | ||
| 130 | auto memFace = memImages._faceBytes[face]; | ||
| 131 | auto memOffset = memFace - startMemory; | ||
| 132 | auto fileFace = fileImages._faceBytes[face]; | ||
| 133 | auto fileOffset = fileFace - startFile; | ||
| 134 | QVERIFY(memOffset % 4 == 0); | ||
| 135 | QVERIFY(memOffset == fileOffset); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString()); | ||
| 141 | } | ||
| 142 | |||
| 143 | #if 0 | ||
| 144 | |||
| 145 | static const QString TEST_FOLDER { "H:/ktx_cacheold" }; | ||
| 146 | //static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" }; | ||
| 147 | |||
| 148 | //static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" }; | ||
| 149 | static const QString EXTENSIONS { "*.ktx" }; | ||
| 150 | |||
| 151 | int mainTemp(int, char**) { | ||
| 152 | qInstallMessageHandler(messageHandler); | ||
| 153 | auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS }); | ||
| 154 | for (auto fileInfo : fileInfoList) { | ||
| 155 | qDebug() << fileInfo.filePath(); | ||
| 156 | std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } }; | ||
| 157 | |||
| 158 | if (!ktx::KTX::validate(storage)) { | ||
| 159 | qDebug() << "KTX invalid"; | ||
| 160 | } | ||
| 161 | |||
| 162 | auto ktxFile = ktx::KTX::create(storage); | ||
| 163 | ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor(); | ||
| 164 | |||
| 165 | qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs"; | ||
| 166 | for (const auto& kv : ktxDescriptor.keyValues) { | ||
| 167 | qDebug() << "\t" << kv._key.c_str(); | ||
| 168 | } | ||
| 169 | |||
| 170 | auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY); | ||
| 171 | if (offsetToMinMipKV) { | ||
| 172 | auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV; | ||
| 173 | auto minMipLevelAvailable = *data; | ||
| 174 | qDebug() << "\tMin mip available " << minMipLevelAvailable; | ||
| 175 | assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels); | ||
| 176 | } | ||
| 177 | auto storageSize = storage->size(); | ||
| 178 | for (const auto& faceImageDesc : ktxDescriptor.images) { | ||
| 179 | //assert(0 == (faceImageDesc._faceSize % 4)); | ||
| 180 | for (const auto& faceOffset : faceImageDesc._faceOffsets) { | ||
| 181 | assert(0 == (faceOffset % 4)); | ||
| 182 | auto faceEndOffset = faceOffset + faceImageDesc._faceSize; | ||
| 183 | assert(faceEndOffset <= storageSize); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | for (const auto& faceImage : ktxFile->_images) { | ||
| 188 | for (const ktx::Byte* faceBytes : faceImage._faceBytes) { | ||
| 189 | assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4)); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | return 0; | ||
| 194 | } | ||
| 195 | #endif | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | // | ||
| 2 | // Created by Bradley Austin Davis on 2016/07/01 | ||
| 3 | // Copyright 2014 High Fidelity, Inc. | ||
| 4 | // | ||
| 5 | // Distributed under the Apache License, Version 2.0. | ||
| 6 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 7 | // | ||
| 8 | |||
| 9 | #include <QtCore/QObject> | ||
| 10 | |||
| 11 | class KtxTests : public QObject { | ||
| 12 | Q_OBJECT | ||
| 13 | private slots: | ||
| 14 | void initTestCase(); | ||
| 15 | void cleanupTestCase(); | ||
| 16 | void testKtxEvalFunctions(); | ||
| 17 | void testKhronosCompressionFunctions(); | ||
| 18 | void testKtxSerialization(); | ||
| 19 | }; | ||
| 20 | |||
| 21 | |||
| @@ -1,223 +0,0 @@ | |||
| 1 | // | ||
| 2 | // Created by Bradley Austin Davis on 2016/07/01 | ||
| 3 | // Copyright 2014 High Fidelity, Inc. | ||
| 4 | // | ||
| 5 | // Distributed under the Apache License, Version 2.0. | ||
| 6 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 7 | // | ||
| 8 | |||
| 9 | #include <iostream> | ||
| 10 | #include <string> | ||
| 11 | #include <vector> | ||
| 12 | #include <sstream> | ||
| 13 | |||
| 14 | #include <QProcessEnvironment> | ||
| 15 | |||
| 16 | #include <QtCore/QDir> | ||
| 17 | #include <QtCore/QElapsedTimer> | ||
| 18 | #include <QtCore/QLoggingCategory> | ||
| 19 | #include <QtCore/QRegularExpression> | ||
| 20 | #include <QtCore/QSettings> | ||
| 21 | #include <QtCore/QTimer> | ||
| 22 | #include <QtCore/QThread> | ||
| 23 | #include <QtCore/QThreadPool> | ||
| 24 | #include <QtCore/QSaveFile> | ||
| 25 | |||
| 26 | #include <QtGui/QGuiApplication> | ||
| 27 | #include <QtGui/QResizeEvent> | ||
| 28 | #include <QtGui/QWindow> | ||
| 29 | |||
| 30 | #include <QtWidgets/QFileDialog> | ||
| 31 | #include <QtWidgets/QInputDialog> | ||
| 32 | #include <QtWidgets/QMessageBox> | ||
| 33 | #include <QtWidgets/QApplication> | ||
| 34 | |||
| 35 | #include <shared/RateCounter.h> | ||
| 36 | #include <shared/NetworkUtils.h> | ||
| 37 | #include <shared/FileLogger.h> | ||
| 38 | #include <shared/FileUtils.h> | ||
| 39 | #include <StatTracker.h> | ||
| 40 | #include <LogHandler.h> | ||
| 41 | |||
| 42 | #include <gpu/Texture.h> | ||
| 43 | #include <gl/Config.h> | ||
| 44 | #include <model/TextureMap.h> | ||
| 45 | #include <ktx/KTX.h> | ||
| 46 | #include <image/Image.h> | ||
| 47 | |||
| 48 | QSharedPointer<FileLogger> logger; | ||
| 49 | |||
| 50 | gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true); | ||
| 51 | |||
| 52 | |||
| 53 | void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { | ||
| 54 | QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message); | ||
| 55 | |||
| 56 | if (!logMessage.isEmpty()) { | ||
| 57 | #ifdef Q_OS_WIN | ||
| 58 | OutputDebugStringA(logMessage.toLocal8Bit().constData()); | ||
| 59 | OutputDebugStringA("\n"); | ||
| 60 | #endif | ||
| 61 | if (logger) { | ||
| 62 | logger->addMessage(qPrintable(logMessage + "\n")); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | const char * LOG_FILTER_RULES = R"V0G0N( | ||
| 68 | hifi.gpu=true | ||
| 69 | )V0G0N"; | ||
| 70 | |||
| 71 | QString getRootPath() { | ||
| 72 | static std::once_flag once; | ||
| 73 | static QString result; | ||
| 74 | std::call_once(once, [&] { | ||
| 75 | QFileInfo file(__FILE__); | ||
| 76 | QDir parent = file.absolutePath(); | ||
| 77 | result = QDir::cleanPath(parent.currentPath() + "/../../.."); | ||
| 78 | }); | ||
| 79 | return result; | ||
| 80 | } | ||
| 81 | |||
| 82 | const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png"; | ||
| 83 | const QString TEST_IMAGE_KTX = getRootPath() + "/scripts/developer/tests/cube_texture.ktx"; | ||
| 84 | |||
| 85 | int main(int argc, char** argv) { | ||
| 86 | QApplication app(argc, argv); | ||
| 87 | QCoreApplication::setApplicationName("KTX"); | ||
| 88 | QCoreApplication::setOrganizationName("High Fidelity"); | ||
| 89 | QCoreApplication::setOrganizationDomain("highfidelity.com"); | ||
| 90 | logger.reset(new FileLogger()); | ||
| 91 | |||
| 92 | Q_ASSERT(ktx::evalPadding(0) == 0); | ||
| 93 | Q_ASSERT(ktx::evalPadding(1) == 3); | ||
| 94 | Q_ASSERT(ktx::evalPadding(2) == 2); | ||
| 95 | Q_ASSERT(ktx::evalPadding(3) == 1); | ||
| 96 | Q_ASSERT(ktx::evalPadding(4) == 0); | ||
| 97 | Q_ASSERT(ktx::evalPadding(1024) == 0); | ||
| 98 | Q_ASSERT(ktx::evalPadding(1025) == 3); | ||
| 99 | Q_ASSERT(ktx::evalPaddedSize(0) == 0); | ||
| 100 | Q_ASSERT(ktx::evalPaddedSize(1) == 4); | ||
| 101 | Q_ASSERT(ktx::evalPaddedSize(2) == 4); | ||
| 102 | Q_ASSERT(ktx::evalPaddedSize(3) == 4); | ||
| 103 | Q_ASSERT(ktx::evalPaddedSize(4) == 4); | ||
| 104 | Q_ASSERT(ktx::evalPaddedSize(1024) == 1024); | ||
| 105 | Q_ASSERT(ktx::evalPaddedSize(1025) == 1028); | ||
| 106 | Q_ASSERT(sizeof(ktx::Header) == 12 + (sizeof(uint32_t) * 13)); | ||
| 107 | |||
| 108 | DependencyManager::set<tracing::Tracer>(); | ||
| 109 | qInstallMessageHandler(messageHandler); | ||
| 110 | QLoggingCategory::setFilterRules(LOG_FILTER_RULES); | ||
| 111 | |||
| 112 | QImage image(TEST_IMAGE); | ||
| 113 | gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true); | ||
| 114 | |||
| 115 | auto ktxMemory = gpu::Texture::serialize(*testTexture); | ||
| 116 | { | ||
| 117 | const auto& ktxStorage = ktxMemory->getStorage(); | ||
| 118 | Q_ASSERT_X(ktx::KTX::validate(ktxStorage), __FUNCTION__, "KTX storage validation failed"); | ||
| 119 | Q_ASSERT_X(ktxMemory->isValid(), __FUNCTION__, "KTX self-validation failed"); | ||
| 120 | QSaveFile outFile(TEST_IMAGE_KTX); | ||
| 121 | if (!outFile.open(QFile::WriteOnly)) { | ||
| 122 | throw std::runtime_error("Unable to open file"); | ||
| 123 | } | ||
| 124 | auto ktxSize = ktxStorage->size(); | ||
| 125 | outFile.resize(ktxSize); | ||
| 126 | auto dest = outFile.map(0, ktxSize); | ||
| 127 | memcpy(dest, ktxStorage->data(), ktxSize); | ||
| 128 | outFile.unmap(dest); | ||
| 129 | outFile.commit(); | ||
| 130 | } | ||
| 131 | |||
| 132 | { | ||
| 133 | auto ktxFile = ktx::KTX::create(std::shared_ptr<storage::Storage>(new storage::FileStorage(TEST_IMAGE_KTX))); | ||
| 134 | { | ||
| 135 | const auto& memStorage = ktxMemory->getStorage(); | ||
| 136 | const auto& fileStorage = ktxFile->getStorage(); | ||
| 137 | Q_ASSERT(memStorage->size() == fileStorage->size()); | ||
| 138 | Q_ASSERT(memStorage->data() != fileStorage->data()); | ||
| 139 | Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size())); | ||
| 140 | Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size()); | ||
| 141 | auto imageCount = ktxFile->_images.size(); | ||
| 142 | auto startMemory = ktxMemory->_storage->data(); | ||
| 143 | auto startFile = ktxFile->_storage->data(); | ||
| 144 | for (size_t i = 0; i < imageCount; ++i) { | ||
| 145 | auto memImages = ktxMemory->_images[i]; | ||
| 146 | auto fileImages = ktxFile->_images[i]; | ||
| 147 | Q_ASSERT(memImages._padding == fileImages._padding); | ||
| 148 | Q_ASSERT(memImages._numFaces == fileImages._numFaces); | ||
| 149 | Q_ASSERT(memImages._imageSize == fileImages._imageSize); | ||
| 150 | Q_ASSERT(memImages._faceSize == fileImages._faceSize); | ||
| 151 | Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces); | ||
| 152 | Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces); | ||
| 153 | auto faceCount = fileImages._numFaces; | ||
| 154 | for (uint32_t face = 0; face < faceCount; ++face) { | ||
| 155 | auto memFace = memImages._faceBytes[face]; | ||
| 156 | auto memOffset = memFace - startMemory; | ||
| 157 | auto fileFace = fileImages._faceBytes[face]; | ||
| 158 | auto fileOffset = fileFace - startFile; | ||
| 159 | Q_ASSERT(memOffset % 4 == 0); | ||
| 160 | Q_ASSERT(memOffset == fileOffset); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | } | ||
| 164 | } | ||
| 165 | testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString()); | ||
| 166 | return 0; | ||
| 167 | } | ||
| 168 | |||
| 169 | #if 0 | ||
| 170 | static const QString TEST_FOLDER { "H:/ktx_cacheold" }; | ||
| 171 | //static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" }; | ||
| 172 | |||
| 173 | //static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" }; | ||
| 174 | static const QString EXTENSIONS { "*.ktx" }; | ||
| 175 | |||
| 176 | int mainTemp(int, char**) { | ||
| 177 | qInstallMessageHandler(messageHandler); | ||
| 178 | auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS }); | ||
| 179 | for (auto fileInfo : fileInfoList) { | ||
| 180 | qDebug() << fileInfo.filePath(); | ||
| 181 | std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } }; | ||
| 182 | |||
| 183 | if (!ktx::KTX::validate(storage)) { | ||
| 184 | qDebug() << "KTX invalid"; | ||
| 185 | } | ||
| 186 | |||
| 187 | auto ktxFile = ktx::KTX::create(storage); | ||
| 188 | ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor(); | ||
| 189 | |||
| 190 | qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs"; | ||
| 191 | for (const auto& kv : ktxDescriptor.keyValues) { | ||
| 192 | qDebug() << "\t" << kv._key.c_str(); | ||
| 193 | } | ||
| 194 | |||
| 195 | auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY); | ||
| 196 | if (offsetToMinMipKV) { | ||
| 197 | auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV; | ||
| 198 | auto minMipLevelAvailable = *data; | ||
| 199 | qDebug() << "\tMin mip available " << minMipLevelAvailable; | ||
| 200 | assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels); | ||
| 201 | } | ||
| 202 | auto storageSize = storage->size(); | ||
| 203 | for (const auto& faceImageDesc : ktxDescriptor.images) { | ||
| 204 | //assert(0 == (faceImageDesc._faceSize % 4)); | ||
| 205 | for (const auto& faceOffset : faceImageDesc._faceOffsets) { | ||
| 206 | assert(0 == (faceOffset % 4)); | ||
| 207 | auto faceEndOffset = faceOffset + faceImageDesc._faceSize; | ||
| 208 | assert(faceEndOffset <= storageSize); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | for (const auto& faceImage : ktxFile->_images) { | ||
| 213 | for (const ktx::Byte* faceBytes : faceImage._faceBytes) { | ||
| 214 | assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4)); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | return 0; | ||
| 219 | } | ||
| 220 | #endif | ||
| 221 | |||
| 222 | #include "main.moc" | ||
| 223 | |||
| @@ -0,0 +1,187 @@ | |||
| 1 | // | ||
| 2 | // ResoruceTests.cpp | ||
| 3 | // | ||
| 4 | // Copyright 2015 High Fidelity, Inc. | ||
| 5 | // | ||
| 6 | // Distributed under the Apache License, Version 2.0. | ||
| 7 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 8 | // | ||
| 9 | |||
| 10 | |||
| 11 | #include "FileCacheTests.h" | ||
| 12 | |||
| 13 | #include <FileCache.h> | ||
| 14 | |||
| 15 | QTEST_GUILESS_MAIN(FileCacheTests) | ||
| 16 | |||
| 17 | using namespace cache; | ||
| 18 | |||
| 19 | // Limit the file size to 10 MB | ||
| 20 | static const size_t MAX_UNUSED_SIZE { 1024 * 1024 * 10 }; | ||
| 21 | static const QByteArray TEST_DATA { 1024 * 1024, '0' }; | ||
| 22 | |||
| 23 | static std::string getFileKey(int i) { | ||
| 24 | return QString(QByteArray { 1, (char)i }.toHex()).toStdString(); | ||
| 25 | } | ||
| 26 | |||
| 27 | class TestFile : public File { | ||
| 28 | using Parent = File; | ||
| 29 | public: | ||
| 30 | TestFile(Metadata&& metadata, const std::string& filepath) | ||
| 31 | : Parent(std::move(metadata), filepath) { | ||
| 32 | } | ||
| 33 | }; | ||
| 34 | |||
| 35 | class TestFileCache : public FileCache { | ||
| 36 | using Parent = FileCache; | ||
| 37 | |||
| 38 | public: | ||
| 39 | TestFileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr) : Parent(dirname, ext, nullptr) { | ||
| 40 | initialize(); | ||
| 41 | } | ||
| 42 | |||
| 43 | std::unique_ptr<File> createFile(Metadata&& metadata, const std::string& filepath) override { | ||
| 44 | qCInfo(file_cache) << "Wrote KTX" << metadata.key.c_str(); | ||
| 45 | return std::unique_ptr<File>(new TestFile(std::move(metadata), filepath)); | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | |||
| 49 | using CachePointer = std::shared_ptr<TestFileCache>; | ||
| 50 | |||
| 51 | // The FileCache relies on deleteLater to clear unused files, but QTest classes don't run a conventional event loop | ||
| 52 | // so we need to call this function to force any pending deletes to occur in the File destructor | ||
| 53 | static void forceDeletes() { | ||
| 54 | while (QCoreApplication::hasPendingEvents()) { | ||
| 55 | QCoreApplication::sendPostedEvents(); | ||
| 56 | QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); | ||
| 57 | QCoreApplication::processEvents(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | size_t FileCacheTests::getCacheDirectorySize() const { | ||
| 62 | size_t result = 0; | ||
| 63 | QDir dir(_testDir.path()); | ||
| 64 | for (const auto& file : dir.entryList({ "*.tmp" })) { | ||
| 65 | result += QFileInfo(dir.absoluteFilePath(file)).size(); | ||
| 66 | } | ||
| 67 | return result; | ||
| 68 | } | ||
| 69 | |||
| 70 | CachePointer makeFileCache(QString& location) { | ||
| 71 | auto result = std::make_shared<TestFileCache>(location.toStdString(), "tmp"); | ||
| 72 | result->setMaxSize(MAX_UNUSED_SIZE); | ||
| 73 | return result; | ||
| 74 | } | ||
| 75 | |||
| 76 | void FileCacheTests::initTestCase() { | ||
| 77 | } | ||
| 78 | |||
| 79 | void FileCacheTests::testUnusedFiles() { | ||
| 80 | auto cache = makeFileCache(_testDir.path()); | ||
| 81 | std::list<FilePointer> inUseFiles; | ||
| 82 | |||
| 83 | { | ||
| 84 | for (int i = 0; i < 100; ++i) { | ||
| 85 | std::string key = getFileKey(i); | ||
| 86 | auto file = cache->writeFile(TEST_DATA.data(), TestFileCache::Metadata(key, TEST_DATA.size())); | ||
| 87 | inUseFiles.push_back(file); | ||
| 88 | forceDeletes(); | ||
| 89 | QThread::msleep(10); | ||
| 90 | } | ||
| 91 | QCOMPARE(cache->getNumCachedFiles(), (size_t)0); | ||
| 92 | QCOMPARE(cache->getNumTotalFiles(), (size_t)100); | ||
| 93 | // Release the in-use files | ||
| 94 | inUseFiles.clear(); | ||
| 95 | // Cache state is updated, but the directory state is unchanged, | ||
| 96 | // because the file deletes are triggered by an event loop | ||
| 97 | QCOMPARE(cache->getNumCachedFiles(), (size_t)10); | ||
| 98 | QCOMPARE(cache->getNumTotalFiles(), (size_t)10); | ||
| 99 | QVERIFY(getCacheDirectorySize() > MAX_UNUSED_SIZE); | ||
| 100 | forceDeletes(); | ||
| 101 | QCOMPARE(cache->getNumCachedFiles(), (size_t)10); | ||
| 102 | QCOMPARE(cache->getNumTotalFiles(), (size_t)10); | ||
| 103 | QVERIFY(getCacheDirectorySize() <= MAX_UNUSED_SIZE); | ||
| 104 | } | ||
| 105 | |||
| 106 | // Reset the cache | ||
| 107 | cache = makeFileCache(_testDir.path()); | ||
| 108 | { | ||
| 109 | // Test files 0 to 89 are missing, because the LRU algorithm deleted them when we released the files | ||
| 110 | for (int i = 0; i < 90; ++i) { | ||
| 111 | std::string key = getFileKey(i); | ||
| 112 | auto file = cache->getFile(key); | ||
| 113 | QVERIFY(!file.get()); | ||
| 114 | } | ||
| 115 | |||
| 116 | // Test files 90 to 99 are present | ||
| 117 | for (int i = 90; i < 100; ++i) { | ||
| 118 | std::string key = getFileKey(i); | ||
| 119 | auto file = cache->getFile(key); | ||
| 120 | QVERIFY(file.get()); | ||
| 121 | inUseFiles.push_back(file); | ||
| 122 | |||
| 123 | if (i == 94) { | ||
| 124 | // Each access touches the file, so we need to sleep here to ensure that the the last 5 files | ||
| 125 | // have later times for cache ejection priority, otherwise the test runs too fast to reliably | ||
| 126 | // differentiate | ||
| 127 | QThread::msleep(1000); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | QCOMPARE(cache->getNumCachedFiles(), (size_t)0); | ||
| 132 | QCOMPARE(cache->getNumTotalFiles(), (size_t)10); | ||
| 133 | inUseFiles.clear(); | ||
| 134 | QCOMPARE(cache->getNumCachedFiles(), (size_t)10); | ||
| 135 | QCOMPARE(cache->getNumTotalFiles(), (size_t)10); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | size_t FileCacheTests::getFreeSpace() const { | ||
| 140 | return QStorageInfo(_testDir.path()).bytesFree(); | ||
| 141 | } | ||
| 142 | |||
| 143 | // FIXME if something else is changing the amount of free space on the target drive concurrently with this test | ||
| 144 | // running, then it may fail | ||
| 145 | void FileCacheTests::testFreeSpacePreservation() { | ||
| 146 | QCOMPARE(getCacheDirectorySize(), MAX_UNUSED_SIZE); | ||
| 147 | // Set the target free space to slightly above whatever the current free space is... | ||
| 148 | size_t targetFreeSpace = getFreeSpace() + MAX_UNUSED_SIZE / 2; | ||
| 149 | |||
| 150 | // Reset the cache | ||
| 151 | auto cache = makeFileCache(_testDir.path()); | ||
| 152 | // Setting the min free space causes it to eject the oldest files that cause the cache to exceed the minimum space | ||
| 153 | cache->setMinFreeSize(targetFreeSpace); | ||
| 154 | QVERIFY(getFreeSpace() < targetFreeSpace); | ||
| 155 | forceDeletes(); | ||
| 156 | QCOMPARE(cache->getNumCachedFiles(), (size_t)5); | ||
| 157 | QCOMPARE(cache->getNumTotalFiles(), (size_t)5); | ||
| 158 | QVERIFY(getFreeSpace() >= targetFreeSpace); | ||
| 159 | for (int i = 0; i < 95; ++i) { | ||
| 160 | std::string key = getFileKey(i); | ||
| 161 | auto file = cache->getFile(key); | ||
| 162 | QVERIFY(!file.get()); | ||
| 163 | } | ||
| 164 | for (int i = 95; i < 100; ++i) { | ||
| 165 | std::string key = getFileKey(i); | ||
| 166 | auto file = cache->getFile(key); | ||
| 167 | QVERIFY(file.get()); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | void FileCacheTests::testWipe() { | ||
| 172 | // Reset the cache | ||
| 173 | auto cache = makeFileCache(_testDir.path()); | ||
| 174 | QCOMPARE(cache->getNumCachedFiles(), (size_t)5); | ||
| 175 | QCOMPARE(cache->getNumTotalFiles(), (size_t)5); | ||
| 176 | cache->wipe(); | ||
| 177 | QCOMPARE(cache->getNumCachedFiles(), (size_t)0); | ||
| 178 | QCOMPARE(cache->getNumTotalFiles(), (size_t)0); | ||
| 179 | QVERIFY(getCacheDirectorySize() > 0); | ||
| 180 | forceDeletes(); | ||
| 181 | QCOMPARE(getCacheDirectorySize(), (size_t)0); | ||
| 182 | } | ||
| 183 | |||
| 184 | |||
| 185 | void FileCacheTests::cleanupTestCase() { | ||
| 186 | } | ||
| 187 | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | // | ||
| 2 | // ResourceTests.h | ||
| 3 | // | ||
| 4 | // Copyright 2015 High Fidelity, Inc. | ||
| 5 | // | ||
| 6 | // Distributed under the Apache License, Version 2.0. | ||
| 7 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef hifi_ResourceTests_h | ||
| 11 | #define hifi_ResourceTests_h | ||
| 12 | |||
| 13 | #include <QtTest/QtTest> | ||
| 14 | #include <QtCore/QTemporaryDir> | ||
| 15 | |||
| 16 | class FileCacheTests : public QObject { | ||
| 17 | Q_OBJECT | ||
| 18 | private slots: | ||
| 19 | void initTestCase(); | ||
| 20 | void testUnusedFiles(); | ||
| 21 | void testFreeSpacePreservation(); | ||
| 22 | void cleanupTestCase(); | ||
| 23 | void testWipe(); | ||
| 24 | |||
| 25 | private: | ||
| 26 | size_t getFreeSpace() const; | ||
| 27 | size_t getCacheDirectorySize() const; | ||
| 28 | QTemporaryDir _testDir; | ||
| 29 | }; | ||
| 30 | |||
| 31 | #endif // hifi_ResourceTests_h | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | |||
| 2 | set(TARGET_NAME qt59) | ||
| 3 | |||
| 4 | if (WIN32) | ||
| 5 | SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") | ||
| 6 | endif() | ||
| 7 | |||
| 8 | # This is not a testcase -- just set it up as a regular hifi project | ||
| 9 | setup_hifi_project(Gui) | ||
| 10 | set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") | ||
| 11 | |||
| 12 | # link in the shared libraries | ||
| 13 | link_hifi_libraries(shared networking) | ||
| 14 | |||
| 15 | package_libraries_for_deployment() | ||
| @@ -0,0 +1,78 @@ | |||
| 1 | // | ||
| 2 | // Created by Bradley Austin Davis on 2017/06/06 | ||
| 3 | // Copyright 2013-2017 High Fidelity, Inc. | ||
| 4 | // | ||
| 5 | // Distributed under the Apache License, Version 2.0. | ||
| 6 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 7 | // | ||
| 8 | |||
| 9 | #include <mutex> | ||
| 10 | |||
| 11 | #include <QtCore/QCoreApplication> | ||
| 12 | |||
| 13 | #include <NodeList.h> | ||
| 14 | #include <AccountManager.h> | ||
| 15 | #include <AddressManager.h> | ||
| 16 | #include <MessagesClient.h> | ||
| 17 | |||
| 18 | #include <BuildInfo.h> | ||
| 19 | |||
| 20 | |||
| 21 | class Qt59TestApp : public QCoreApplication { | ||
| 22 | Q_OBJECT | ||
| 23 | public: | ||
| 24 | Qt59TestApp(int argc, char* argv[]); | ||
| 25 | ~Qt59TestApp(); | ||
| 26 | |||
| 27 | private: | ||
| 28 | void finish(int exitCode); | ||
| 29 | }; | ||
| 30 | |||
| 31 | |||
| 32 | |||
| 33 | Qt59TestApp::Qt59TestApp(int argc, char* argv[]) : | ||
| 34 | QCoreApplication(argc, argv) | ||
| 35 | { | ||
| 36 | |||
| 37 | Setting::init(); | ||
| 38 | DependencyManager::registerInheritance<LimitedNodeList, NodeList>(); | ||
| 39 | DependencyManager::set<AccountManager>([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); }); | ||
| 40 | DependencyManager::set<AddressManager>(); | ||
| 41 | DependencyManager::set<NodeList>(NodeType::Agent, 0); | ||
| 42 | auto nodeList = DependencyManager::get<NodeList>(); | ||
| 43 | nodeList->startThread(); | ||
| 44 | auto messagesClient = DependencyManager::set<MessagesClient>(); | ||
| 45 | messagesClient->startThread(); | ||
| 46 | QTimer::singleShot(1000, [this] { finish(0); }); | ||
| 47 | } | ||
| 48 | |||
| 49 | Qt59TestApp::~Qt59TestApp() { | ||
| 50 | } | ||
| 51 | |||
| 52 | |||
| 53 | void Qt59TestApp::finish(int exitCode) { | ||
| 54 | auto nodeList = DependencyManager::get<NodeList>(); | ||
| 55 | |||
| 56 | // send the domain a disconnect packet, force stoppage of domain-server check-ins | ||
| 57 | nodeList->getDomainHandler().disconnect(); | ||
| 58 | nodeList->setIsShuttingDown(true); | ||
| 59 | nodeList->getPacketReceiver().setShouldDropPackets(true); | ||
| 60 | |||
| 61 | // remove the NodeList from the DependencyManager | ||
| 62 | DependencyManager::destroy<NodeList>(); | ||
| 63 | QCoreApplication::exit(exitCode); | ||
| 64 | } | ||
| 65 | |||
| 66 | |||
| 67 | int main(int argc, char * argv[]) { | ||
| 68 | QCoreApplication::setApplicationName("Qt59Test"); | ||
| 69 | QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); | ||
| 70 | QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); | ||
| 71 | QCoreApplication::setApplicationVersion(BuildInfo::VERSION); | ||
| 72 | |||
| 73 | Qt59TestApp app(argc, argv); | ||
| 74 | |||
| 75 | return app.exec(); | ||
| 76 | } | ||
| 77 | |||
| 78 | #include "main.moc" | ||
| @@ -10,7 +10,7 @@ setup_hifi_project(Quick Gui OpenGL) · 9 unmodified lines | |||
| 10 | set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") | 10 | set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") |
| 11 | 11 | ||
| 12 | # link in the shared libraries | 12 | # link in the shared libraries |
| 13 | link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image) | 13 | link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image procedural) |
| 14 | 14 | ||
| 15 | package_libraries_for_deployment() | 15 | package_libraries_for_deployment() |
| 16 | 16 | ||
| @@ -31,13 +31,9 @@ · 30 unmodified lines | |||
| 31 | #include <render-utils/deferred_light_point_vert.h> | 31 | #include <render-utils/deferred_light_point_vert.h> |
| 32 | #include <render-utils/deferred_light_spot_vert.h> | 32 | #include <render-utils/deferred_light_spot_vert.h> |
| 33 | 33 | ||
| 34 | #include <render-utils/directional_light_frag.h> | ||
| 35 | #include <render-utils/directional_ambient_light_frag.h> | 34 | #include <render-utils/directional_ambient_light_frag.h> |
| 36 | #include <render-utils/directional_skybox_light_frag.h> | 35 | #include <render-utils/directional_skybox_light_frag.h> |
| 37 | 36 | ||
| 38 | #include <render-utils/point_light_frag.h> | ||
| 39 | #include <render-utils/spot_light_frag.h> | ||
| 40 | |||
| 41 | #include <render-utils/standardTransformPNTC_vert.h> | 37 | #include <render-utils/standardTransformPNTC_vert.h> |
| 42 | #include <render-utils/standardDrawTexture_frag.h> | 38 | #include <render-utils/standardDrawTexture_frag.h> |
| 43 | 39 | ||
| @@ -66,9 +62,6 @@ · 22 unmodified lines | |||
| 66 | #include <entities-renderer/textured_particle_frag.h> | 62 | #include <entities-renderer/textured_particle_frag.h> |
| 67 | #include <entities-renderer/textured_particle_vert.h> | 63 | #include <entities-renderer/textured_particle_vert.h> |
| 68 | 64 | ||
| 69 | #include <render-utils/hit_effect_vert.h> | ||
| 70 | #include <render-utils/hit_effect_frag.h> | ||
| 71 | |||
| 72 | #include <render-utils/overlay3D_vert.h> | 65 | #include <render-utils/overlay3D_vert.h> |
| 73 | #include <render-utils/overlay3D_frag.h> | 66 | #include <render-utils/overlay3D_frag.h> |
| 74 | 67 | ||
| @@ -155,11 +148,8 @@ void QTestWindow::draw() { · 80 unmodified lines | |||
| 155 | testShaderBuild(simple_vert, simple_frag); | 148 | testShaderBuild(simple_vert, simple_frag); |
| 156 | testShaderBuild(simple_vert, simple_textured_frag); | 149 | testShaderBuild(simple_vert, simple_textured_frag); |
| 157 | testShaderBuild(simple_vert, simple_textured_unlit_frag); | 150 | testShaderBuild(simple_vert, simple_textured_unlit_frag); |
| 158 | testShaderBuild(deferred_light_vert, directional_light_frag); | ||
| 159 | testShaderBuild(deferred_light_vert, directional_ambient_light_frag); | 151 | testShaderBuild(deferred_light_vert, directional_ambient_light_frag); |
| 160 | testShaderBuild(deferred_light_vert, directional_skybox_light_frag); | 152 | testShaderBuild(deferred_light_vert, directional_skybox_light_frag); |
| 161 | testShaderBuild(deferred_light_point_vert, point_light_frag); | ||
| 162 | testShaderBuild(deferred_light_spot_vert, spot_light_frag); | ||
| 163 | testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag); | 153 | testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag); |
| 164 | testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag); | 154 | testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag); |
| 165 | 155 | ||
| @@ -190,7 +180,6 @@ void QTestWindow::draw() { · 24 unmodified lines | |||
| 190 | testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag); | 180 | testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag); |
| 191 | testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag); | 181 | testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag); |
| 192 | */ | 182 | */ |
| 193 | testShaderBuild(hit_effect_vert, hit_effect_frag); | ||
| 194 | 183 | ||
| 195 | testShaderBuild(overlay3D_vert, overlay3D_frag); | 184 | testShaderBuild(overlay3D_vert, overlay3D_frag); |
| 196 | 185 | ||
| @@ -0,0 +1,77 @@ | |||
| 1 | // | ||
| 2 | // CubicHermiteSplineTests.cpp | ||
| 3 | // tests/shared/src | ||
| 4 | // | ||
| 5 | // Copyright 2017 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | // | ||
| 10 | |||
| 11 | #include "CubicHermiteSplineTests.h" | ||
| 12 | #include "../QTestExtensions.h" | ||
| 13 | #include <QtCore/QDebug> | ||
| 14 | #include "CubicHermiteSpline.h" | ||
| 15 | |||
| 16 | QTEST_MAIN(CubicHermiteSplineTests) | ||
| 17 | |||
| 18 | void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() { | ||
| 19 | glm::vec3 p0(0.0f, 0.0f, 0.0f); | ||
| 20 | glm::vec3 m0(1.0f, 0.0f, 0.0f); | ||
| 21 | glm::vec3 p1(1.0f, 1.0f, 0.0f); | ||
| 22 | glm::vec3 m1(2.0f, 0.0f, 0.0f); | ||
| 23 | CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1); | ||
| 24 | |||
| 25 | const float EPSILON = 0.0001f; | ||
| 26 | |||
| 27 | QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON); | ||
| 28 | QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON); | ||
| 29 | |||
| 30 | // these values were computed offline. | ||
| 31 | const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f); | ||
| 32 | const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f); | ||
| 33 | const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f); | ||
| 34 | |||
| 35 | QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON); | ||
| 36 | QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON); | ||
| 37 | QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON); | ||
| 38 | } | ||
| 39 | |||
| 40 | void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() { | ||
| 41 | glm::vec3 p0(0.0f, 0.0f, 0.0f); | ||
| 42 | glm::vec3 m0(1.0f, 0.0f, 0.0f); | ||
| 43 | glm::vec3 p1(1.0f, 1.0f, 0.0f); | ||
| 44 | glm::vec3 m1(2.0f, 0.0f, 0.0f); | ||
| 45 | CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1); | ||
| 46 | |||
| 47 | const float EPSILON = 0.001f; | ||
| 48 | |||
| 49 | float arcLengths[5] = { | ||
| 50 | hermiteSpline.arcLength(0.0f), | ||
| 51 | hermiteSpline.arcLength(0.25f), | ||
| 52 | hermiteSpline.arcLength(0.5f), | ||
| 53 | hermiteSpline.arcLength(0.75f), | ||
| 54 | hermiteSpline.arcLength(1.0f) | ||
| 55 | }; | ||
| 56 | |||
| 57 | // these values were computed offline | ||
| 58 | float referenceArcLengths[5] = { | ||
| 59 | 0.0f, | ||
| 60 | 0.268317f, | ||
| 61 | 0.652788f, | ||
| 62 | 1.07096f, | ||
| 63 | 1.50267f | ||
| 64 | }; | ||
| 65 | |||
| 66 | QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON); | ||
| 67 | QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON); | ||
| 68 | QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON); | ||
| 69 | QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON); | ||
| 70 | QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON); | ||
| 71 | |||
| 72 | QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON); | ||
| 73 | QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON); | ||
| 74 | QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON); | ||
| 75 | QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON); | ||
| 76 | QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON); | ||
| 77 | } | ||
| @@ -0,0 +1,23 @@ | |||
| 1 | // | ||
| 2 | // CubicHermiteSplineTests.h | ||
| 3 | // tests/shared/src | ||
| 4 | // | ||
| 5 | // Copyright 2017 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | // | ||
| 10 | |||
| 11 | #ifndef hifi_CubicHermiteSplineTests_h | ||
| 12 | #define hifi_CubicHermiteSplineTests_h | ||
| 13 | |||
| 14 | #include <QtTest/QtTest> | ||
| 15 | |||
| 16 | class CubicHermiteSplineTests : public QObject { | ||
| 17 | Q_OBJECT | ||
| 18 | private slots: | ||
| 19 | void testCubicHermiteSplineFunctor(); | ||
| 20 | void testCubicHermiteSplineFunctorWithArcLength(); | ||
| 21 | }; | ||
| 22 | |||
| 23 | #endif // hifi_TransformTests_h | ||
| @@ -140,3 +140,77 @@ void GLMHelpersTests::testSimd() { · 139 unmodified lines | |||
| 140 | } | 140 | } |
| 141 | qDebug() << "Done "; | 141 | qDebug() << "Done "; |
| 142 | } | 142 | } |
| 143 | |||
| 144 | void GLMHelpersTests::testGenerateBasisVectors() { | ||
| 145 | { // very simple case: primary along X, secondary is linear combination of X and Y | ||
| 146 | glm::vec3 u(1.0f, 0.0f, 0.0f); | ||
| 147 | glm::vec3 v(1.0f, 1.0f, 0.0f); | ||
| 148 | glm::vec3 w; | ||
| 149 | |||
| 150 | generateBasisVectors(u, v, u, v, w); | ||
| 151 | |||
| 152 | QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_X, EPSILON); | ||
| 153 | QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_Y, EPSILON); | ||
| 154 | QCOMPARE_WITH_ABS_ERROR(w, Vectors::UNIT_Z, EPSILON); | ||
| 155 | } | ||
| 156 | |||
| 157 | { // point primary along Y instead of X | ||
| 158 | glm::vec3 u(0.0f, 1.0f, 0.0f); | ||
| 159 | glm::vec3 v(1.0f, 1.0f, 0.0f); | ||
| 160 | glm::vec3 w; | ||
| 161 | |||
| 162 | generateBasisVectors(u, v, u, v, w); | ||
| 163 | |||
| 164 | QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_Y, EPSILON); | ||
| 165 | QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_X, EPSILON); | ||
| 166 | QCOMPARE_WITH_ABS_ERROR(w, -Vectors::UNIT_Z, EPSILON); | ||
| 167 | } | ||
| 168 | |||
| 169 | { // pass bad data (both vectors along Y). The helper will guess X for secondary. | ||
| 170 | glm::vec3 u(0.0f, 1.0f, 0.0f); | ||
| 171 | glm::vec3 v(0.0f, 1.0f, 0.0f); | ||
| 172 | glm::vec3 w; | ||
| 173 | |||
| 174 | generateBasisVectors(u, v, u, v, w); | ||
| 175 | |||
| 176 | QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_Y, EPSILON); | ||
| 177 | QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_X, EPSILON); | ||
| 178 | QCOMPARE_WITH_ABS_ERROR(w, -Vectors::UNIT_Z, EPSILON); | ||
| 179 | } | ||
| 180 | |||
| 181 | { // pass bad data (both vectors along X). The helper will guess X for secondary, fail, then guess Y. | ||
| 182 | glm::vec3 u(1.0f, 0.0f, 0.0f); | ||
| 183 | glm::vec3 v(1.0f, 0.0f, 0.0f); | ||
| 184 | glm::vec3 w; | ||
| 185 | |||
| 186 | generateBasisVectors(u, v, u, v, w); | ||
| 187 | |||
| 188 | QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_X, EPSILON); | ||
| 189 | QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_Y, EPSILON); | ||
| 190 | QCOMPARE_WITH_ABS_ERROR(w, Vectors::UNIT_Z, EPSILON); | ||
| 191 | } | ||
| 192 | |||
| 193 | { // general case for arbitrary rotation | ||
| 194 | float angle = 1.234f; | ||
| 195 | glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); | ||
| 196 | glm::quat rotation = glm::angleAxis(angle, axis); | ||
| 197 | |||
| 198 | // expected values | ||
| 199 | glm::vec3 x = rotation * Vectors::UNIT_X; | ||
| 200 | glm::vec3 y = rotation * Vectors::UNIT_Y; | ||
| 201 | glm::vec3 z = rotation * Vectors::UNIT_Z; | ||
| 202 | |||
| 203 | // primary is along x | ||
| 204 | // secondary is linear combination of x and y | ||
| 205 | // tertiary is unknown | ||
| 206 | glm::vec3 u = 1.23f * x; | ||
| 207 | glm::vec3 v = 2.34f * x + 3.45f * y; | ||
| 208 | glm::vec3 w; | ||
| 209 | |||
| 210 | generateBasisVectors(u, v, u, v, w); | ||
| 211 | |||
| 212 | QCOMPARE_WITH_ABS_ERROR(u, x, EPSILON); | ||
| 213 | QCOMPARE_WITH_ABS_ERROR(v, y, EPSILON); | ||
| 214 | QCOMPARE_WITH_ABS_ERROR(w, z, EPSILON); | ||
| 215 | } | ||
| 216 | } | ||
| @@ -21,6 +21,7 @@ private slots: · 20 unmodified lines | |||
| 21 | void testEulerDecomposition(); | 21 | void testEulerDecomposition(); |
| 22 | void testSixByteOrientationCompression(); | 22 | void testSixByteOrientationCompression(); |
| 23 | void testSimd(); | 23 | void testSimd(); |
| 24 | void testGenerateBasisVectors(); | ||
| 24 | }; | 25 | }; |
| 25 | 26 | ||
| 26 | float getErrorDifference(const float& a, const float& b); | 27 | float getErrorDifference(const float& a, const float& b); |
| @@ -8,6 +8,8 @@ · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | #include "StorageTests.h" | 9 | #include "StorageTests.h" |
| 10 | 10 | ||
| 11 | #include <memory> | ||
| 12 | |||
| 11 | QTEST_MAIN(StorageTests) | 13 | QTEST_MAIN(StorageTests) |
| 12 | 14 | ||
| 13 | using namespace storage; | 15 | using namespace storage; |
| @@ -32,8 +34,8 @@ void StorageTests::testConversion() { · 18 unmodified lines | |||
| 32 | QFileInfo fileInfo(_testFile); | 34 | QFileInfo fileInfo(_testFile); |
| 33 | QCOMPARE(fileInfo.exists(), false); | 35 | QCOMPARE(fileInfo.exists(), false); |
| 34 | } | 36 | } |
| 35 | StoragePointer storagePointer = std::make_unique<MemoryStorage>(_testData.size(), _testData.data()); | 37 | StoragePointer storagePointer = std::unique_ptr<MemoryStorage>(new MemoryStorage(_testData.size(), _testData.data())); |
| 36 | QCOMPARE(storagePointer->size(), (quint64)_testData.size()); | 38 | QCOMPARE(storagePointer->size(), _testData.size()); |
| 37 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); | 39 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); |
| 38 | // Convert to a file | 40 | // Convert to a file |
| 39 | storagePointer = storagePointer->toFileStorage(_testFile); | 41 | storagePointer = storagePointer->toFileStorage(_testFile); |
| @@ -42,12 +44,12 @@ void StorageTests::testConversion() { · 2 unmodified lines | |||
| 42 | QCOMPARE(fileInfo.exists(), true); | 44 | QCOMPARE(fileInfo.exists(), true); |
| 43 | QCOMPARE(fileInfo.size(), (qint64)_testData.size()); | 45 | QCOMPARE(fileInfo.size(), (qint64)_testData.size()); |
| 44 | } | 46 | } |
| 45 | QCOMPARE(storagePointer->size(), (quint64)_testData.size()); | 47 | QCOMPARE(storagePointer->size(), _testData.size()); |
| 46 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); | 48 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); |
| 47 | 49 | ||
| 48 | // Convert to memory | 50 | // Convert to memory |
| 49 | storagePointer = storagePointer->toMemoryStorage(); | 51 | storagePointer = storagePointer->toMemoryStorage(); |
| 50 | QCOMPARE(storagePointer->size(), (quint64)_testData.size()); | 52 | QCOMPARE(storagePointer->size(), _testData.size()); |
| 51 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); | 53 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); |
| 52 | { | 54 | { |
| 53 | // ensure the file is unaffected | 55 | // ensure the file is unaffected |
| @@ -58,13 +60,13 @@ void StorageTests::testConversion() { · 4 unmodified lines | |||
| 58 | 60 | ||
| 59 | // truncate the data as a new memory object | 61 | // truncate the data as a new memory object |
| 60 | auto newSize = _testData.size() / 2; | 62 | auto newSize = _testData.size() / 2; |
| 61 | storagePointer = std::make_unique<MemoryStorage>(newSize, storagePointer->data()); | 63 | storagePointer = std::unique_ptr<Storage>(new MemoryStorage(newSize, storagePointer->data())); |
| 62 | QCOMPARE(storagePointer->size(), (quint64)newSize); | 64 | QCOMPARE(storagePointer->size(), newSize); |
| 63 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); | 65 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); |
| 64 | 66 | ||
| 65 | // Convert back to file | 67 | // Convert back to file |
| 66 | storagePointer = storagePointer->toFileStorage(_testFile); | 68 | storagePointer = storagePointer->toFileStorage(_testFile); |
| 67 | QCOMPARE(storagePointer->size(), (quint64)newSize); | 69 | QCOMPARE(storagePointer->size(), newSize); |
| 68 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); | 70 | QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); |
| 69 | { | 71 | { |
| 70 | // ensure the file is truncated | 72 | // ensure the file is truncated |
| @@ -90,23 +90,14 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : · 89 unmodified lines | |||
| 90 | 90 | ||
| 91 | 91 | ||
| 92 | auto nodeList = DependencyManager::get<NodeList>(); | 92 | auto nodeList = DependencyManager::get<NodeList>(); |
| 93 | |||
| 94 | // start the nodeThread so its event loop is running | 93 | // start the nodeThread so its event loop is running |
| 95 | QThread* nodeThread = new QThread(this); | 94 | nodeList->startThread(); |
| 96 | nodeThread->setObjectName("NodeList Thread"); | ||
| 97 | nodeThread->start(); | ||
| 98 | |||
| 99 | // make sure the node thread is given highest priority | ||
| 100 | nodeThread->setPriority(QThread::TimeCriticalPriority); | ||
| 101 | 95 | ||
| 102 | // setup a timer for domain-server check ins | 96 | // setup a timer for domain-server check ins |
| 103 | QTimer* domainCheckInTimer = new QTimer(nodeList.data()); | 97 | QTimer* domainCheckInTimer = new QTimer(nodeList.data()); |
| 104 | connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); | 98 | connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); |
| 105 | domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); | 99 | domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); |
| 106 | 100 | ||
| 107 | // put the NodeList and datagram processing on the node thread | ||
| 108 | nodeList->moveToThread(nodeThread); | ||
| 109 | |||
| 110 | const DomainHandler& domainHandler = nodeList->getDomainHandler(); | 101 | const DomainHandler& domainHandler = nodeList->getDomainHandler(); |
| 111 | 102 | ||
| 112 | connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); | 103 | connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); |
| @@ -239,12 +230,8 @@ void ACClientApp::finish(int exitCode) { · 126 unmodified lines | |||
| 239 | // tell the packet receiver we're shutting down, so it can drop packets | 230 | // tell the packet receiver we're shutting down, so it can drop packets |
| 240 | nodeList->getPacketReceiver().setShouldDropPackets(true); | 231 | nodeList->getPacketReceiver().setShouldDropPackets(true); |
| 241 | 232 | ||
| 242 | QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); | ||
| 243 | // remove the NodeList from the DependencyManager | 233 | // remove the NodeList from the DependencyManager |
| 244 | DependencyManager::destroy<NodeList>(); | 234 | DependencyManager::destroy<NodeList>(); |
| 245 | // ask the node thread to quit and wait until it is done | ||
| 246 | nodeThread->quit(); | ||
| 247 | nodeThread->wait(); | ||
| 248 | 235 | ||
| 249 | printFailedServers(); | 236 | printFailedServers(); |
| 250 | QCoreApplication::exit(exitCode); | 237 | QCoreApplication::exit(exitCode); |
| @@ -111,23 +111,13 @@ ATPGetApp::ATPGetApp(int argc, char* argv[]) : · 110 unmodified lines | |||
| 111 | 111 | ||
| 112 | 112 | ||
| 113 | auto nodeList = DependencyManager::get<NodeList>(); | 113 | auto nodeList = DependencyManager::get<NodeList>(); |
| 114 | 114 | nodeList->startThread(); | |
| 115 | // start the nodeThread so its event loop is running | ||
| 116 | QThread* nodeThread = new QThread(this); | ||
| 117 | nodeThread->setObjectName("NodeList Thread"); | ||
| 118 | nodeThread->start(); | ||
| 119 | |||
| 120 | // make sure the node thread is given highest priority | ||
| 121 | nodeThread->setPriority(QThread::TimeCriticalPriority); | ||
| 122 | 115 | ||
| 123 | // setup a timer for domain-server check ins | 116 | // setup a timer for domain-server check ins |
| 124 | QTimer* domainCheckInTimer = new QTimer(nodeList.data()); | 117 | QTimer* domainCheckInTimer = new QTimer(nodeList.data()); |
| 125 | connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); | 118 | connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); |
| 126 | domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); | 119 | domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); |
| 127 | 120 | ||
| 128 | // put the NodeList and datagram processing on the node thread | ||
| 129 | nodeList->moveToThread(nodeThread); | ||
| 130 | |||
| 131 | const DomainHandler& domainHandler = nodeList->getDomainHandler(); | 121 | const DomainHandler& domainHandler = nodeList->getDomainHandler(); |
| 132 | 122 | ||
| 133 | connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); | 123 | connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); |
| @@ -258,12 +248,8 @@ void ATPGetApp::finish(int exitCode) { · 124 unmodified lines | |||
| 258 | // tell the packet receiver we're shutting down, so it can drop packets | 248 | // tell the packet receiver we're shutting down, so it can drop packets |
| 259 | nodeList->getPacketReceiver().setShouldDropPackets(true); | 249 | nodeList->getPacketReceiver().setShouldDropPackets(true); |
| 260 | 250 | ||
| 261 | QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); | ||
| 262 | // remove the NodeList from the DependencyManager | 251 | // remove the NodeList from the DependencyManager |
| 263 | DependencyManager::destroy<NodeList>(); | 252 | DependencyManager::destroy<NodeList>(); |
| 264 | // ask the node thread to quit and wait until it is done | ||
| 265 | nodeThread->quit(); | ||
| 266 | nodeThread->wait(); | ||
| 267 | 253 | ||
| 268 | QCoreApplication::exit(exitCode); | 254 | QCoreApplication::exit(exitCode); |
| 269 | } | 255 | } |
| @@ -0,0 +1,64 @@ | |||
| 1 | // | ||
| 2 | // BakerCLI.cpp | ||
| 3 | // tools/oven/src | ||
| 4 | // | ||
| 5 | // Created by Robbie Uvanni on 6/6/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #include <QObject> | ||
| 13 | #include <QImageReader> | ||
| 14 | #include <QtCore/QDebug> | ||
| 15 | |||
| 16 | #include "ModelBakingLoggingCategory.h" | ||
| 17 | #include "Oven.h" | ||
| 18 | #include "BakerCLI.h" | ||
| 19 | #include "FBXBaker.h" | ||
| 20 | #include "TextureBaker.h" | ||
| 21 | |||
| 22 | BakerCLI::BakerCLI(Oven* parent) : QObject(parent) { | ||
| 23 | } | ||
| 24 | |||
| 25 | void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { | ||
| 26 | |||
| 27 | // if the URL doesn't have a scheme, assume it is a local file | ||
| 28 | if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") { | ||
| 29 | inputUrl.setScheme("file"); | ||
| 30 | } | ||
| 31 | |||
| 32 | static const QString MODEL_EXTENSION { ".fbx" }; | ||
| 33 | |||
| 34 | // check what kind of baker we should be creating | ||
| 35 | bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive); | ||
| 36 | bool isSupportedImage = false; | ||
| 37 | |||
| 38 | for (QByteArray format : QImageReader::supportedImageFormats()) { | ||
| 39 | isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive); | ||
| 40 | } | ||
| 41 | |||
| 42 | // create our appropiate baker | ||
| 43 | if (isFBX) { | ||
| 44 | _baker = std::unique_ptr<Baker> { new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }) }; | ||
| 45 | _baker->moveToThread(qApp->getFBXBakerThread()); | ||
| 46 | } else if (isSupportedImage) { | ||
| 47 | _baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; | ||
| 48 | _baker->moveToThread(qApp->getNextWorkerThread()); | ||
| 49 | } else { | ||
| 50 | qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; | ||
| 51 | QApplication::exit(1); | ||
| 52 | } | ||
| 53 | |||
| 54 | // invoke the bake method on the baker thread | ||
| 55 | QMetaObject::invokeMethod(_baker.get(), "bake"); | ||
| 56 | |||
| 57 | // make sure we hear about the results of this baker when it is done | ||
| 58 | connect(_baker.get(), &Baker::finished, this, &BakerCLI::handleFinishedBaker); | ||
| 59 | } | ||
| 60 | |||
| 61 | void BakerCLI::handleFinishedBaker() { | ||
| 62 | qCDebug(model_baking) << "Finished baking file."; | ||
| 63 | QApplication::exit(_baker.get()->hasErrors()); | ||
| 64 | } | ||
| @@ -0,0 +1,34 @@ | |||
| 1 | // | ||
| 2 | // BakerCLI.h | ||
| 3 | // tools/oven/src | ||
| 4 | // | ||
| 5 | // Created by Robbie Uvanni on 6/6/17. | ||
| 6 | // Copyright 2017 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #ifndef hifi_BakerCLI_h | ||
| 13 | #define hifi_BakerCLI_h | ||
| 14 | |||
| 15 | #include <QtCore/QObject> | ||
| 16 | |||
| 17 | #include "Baker.h" | ||
| 18 | #include "Oven.h" | ||
| 19 | |||
| 20 | class BakerCLI : public QObject { | ||
| 21 | Q_OBJECT | ||
| 22 | |||
| 23 | public: | ||
| 24 | BakerCLI(Oven* parent); | ||
| 25 | void bakeFile(QUrl inputUrl, const QString outputPath); | ||
| 26 | |||
| 27 | private slots: | ||
| 28 | void handleFinishedBaker(); | ||
| 29 | |||
| 30 | private: | ||
| 31 | std::unique_ptr<Baker> _baker; | ||
| 32 | }; | ||
| 33 | |||
| 34 | #endif // hifi_BakerCLI_h | ||
| @@ -11,16 +11,20 @@ · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | #include <QtCore/QDebug> | 12 | #include <QtCore/QDebug> |
| 13 | #include <QtCore/QThread> | 13 | #include <QtCore/QThread> |
| 14 | #include <QtCore/QCommandLineParser> | ||
| 14 | 15 | ||
| 15 | #include <image/Image.h> | 16 | #include <image/Image.h> |
| 16 | #include <SettingInterface.h> | 17 | #include <SettingInterface.h> |
| 17 | 18 | ||
| 18 | #include "ui/OvenMainWindow.h" | 19 | #include "ui/OvenMainWindow.h" |
| 19 | |||
| 20 | #include "Oven.h" | 20 | #include "Oven.h" |
| 21 | #include "BakerCli.h" | ||
| 21 | 22 | ||
| 22 | static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; | 23 | static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; |
| 23 | 24 | ||
| 25 | static const QString CLI_INPUT_PARAMETER = "i"; | ||
| 26 | static const QString CLI_OUTPUT_PARAMETER = "o"; | ||
| 27 | |||
| 24 | Oven::Oven(int argc, char* argv[]) : | 28 | Oven::Oven(int argc, char* argv[]) : |
| 25 | QApplication(argc, argv) | 29 | QApplication(argc, argv) |
| 26 | { | 30 | { |
| @@ -30,24 +34,43 @@ Oven::Oven(int argc, char* argv[]) : · 3 unmodified lines | |||
| 30 | // init the settings interface so we can save and load settings | 34 | // init the settings interface so we can save and load settings |
| 31 | Setting::init(); | 35 | Setting::init(); |
| 32 | 36 | ||
| 37 | // parse the command line parameters | ||
| 38 | QCommandLineParser parser; | ||
| 39 | |||
| 40 | parser.addOptions({ | ||
| 41 | { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, | ||
| 42 | { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" } | ||
| 43 | }); | ||
| 44 | parser.addHelpOption(); | ||
| 45 | parser.process(*this); | ||
| 46 | |||
| 33 | // enable compression in image library, except for cube maps | 47 | // enable compression in image library, except for cube maps |
| 34 | image::setColorTexturesCompressionEnabled(true); | 48 | image::setColorTexturesCompressionEnabled(true); |
| 35 | image::setGrayscaleTexturesCompressionEnabled(true); | 49 | image::setGrayscaleTexturesCompressionEnabled(true); |
| 36 | image::setNormalTexturesCompressionEnabled(true); | 50 | image::setNormalTexturesCompressionEnabled(true); |
| 37 | image::setCubeTexturesCompressionEnabled(true); | 51 | image::setCubeTexturesCompressionEnabled(true); |
| 38 | 52 | ||
| 39 | // check if we were passed any command line arguments that would tell us just to run without the GUI | ||
| 40 | |||
| 41 | // setup the GUI | ||
| 42 | _mainWindow = new OvenMainWindow; | ||
| 43 | _mainWindow->show(); | ||
| 44 | |||
| 45 | // setup our worker threads | 53 | // setup our worker threads |
| 46 | setupWorkerThreads(QThread::idealThreadCount() - 1); | 54 | setupWorkerThreads(QThread::idealThreadCount() - 1); |
| 47 | 55 | ||
| 48 | // Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process | 56 | // Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process |
| 49 | // setup the FBX Baker thread | 57 | // setup the FBX Baker thread |
| 50 | setupFBXBakerThread(); | 58 | setupFBXBakerThread(); |
| 59 | |||
| 60 | // check if we were passed any command line arguments that would tell us just to run without the GUI | ||
| 61 | if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) { | ||
| 62 | if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) { | ||
| 63 | BakerCLI* cli = new BakerCLI(this); | ||
| 64 | cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER)); | ||
| 65 | } else { | ||
| 66 | parser.showHelp(); | ||
| 67 | QApplication::quit(); | ||
| 68 | } | ||
| 69 | } else { | ||
| 70 | // setup the GUI | ||
| 71 | _mainWindow = new OvenMainWindow; | ||
| 72 | _mainWindow->show(); | ||
| 73 | } | ||
| 51 | } | 74 | } |
| 52 | 75 | ||
| 53 | Oven::~Oven() { | 76 | Oven::~Oven() { |
| @@ -14,7 +14,7 @@ · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | #include <QtWidgets/QApplication> | 15 | #include <QtWidgets/QApplication> |
| 16 | 16 | ||
| 17 | #include <tbb/concurrent_vector.h> | 17 | #include <TBBHelpers.h> |
| 18 | 18 | ||
| 19 | #include <atomic> | 19 | #include <atomic> |
| 20 | 20 | ||
File renamed without changes.
File renamed without changes.
File renamed without changes.
| @@ -0,0 +1,1262 @@ | |||
| 1 | /* | ||
| 2 | // edit-style.css | ||
| 3 | // | ||
| 4 | // Created by Ryan Huffman on 13 Nov 2014 | ||
| 5 | // Copyright 2014 High Fidelity, Inc. | ||
| 6 | // | ||
| 7 | // Distributed under the Apache License, Version 2.0. | ||
| 8 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 9 | */ | ||
| 10 | |||
| 11 | @font-face { | ||
| 12 | font-family: Raleway-Regular; | ||
| 13 | src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ | ||
| 14 | url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ | ||
| 15 | url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */ | ||
| 16 | url(../fonts/Raleway-Regular.ttf); /* Marketplace script */ | ||
| 17 | } | ||
| 18 | |||
| 19 | @font-face { | ||
| 20 | font-family: Raleway-Light; | ||
| 21 | src: url(../../../../resources/fonts/Raleway-Light.ttf), | ||
| 22 | url(../../../../fonts/Raleway-Light.ttf), | ||
| 23 | url(../../../../interface/resources/fonts/Raleway-Light.ttf), | ||
| 24 | url(../fonts/Raleway-Light.ttf); | ||
| 25 | } | ||
| 26 | |||
| 27 | @font-face { | ||
| 28 | font-family: Raleway-Bold; | ||
| 29 | src: url(../../../../resources/fonts/Raleway-Bold.ttf), | ||
| 30 | url(../../../../fonts/Raleway-Bold.ttf), | ||
| 31 | url(../../../../interface/resources/fonts/Raleway-Bold.ttf), | ||
| 32 | url(../fonts/Raleway-Bold.ttf); | ||
| 33 | } | ||
| 34 | |||
| 35 | @font-face { | ||
| 36 | font-family: Raleway-SemiBold; | ||
| 37 | src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), | ||
| 38 | url(../../../../fonts/Raleway-SemiBold.ttf), | ||
| 39 | url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf), | ||
| 40 | url(../fonts/Raleway-SemiBold.ttf); | ||
| 41 | } | ||
| 42 | |||
| 43 | @font-face { | ||
| 44 | font-family: FiraSans-SemiBold; | ||
| 45 | src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), | ||
| 46 | url(../../../../fonts/FiraSans-SemiBold.ttf), | ||
| 47 | url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf), | ||
| 48 | url(../fonts/FiraSans-SemiBold.ttf); | ||
| 49 | } | ||
| 50 | |||
| 51 | @font-face { | ||
| 52 | font-family: AnonymousPro-Regular; | ||
| 53 | src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), | ||
| 54 | url(../../../../fonts/AnonymousPro-Regular.ttf), | ||
| 55 | url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf), | ||
| 56 | url(../fonts/AnonymousPro-Regular.ttf); | ||
| 57 | } | ||
| 58 | |||
| 59 | @font-face { | ||
| 60 | font-family: HiFi-Glyphs; | ||
| 61 | src: url(../../../../resources/fonts/hifi-glyphs.ttf), | ||
| 62 | url(../../../../fonts/hifi-glyphs.ttf), | ||
| 63 | url(../../../../interface/resources/fonts/hifi-glyphs.ttf), | ||
| 64 | url(../fonts/hifi-glyphs.ttf); | ||
| 65 | } | ||
| 66 | |||
| 67 | * { | ||
| 68 | margin: 0; | ||
| 69 | padding: 0; | ||
| 70 | } | ||
| 71 | |||
| 72 | body { | ||
| 73 | padding: 21px 21px 21px 21px; | ||
| 74 | |||
| 75 | color: #afafaf; | ||
| 76 | background-color: #404040; | ||
| 77 | font-family: Raleway-Regular; | ||
| 78 | font-size: 15px; | ||
| 79 | |||
| 80 | -webkit-touch-callout: none; | ||
| 81 | -webkit-user-select: none; | ||
| 82 | -khtml-user-select: none; | ||
| 83 | -moz-user-select: none; | ||
| 84 | -ms-user-select: none; | ||
| 85 | user-select: none; | ||
| 86 | |||
| 87 | overflow-x: hidden; | ||
| 88 | overflow-y: auto; | ||
| 89 | } | ||
| 90 | |||
| 91 | table { | ||
| 92 | font-family: FiraSans-SemiBold; | ||
| 93 | font-size: 15px; | ||
| 94 | color: #afafaf; | ||
| 95 | border-collapse: collapse; | ||
| 96 | width: 100%; | ||
| 97 | border: 2px solid #575757; | ||
| 98 | border-radius: 7px; | ||
| 99 | } | ||
| 100 | |||
| 101 | thead { | ||
| 102 | font-family: Raleway-Regular; | ||
| 103 | font-size: 12px; | ||
| 104 | text-transform: uppercase; | ||
| 105 | background-color: #1c1c1c; | ||
| 106 | padding: 1px 0px; | ||
| 107 | border-bottom: 1px solid #575757; | ||
| 108 | width: 100%; | ||
| 109 | } | ||
| 110 | |||
| 111 | tbody { | ||
| 112 | width: 100%; | ||
| 113 | } | ||
| 114 | |||
| 115 | tfoot { | ||
| 116 | font-family: Raleway-Light; | ||
| 117 | font-size: 13px; | ||
| 118 | background-color: #1c1c1c; | ||
| 119 | border-top: 1px solid #575757; | ||
| 120 | width: 100%; | ||
| 121 | } | ||
| 122 | |||
| 123 | tfoot tr { | ||
| 124 | background-color: #1c1cff; | ||
| 125 | } | ||
| 126 | |||
| 127 | thead tr { | ||
| 128 | height: 26px; /* 28px with thead padding */ | ||
| 129 | } | ||
| 130 | |||
| 131 | thead th { | ||
| 132 | height: 26px; | ||
| 133 | background-color: #1c1c1c; | ||
| 134 | border-right: 1px solid #575757; | ||
| 135 | } | ||
| 136 | |||
| 137 | thead th:last-child { | ||
| 138 | border: none; | ||
| 139 | } | ||
| 140 | |||
| 141 | tbody td { | ||
| 142 | height: 26px; | ||
| 143 | } | ||
| 144 | |||
| 145 | tfoot td { | ||
| 146 | height: 18px; | ||
| 147 | width: 100%; | ||
| 148 | background-color: #1c1c1c; | ||
| 149 | margin-left: 12px; | ||
| 150 | } | ||
| 151 | |||
| 152 | tr { | ||
| 153 | width: 100%; | ||
| 154 | cursor: pointer; | ||
| 155 | } | ||
| 156 | |||
| 157 | tr:nth-child(odd) { | ||
| 158 | background-color: #2e2e2e; | ||
| 159 | } | ||
| 160 | |||
| 161 | tr:nth-child(even) { | ||
| 162 | background-color: #1c1c1c; | ||
| 163 | } | ||
| 164 | |||
| 165 | tr:focus { | ||
| 166 | outline: none; | ||
| 167 | } | ||
| 168 | |||
| 169 | tr.selected { | ||
| 170 | color: #000000; | ||
| 171 | background-color: #00b4ef; | ||
| 172 | } | ||
| 173 | |||
| 174 | tr.selected + tr.selected { | ||
| 175 | border-top: 1px solid #2e2e2e; | ||
| 176 | } | ||
| 177 | |||
| 178 | th { | ||
| 179 | text-align: center; | ||
| 180 | word-wrap: nowrap; | ||
| 181 | white-space: nowrap; | ||
| 182 | padding-left: 12px; | ||
| 183 | padding-right: 12px; | ||
| 184 | } | ||
| 185 | |||
| 186 | td { | ||
| 187 | overflow: hidden; | ||
| 188 | text-overflow: ellipsis; | ||
| 189 | white-space: nowrap; | ||
| 190 | word-wrap: nowrap; | ||
| 191 | padding-left: 12px; | ||
| 192 | padding-right: 12px; | ||
| 193 | } | ||
| 194 | |||
| 195 | td.url { | ||
| 196 | white-space: nowrap; | ||
| 197 | overflow: hidden; | ||
| 198 | } | ||
| 199 | |||
| 200 | |||
| 201 | input[type="text"], input[type="number"], textarea { | ||
| 202 | margin: 0; | ||
| 203 | padding: 0 12px; | ||
| 204 | color: #afafaf; | ||
| 205 | background-color: #252525; | ||
| 206 | border: none; | ||
| 207 | font-family: FiraSans-SemiBold; | ||
| 208 | font-size: 15px; | ||
| 209 | } | ||
| 210 | |||
| 211 | textarea { | ||
| 212 | font-family: AnonymousPro-Regular; | ||
| 213 | font-size: 16px; | ||
| 214 | padding-top: 5px; | ||
| 215 | padding-bottom: 5px; | ||
| 216 | min-height: 64px; | ||
| 217 | width: 100%; | ||
| 218 | resize: vertical; | ||
| 219 | } | ||
| 220 | |||
| 221 | input::-webkit-input-placeholder { | ||
| 222 | font-style: italic; | ||
| 223 | } | ||
| 224 | |||
| 225 | input:focus, textarea:focus { | ||
| 226 | color: #fff; | ||
| 227 | background-color: #000; | ||
| 228 | outline: 1px solid #00b4ef; | ||
| 229 | outline-offset: -1px; | ||
| 230 | } | ||
| 231 | |||
| 232 | input::selection, textarea::selection { | ||
| 233 | color: #000000; | ||
| 234 | background-color: #00b4ef; | ||
| 235 | } | ||
| 236 | |||
| 237 | input.search { | ||
| 238 | border-radius: 14px; | ||
| 239 | } | ||
| 240 | |||
| 241 | input.search:focus { | ||
| 242 | outline: none; | ||
| 243 | box-sizing: border-box; | ||
| 244 | height: 26px; | ||
| 245 | margin-top: 1px; | ||
| 246 | margin-bottom: 1px; | ||
| 247 | box-shadow: 0 0 0px 1px #00b4ef; | ||
| 248 | } | ||
| 249 | |||
| 250 | input:disabled, textarea:disabled { | ||
| 251 | background-color: #383838; | ||
| 252 | color: #afafaf; | ||
| 253 | } | ||
| 254 | |||
| 255 | input[type="text"] { | ||
| 256 | height: 28px; | ||
| 257 | width: 100%; | ||
| 258 | } | ||
| 259 | |||
| 260 | input[type="number"] { | ||
| 261 | position: relative; | ||
| 262 | height: 28px; | ||
| 263 | width: 124px; | ||
| 264 | } | ||
| 265 | |||
| 266 | input[type=number] { | ||
| 267 | padding-right: 3px; | ||
| 268 | } | ||
| 269 | input[type=number]::-webkit-inner-spin-button { | ||
| 270 | -webkit-appearance: none; | ||
| 271 | opacity: 1.0; | ||
| 272 | display: block; | ||
| 273 | position: relative; | ||
| 274 | width: 10px; | ||
| 275 | height: 100%; | ||
| 276 | overflow: hidden; | ||
| 277 | font-family: hifi-glyphs; | ||
| 278 | font-size: 46px; | ||
| 279 | color: #afafaf; | ||
| 280 | cursor: pointer; | ||
| 281 | /*background-color: #000000;*/ | ||
| 282 | } | ||
| 283 | input[type=number]::-webkit-inner-spin-button:before, | ||
| 284 | input[type=number]::-webkit-inner-spin-button:after { | ||
| 285 | position:absolute; | ||
| 286 | left: -19px; | ||
| 287 | line-height: 8px; | ||
| 288 | text-align: center; | ||
| 289 | } | ||
| 290 | input[type=number]::-webkit-inner-spin-button:before { | ||
| 291 | content: "6"; | ||
| 292 | top: 4px; | ||
| 293 | } | ||
| 294 | input[type=number]::-webkit-inner-spin-button:after { | ||
| 295 | content: "5"; | ||
| 296 | bottom: 4px; | ||
| 297 | } | ||
| 298 | |||
| 299 | input[type=number].hover-up::-webkit-inner-spin-button:before, | ||
| 300 | input[type=number].hover-down::-webkit-inner-spin-button:after { | ||
| 301 | color: #ffffff; | ||
| 302 | } | ||
| 303 | |||
| 304 | input.no-spin::-webkit-outer-spin-button, | ||
| 305 | input.no-spin::-webkit-inner-spin-button { | ||
| 306 | display: none; | ||
| 307 | -webkit-appearance: none; | ||
| 308 | margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ | ||
| 309 | padding-right: 12px; | ||
| 310 | } | ||
| 311 | |||
| 312 | input[type=button] { | ||
| 313 | font-family: Raleway-Bold; | ||
| 314 | font-size: 13px; | ||
| 315 | text-transform: uppercase; | ||
| 316 | vertical-align: top; | ||
| 317 | height: 28px; | ||
| 318 | min-width: 120px; | ||
| 319 | padding: 0px 18px; | ||
| 320 | margin-right: 6px; | ||
| 321 | border-radius: 5px; | ||
| 322 | border: none; | ||
| 323 | color: #fff; | ||
| 324 | background-color: #000; | ||
| 325 | background: linear-gradient(#343434 20%, #000 100%); | ||
| 326 | cursor: pointer; | ||
| 327 | } | ||
| 328 | |||
| 329 | input[type=button].glyph { | ||
| 330 | font-family: HiFi-Glyphs; | ||
| 331 | font-size: 20px; | ||
| 332 | text-transform: none; | ||
| 333 | min-width: 32px; | ||
| 334 | padding: 0; | ||
| 335 | } | ||
| 336 | |||
| 337 | input[type=button].red { | ||
| 338 | color: #fff; | ||
| 339 | background-color: #94132e; | ||
| 340 | background: linear-gradient(#d42043 20%, #94132e 100%); | ||
| 341 | } | ||
| 342 | input[type=button].blue { | ||
| 343 | color: #fff; | ||
| 344 | background-color: #1080b8; | ||
| 345 | background: linear-gradient(#00b4ef 20%, #1080b8 100%); | ||
| 346 | } | ||
| 347 | input[type=button].white { | ||
| 348 | color: #121212; | ||
| 349 | background-color: #afafaf; | ||
| 350 | background: linear-gradient(#fff 20%, #afafaf 100%); | ||
| 351 | } | ||
| 352 | |||
| 353 | input[type=button]:enabled:hover { | ||
| 354 | background: linear-gradient(#000, #000); | ||
| 355 | border: none; | ||
| 356 | } | ||
| 357 | input[type=button].red:enabled:hover { | ||
| 358 | background: linear-gradient(#d42043, #d42043); | ||
| 359 | border: none; | ||
| 360 | } | ||
| 361 | input[type=button].blue:enabled:hover { | ||
| 362 | background: linear-gradient(#00b4ef, #00b4ef); | ||
| 363 | border: none; | ||
| 364 | } | ||
| 365 | input[type=button].white:enabled:hover { | ||
| 366 | background: linear-gradient(#fff, #fff); | ||
| 367 | border: none; | ||
| 368 | } | ||
| 369 | |||
| 370 | input[type=button]:active { | ||
| 371 | background: linear-gradient(#343434, #343434); | ||
| 372 | } | ||
| 373 | input[type=button].red:active { | ||
| 374 | background: linear-gradient(#94132e, #94132e); | ||
| 375 | } | ||
| 376 | input[type=button].blue:active { | ||
| 377 | background: linear-gradient(#1080b8, #1080b8); | ||
| 378 | } | ||
| 379 | input[type=button].white:active { | ||
| 380 | background: linear-gradient(#afafaf, #afafaf); | ||
| 381 | } | ||
| 382 | |||
| 383 | input[type=button]:disabled { | ||
| 384 | color: #252525; | ||
| 385 | background: linear-gradient(#575757 20%, #252525 100%); | ||
| 386 | } | ||
| 387 | |||
| 388 | input[type=button][pressed=pressed] { | ||
| 389 | color: #00b4ef; | ||
| 390 | } | ||
| 391 | |||
| 392 | input[type=checkbox] { | ||
| 393 | display: none; | ||
| 394 | } | ||
| 395 | input[type=checkbox] + label { | ||
| 396 | padding-left: 24px; | ||
| 397 | background-position-y: 6px; | ||
| 398 | background-repeat: no-repeat; | ||
| 399 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC); | ||
| 400 | } | ||
| 401 | input[type=checkbox]:enabled + label:hover { | ||
| 402 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=); | ||
| 403 | } | ||
| 404 | input[type=checkbox]:checked + label { | ||
| 405 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC); | ||
| 406 | } | ||
| 407 | input[type=checkbox]:checked + label:hover { | ||
| 408 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=); | ||
| 409 | } | ||
| 410 | |||
| 411 | .icon-input input { | ||
| 412 | position: relative; | ||
| 413 | padding-left: 36px; | ||
| 414 | } | ||
| 415 | .icon-input span { | ||
| 416 | position: absolute; | ||
| 417 | left: 6px; | ||
| 418 | top: -2px; | ||
| 419 | font-family: hifi-glyphs; | ||
| 420 | font-size: 30px; | ||
| 421 | color: #afafaf; | ||
| 422 | } | ||
| 423 | .icon-input input:focus + span { | ||
| 424 | color: #ffffff; | ||
| 425 | } | ||
| 426 | |||
| 427 | .selectable { | ||
| 428 | -webkit-touch-callout: text; | ||
| 429 | -webkit-user-select: text; | ||
| 430 | -khtml-user-select: text; | ||
| 431 | -moz-user-select: text; | ||
| 432 | -ms-user-select: text; | ||
| 433 | user-select: text; | ||
| 434 | cursor: text; | ||
| 435 | } | ||
| 436 | |||
| 437 | .color-box { | ||
| 438 | display: inline-block; | ||
| 439 | width: 15pt; | ||
| 440 | height: 15pt; | ||
| 441 | border: 0.75pt solid black; | ||
| 442 | margin: 1.5pt; | ||
| 443 | cursor: pointer; | ||
| 444 | } | ||
| 445 | |||
| 446 | .color-box.highlight { | ||
| 447 | width: 13.5pt; | ||
| 448 | height: 13.5pt; | ||
| 449 | border: 1.5pt solid black; | ||
| 450 | } | ||
| 451 | |||
| 452 | |||
| 453 | .section-header, .sub-section-header, hr { | ||
| 454 | display: table; | ||
| 455 | width: 100%; | ||
| 456 | margin: 21px -21px 0 -21px; | ||
| 457 | padding: 14px 21px 0 21px; | ||
| 458 | font-family: Raleway-Regular; | ||
| 459 | font-size: 12px; | ||
| 460 | color: #afafaf; | ||
| 461 | height: 28px; | ||
| 462 | text-transform: uppercase; | ||
| 463 | outline: none; | ||
| 464 | } | ||
| 465 | |||
| 466 | .section-header { | ||
| 467 | position: relative; | ||
| 468 | background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAqCAIAAAAbNW1vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAmSURBVChTY1BFAgzhSIDBAQmMcoYHRwIJMCgjAQZ9JMBgBQdWVgBh5XmBV5A2FQAAAABJRU5ErkJggg==) repeat-x top left; | ||
| 469 | } | ||
| 470 | |||
| 471 | .sub-section-header, .no-collapse, hr { | ||
| 472 | background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; | ||
| 473 | } | ||
| 474 | |||
| 475 | .section-header:first-child { | ||
| 476 | margin-top: -2px; | ||
| 477 | padding-top: 0; | ||
| 478 | background: none; | ||
| 479 | height: auto; | ||
| 480 | } | ||
| 481 | |||
| 482 | .sub-section-header { | ||
| 483 | margin-bottom: -10px; | ||
| 484 | } | ||
| 485 | |||
| 486 | .section-header span { | ||
| 487 | font-family: HiFi-Glyphs; | ||
| 488 | font-size: 30px; | ||
| 489 | float: right; | ||
| 490 | position: absolute; | ||
| 491 | top: 4px; | ||
| 492 | right: 13px; | ||
| 493 | } | ||
| 494 | |||
| 495 | .section-header[collapsed="true"] { | ||
| 496 | margin-bottom: -21px; | ||
| 497 | } | ||
| 498 | |||
| 499 | hr { | ||
| 500 | border: none; | ||
| 501 | padding-top: 2px; | ||
| 502 | } | ||
| 503 | |||
| 504 | .text-group[collapsed="true"] ~ .text-group, | ||
| 505 | .zone-group[collapsed="true"] ~ .zone-group, | ||
| 506 | .web-group[collapsed="true"] ~ .web-group, | ||
| 507 | .hyperlink-group[collapsed="true"] ~ .hyperlink-group, | ||
| 508 | .spatial-group[collapsed="true"] ~ .spatial-group, | ||
| 509 | .physical-group[collapsed="true"] ~ .physical-group, | ||
| 510 | .behavior-group[collapsed="true"] ~ .behavior-group, | ||
| 511 | .model-group[collapsed="true"] ~ .model-group, | ||
| 512 | .light-group[collapsed="true"] ~ .light-group { | ||
| 513 | display: none !important; | ||
| 514 | } | ||
| 515 | |||
| 516 | |||
| 517 | .property { | ||
| 518 | display: table; | ||
| 519 | width: 100%; | ||
| 520 | margin-top: 21px; | ||
| 521 | min-height: 28px; | ||
| 522 | } | ||
| 523 | |||
| 524 | .property.checkbox { | ||
| 525 | width: auto; | ||
| 526 | } | ||
| 527 | |||
| 528 | .property label, .number label { | ||
| 529 | display: table-cell; | ||
| 530 | vertical-align: middle; | ||
| 531 | font-family: Raleway-SemiBold; | ||
| 532 | font-size: 14px; | ||
| 533 | } | ||
| 534 | .property label .unit, .number label .unit { | ||
| 535 | margin-left: 8px; | ||
| 536 | font-family: Raleway-Light; | ||
| 537 | font-size: 13px; | ||
| 538 | } | ||
| 539 | |||
| 540 | .value { | ||
| 541 | display: block; | ||
| 542 | min-height: 18px; | ||
| 543 | } | ||
| 544 | .value label { | ||
| 545 | display: inline-block; | ||
| 546 | vertical-align: top; | ||
| 547 | width: 48px; | ||
| 548 | } | ||
| 549 | .value span { | ||
| 550 | font-family: FiraSans-SemiBold; | ||
| 551 | font-size: 15px; | ||
| 552 | } | ||
| 553 | |||
| 554 | .checkbox + .checkbox { | ||
| 555 | margin-top: 0; | ||
| 556 | } | ||
| 557 | |||
| 558 | .checkbox-sub-props { | ||
| 559 | margin-top: 18px; | ||
| 560 | } | ||
| 561 | |||
| 562 | .property .number { | ||
| 563 | float: left; | ||
| 564 | } | ||
| 565 | .property .number + .number { | ||
| 566 | margin-left: 10px; | ||
| 567 | } | ||
| 568 | |||
| 569 | .text label, .url label, .number label, .textarea label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { | ||
| 570 | float: left; | ||
| 571 | margin-left: 1px; | ||
| 572 | margin-bottom: 3px; | ||
| 573 | margin-top: -2px; | ||
| 574 | } | ||
| 575 | |||
| 576 | .number > input { | ||
| 577 | clear: both; | ||
| 578 | float: left; | ||
| 579 | } | ||
| 580 | .number > span { | ||
| 581 | clear: both; | ||
| 582 | float: left; | ||
| 583 | } | ||
| 584 | .xyz > div, .pyr > div, .gen > div { | ||
| 585 | clear: both; | ||
| 586 | } | ||
| 587 | |||
| 588 | .dropdown { | ||
| 589 | position: relative; | ||
| 590 | margin-bottom: -17px; | ||
| 591 | } | ||
| 592 | |||
| 593 | .dropdown select { | ||
| 594 | clear: both; | ||
| 595 | } | ||
| 596 | |||
| 597 | .dropdown dl { | ||
| 598 | clear: both; | ||
| 599 | } | ||
| 600 | .dropdown dl { | ||
| 601 | font-family: FiraSans-SemiBold; | ||
| 602 | font-size: 15px; | ||
| 603 | width: 292px; | ||
| 604 | height: 28px; | ||
| 605 | padding: 0 28px 0 12px; | ||
| 606 | color: #afafaf; | ||
| 607 | background: linear-gradient(#7d7d7d 20%, #686a68 100%); | ||
| 608 | position: relative; | ||
| 609 | } | ||
| 610 | .dropdown dl[dropped="true"] { | ||
| 611 | color: #404040; | ||
| 612 | background: linear-gradient(#afafaf, #afafaf); | ||
| 613 | } | ||
| 614 | |||
| 615 | .dropdown dt { | ||
| 616 | height: 100%; | ||
| 617 | box-sizing: border-box; | ||
| 618 | border-right: 1px solid #121212; | ||
| 619 | width: 100%; | ||
| 620 | } | ||
| 621 | .dropdown dt:hover { | ||
| 622 | color: #404040; | ||
| 623 | } | ||
| 624 | .dropdown dt:focus { | ||
| 625 | outline: none; | ||
| 626 | } | ||
| 627 | .dropdown dt span:first-child { | ||
| 628 | display: inline-block; | ||
| 629 | position: relative; | ||
| 630 | top: 5px; | ||
| 631 | } | ||
| 632 | .dropdown dt span:last-child { | ||
| 633 | font-family: HiFi-Glyphs; | ||
| 634 | font-size: 42px; | ||
| 635 | float: right; | ||
| 636 | margin-right: -48px; | ||
| 637 | position: relative; | ||
| 638 | left: -12px; | ||
| 639 | top: -9px; | ||
| 640 | } | ||
| 641 | |||
| 642 | .dropdown dd { | ||
| 643 | position: absolute; | ||
| 644 | top: 28px; | ||
| 645 | left: 3px; | ||
| 646 | display: none; | ||
| 647 | } | ||
| 648 | .dropdown dl[dropped="true"] dd { | ||
| 649 | display: block; | ||
| 650 | } | ||
| 651 | |||
| 652 | .dropdown li { | ||
| 653 | list-style-type: none; | ||
| 654 | padding: 3px 0 1px 12px; | ||
| 655 | width: 320px; | ||
| 656 | height: auto; | ||
| 657 | font-family: FiraSans-SemiBold; | ||
| 658 | font-size: 15px; | ||
| 659 | color: #404040; | ||
| 660 | background-color: #afafaf | ||
| 661 | } | ||
| 662 | .dropdown li:hover { | ||
| 663 | background-color: #00b4ef; | ||
| 664 | } | ||
| 665 | |||
| 666 | .dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] { | ||
| 667 | color: #252525; | ||
| 668 | background: linear-gradient(#575757 20%, #252525 100%); | ||
| 669 | } | ||
| 670 | .dropdown dl[disabled="disabled"] dd { | ||
| 671 | display: none; | ||
| 672 | } | ||
| 673 | .dropdown dl[disabled="disabled"] dt:hover { | ||
| 674 | color: #252525; | ||
| 675 | } | ||
587 more lines aren’t shown here.
File renamed without changes.
Binary file not shown.
This diff is large: its 95 lines aren’t shown here.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This diff is large: its 95 lines aren’t shown here.
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
This diff is large: its 50 lines aren’t shown here.
This diff is large: its 23 lines aren’t shown here.
This diff is large: its 102 lines aren’t shown here.
This diff is large: its 28 lines aren’t shown here.