diff --git a/.gitignore b/.gitignore index 1ffb93fe80..df91e0ca7b 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,8 @@ TAGS node_modules npm-debug.log +# ignore qmlc files generated from qml as cache +*.qmlc # Android studio files *___jb_old___ @@ -88,4 +90,4 @@ android/app/src/main/assets interface/compiledResources # GPUCache -interface/resources/GPUCache/* \ No newline at end of file +interface/resources/GPUCache/* diff --git a/BUILD.md b/BUILD.md index feed677828..ba38f4b51d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ### Dependencies - [cmake](https://cmake.org/download/): 3.9 -- [Qt](https://www.qt.io/download-open-source): 5.9.1 +- [Qt](https://www.qt.io/download-open-source): 5.10.1 - [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities. - [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -46,8 +46,8 @@ This can either be entered directly into your shell session before you build or The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake #### Generating build files @@ -66,7 +66,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/lib/cmake #### Finding Dependencies diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index 3d6d056d79..8c9263b6e7 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -53,7 +53,7 @@ Enter the repository `android` directory Execute a gradle pre-build setup. This step should only need to be done once -`gradle setupDepedencies` +`gradle setupDependencies` # Building & Running diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 038f53154c..64c0e9a643 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -14,8 +14,8 @@ Should you choose not to install Qt5 via a package manager that handles dependen Install qt: ```bash -wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.6.1_5.6.1_amd64.deb -sudo dpkg -i hifi-qt5.6.1_5.6.1_amd64.deb +wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.10.1_5.10.1_amd64.deb +sudo dpkg -i hifi-qt5.10.1_5.10.1_amd64.deb ``` Install build dependencies: @@ -66,7 +66,7 @@ cd hifi/build Prepare makefiles: ```bash -cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.6.1/5.6/gcc_64/lib/cmake .. +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake .. ``` Start compilation and get a cup of coffee: @@ -74,7 +74,7 @@ Start compilation and get a cup of coffee: make domain-server assignment-client interface ``` -In a server does not make sense to compile interface +In a server does not make sense to compile interface ### Running the software diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 6b66863534..62102b3e18 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -20,7 +20,7 @@ Note that this uses the version from the homebrew formula at the time of this wr Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations. For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH: - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.10.1/lib/cmake 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. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index eea1f85e5b..3222e75c66 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -1,13 +1,13 @@ This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit. ## Building High Fidelity -Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. +Note: We are now using Visual Studio 2017 and Qt 5.10.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. ### Step 1. Visual Studio 2017 -If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). +If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right. @@ -17,15 +17,15 @@ Download and install the latest version of CMake 3.9. Download the file named w ### Step 3. Installing Qt -Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.9.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)". +Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)". -Note: Installing the Sources is optional but recommended if you have room for them (~2GB). +Note: Installing the Sources is optional but recommended if you have room for them (~2GB). ### Step 4. Setting Qt Environment Variable Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search). * Set "Variable name": `QT_CMAKE_PREFIX_PATH` -* Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake` +* Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake` ### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg) @@ -39,7 +39,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables * In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows` * Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl` - + ### Step 7. Running CMake to Generate Build Files Run Command Prompt from Start and run the following commands: @@ -49,7 +49,7 @@ mkdir build cd build cmake .. -G "Visual Studio 15 Win64" ``` - + Where `%HIFI_DIR%` is the directory for the highfidelity repository. ### Step 8. Making a Build @@ -74,10 +74,10 @@ Note: You can also run Interface by launching it from command line or File Explo ## Troubleshooting -For any problems after Step #7, first try this: +For any problems after Step #7, first try this: * Delete your locally cloned copy of the highfidelity repository * Restart your computer -* Redownload the [repository](https://github.com/highfidelity/hifi) +* Redownload the [repository](https://github.com/highfidelity/hifi) * Restart directions from Step #7 #### CMake gives you the same error message repeatedly after the build fails @@ -90,4 +90,4 @@ Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. Verify that #### Qt is throwing an error -Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. +Make sure you have the correct version (5.10.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. diff --git a/CMakeLists.txt b/CMakeLists.txt index 25fd4731e6..93b784b462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# If we're running under the gradle build, HIFI_ANDROID will be set here, but +# If we're running under the gradle build, HIFI_ANDROID will be set here, but # ANDROID will not be set until after the `project` statement. This is the *ONLY* # place you need to use `HIFI_ANDROID` instead of `ANDROID` if (WIN32 AND NOT HIFI_ANDROID) @@ -14,8 +14,12 @@ include("cmake/init.cmake") include("cmake/compiler.cmake") if (BUILD_SCRIBE_ONLY) - add_subdirectory(tools/scribe) - return() + add_subdirectory(tools/scribe) + return() +endif() + +if (NOT DEFINED CLIENT_ONLY) + set(CLIENT_ONLY 0) endif() if (NOT DEFINED SERVER_ONLY) @@ -23,80 +27,93 @@ if (NOT DEFINED SERVER_ONLY) endif() if (ANDROID OR UWP) - set(MOBILE 1) + set(MOBILE 1) else() - set(MOBILE 0) + set(MOBILE 0) endif() +set(BUILD_CLIENT_OPTION ON) +set(BUILD_SERVER_OPTION ON) +set(BUILD_TESTS_OPTION ON) +set(BUILD_TOOLS_OPTION ON) +set(BUILD_INSTALLER_OPTION ON) +set(GLES_OPTION OFF) +set(DISABLE_QML_OPTION OFF) +set(DOWNLOAD_SERVERLESS_CONTENT_OPTION OFF) + if (ANDROID OR UWP) - option(BUILD_SERVER "Build server components" OFF) - option(BUILD_TOOLS "Build tools" OFF) - option(BUILD_INSTALLER "Build installer" OFF) -else() - option(BUILD_SERVER "Build server components" ON) - option(BUILD_TOOLS "Build tools" ON) - option(BUILD_INSTALLER "Build installer" ON) + set(BUILD_SERVER_OPTION OFF) + set(BUILD_TOOLS_OPTION OFF) + set(BUILD_INSTALLER OFF) +endif() + +if (CLIENT_ONLY) + set(BUILD_SERVER_OPTION OFF) endif() if (SERVER_ONLY) - option(BUILD_CLIENT "Build client components" OFF) - option(BUILD_TESTS "Build tests" OFF) -else() - option(BUILD_CLIENT "Build client components" ON) - option(BUILD_TESTS "Build tests" ON) + set(BUILD_CLIENT_OPTION OFF) + set(BUILD_TESTS_OPTION OFF) endif() if (ANDROID) - option(USE_GLES "Use OpenGL ES" ON) - set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) + set(GLES_OPTION ON) + set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) else () - option(USE_GLES "Use OpenGL ES" OFF) - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) + set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) endif () if (USE_GLES AND (NOT ANDROID)) - option(DISABLE_QML "Disable QML" ON) -else() - option(DISABLE_QML "Disable QML" OFF) + set(DISABLE_QML_OPTION ON) endif() + +option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION}) +option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION}) +option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION}) +option(BUILD_TOOLS "Build tools" ${BUILD_TOOLS_OPTION}) +option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION}) +option(USE_GLES "Use OpenGL ES" ${GLES_OPTION}) +option(DISABLE_QML "Disable QML" ${DISABLE_QML_OPTION}) option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF) - - +option( + DOWNLOAD_SERVERLESS_CONTENT + "Download and setup default serverless content beside Interface" + ${DOWNLOAD_SERVERLESS_CONTENT_OPTION} +) set(PLATFORM_QT_GL OpenGL) if (USE_GLES) add_definitions(-DUSE_GLES) - set(PLATFORM_GL_BACKEND gpu-gles) + set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles) else() - set(PLATFORM_GL_BACKEND gpu-gl) + set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl) endif() - foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}") endforeach() - -MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) -MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) -MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) -MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS}) -MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER}) -MESSAGE(STATUS "GL ES: " ${USE_GLES}) +MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) +MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) +MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) +MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS}) +MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER}) +MESSAGE(STATUS "GL ES: " ${USE_GLES}) +MESSAGE(STATUS "DL serverless content: " ${DOWNLOAD_SERVERLESS_CONTENT}) if (DISABLE_QML) -MESSAGE(STATUS "QML disabled!") -add_definitions(-DDISABLE_QML) + MESSAGE(STATUS "QML disabled!") + add_definitions(-DDISABLE_QML) endif() if (DISABLE_KTX_CACHE) -MESSAGE(STATUS "KTX cache disabled!") -add_definitions(-DDISABLE_KTX_CACHE) + MESSAGE(STATUS "KTX cache disabled!") + add_definitions(-DDISABLE_KTX_CACHE) endif() if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING}) - MESSAGE(STATUS "Memory debugging is enabled") + MESSAGE(STATUS "Memory debugging is enabled") endif() # @@ -132,8 +149,8 @@ set_packaging_parameters() # FIXME hack to work on the proper Android toolchain if (ANDROID) - add_subdirectory(android/app) - return() + add_subdirectory(android/app) + return() endif() # add subdirectories for all targets @@ -148,32 +165,30 @@ if (BUILD_SERVER) endif() if (BUILD_CLIENT) - add_subdirectory(interface) - set_target_properties(interface PROPERTIES FOLDER "Apps") - if (ANDROID) - add_subdirectory(gvr-interface) - set_target_properties(gvr-interface PROPERTIES FOLDER "Apps") - endif() + add_subdirectory(interface) + set_target_properties(interface PROPERTIES FOLDER "Apps") + + option(USE_SIXENSE "Build Interface with sixense library/plugin" OFF) endif() if (BUILD_CLIENT OR BUILD_SERVER) - add_subdirectory(plugins) + add_subdirectory(plugins) endif() # BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway add_subdirectory(tools) if (BUILD_TESTS) - add_subdirectory(tests) + add_subdirectory(tests) endif() if (BUILD_INSTALLER) - if (UNIX) - install( - DIRECTORY "${CMAKE_SOURCE_DIR}/scripts" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface - COMPONENT ${CLIENT_COMPONENT} - ) - endif() - generate_installers() + if (UNIX) + install( + DIRECTORY "${CMAKE_SOURCE_DIR}/scripts" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface + COMPONENT ${CLIENT_COMPONENT} + ) + endif() + generate_installers() endif() diff --git a/INSTALL.md b/INSTALL.md index 79d7e96977..e07d28a43d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -17,6 +17,8 @@ To produce an executable installer on Windows, the following are required: - [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3 - [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c - [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 +- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0 +- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index efb7abbb93..b3a8c87649 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,9 +19,9 @@ android:allowBackup="true" android:screenOrientation="unspecified" android:theme="@style/NoSystemUI" - android:icon="@mipmap/ic_launcher" + android:icon="@drawable/ic_launcher" android:launchMode="singleTop" - android:roundIcon="@mipmap/ic_launcher_round"> + android:roundIcon="@drawable/ic_launcher"> @@ -44,7 +44,6 @@ android:label="@string/app_name" android:screenOrientation="landscape" android:launchMode="singleTop" - android:enableVrMode="com.google.vr.vrcore/com.google.vr.vrcore.common.VrCoreListenerService" > diff --git a/android/app/src/main/res/drawable/ic_launcher.xml b/android/app/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000000..03b1edc4e9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/icon.png b/android/app/src/main/res/drawable/icon.png deleted file mode 100644 index 70aaf9b4ed..0000000000 Binary files a/android/app/src/main/res/drawable/icon.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d376d7af88..0000000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 81a137957d..0000000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 88b1be5903..0000000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index d032c30014..0000000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4a018d4ed9..0000000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 3cccf1037b..0000000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e97062e0ee..0000000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8d142881da..0000000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f01203c738..0000000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 211e298961..0000000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/build.gradle b/android/build.gradle index 22273f9059..1dfef97f91 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -137,6 +137,13 @@ def packages = [ checksum: '20768f298f53b195e71b414b0ae240c4', sharedLibFolder: 'lib/release', includeLibs: ['libtbb.so', 'libtbbmalloc.so'], + ], + hifiAC: [ + file: 'libplugins_libhifiCodec.zip', + versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G', + checksum: '9412a8e12c88a4096c1fc843bb9fe52d', + sharedLibFolder: '', + includeLibs: ['libplugins_libhifiCodec.so'] ] ] @@ -353,6 +360,7 @@ task verifyGvr(type: Verify) { def p = packages['gvr']; src new File(baseFolder, task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyDependencyDownloads(dependsOn: downloadDependencies) { } verifyDependencyDownloads.dependsOn verifyQt @@ -362,6 +370,7 @@ verifyDependencyDownloads.dependsOn verifyGvr verifyDependencyDownloads.dependsOn verifyOpenSSL verifyDependencyDownloads.dependsOn verifyPolyvox verifyDependencyDownloads.dependsOn verifyTBB +verifyDependencyDownloads.dependsOn verifyHifiAC task extractDependencies(dependsOn: verifyDependencyDownloads) { doLast { diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 87827a27d9..23df64be2e 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -46,15 +46,68 @@ static const uint8_t CPU_AFFINITY_COUNT_LOW = 1; static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000; #endif -const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; - static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" }; static QStringList BAKEABLE_TEXTURE_EXTENSIONS; -static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {}; +static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = { }; + static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js"; +static const ModelBakeVersion CURRENT_MODEL_BAKE_VERSION = (ModelBakeVersion)((BakeVersion)ModelBakeVersion::COUNT - 1); +static const TextureBakeVersion CURRENT_TEXTURE_BAKE_VERSION = (TextureBakeVersion)((BakeVersion)TextureBakeVersion::COUNT - 1); +static const ScriptBakeVersion CURRENT_SCRIPT_BAKE_VERSION = (ScriptBakeVersion)((BakeVersion)ScriptBakeVersion::COUNT - 1); + +BakedAssetType assetTypeForExtension(const QString& extension) { + auto extensionLower = extension.toLower(); + if (BAKEABLE_MODEL_EXTENSIONS.contains(extensionLower)) { + return Model; + } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extensionLower.toLocal8Bit())) { + return Texture; + } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extensionLower)) { + return Script; + } + return Undefined; +} + +BakedAssetType assetTypeForFilename(const QString& filename) { + auto dotIndex = filename.lastIndexOf("."); + if (dotIndex == -1) { + return BakedAssetType::Undefined; + } + + auto extension = filename.mid(dotIndex + 1); + return assetTypeForExtension(extension); +} + +QString bakedFilenameForAssetType(BakedAssetType type) { + switch (type) { + case Model: + return BAKED_MODEL_SIMPLE_NAME; + case Texture: + return BAKED_TEXTURE_SIMPLE_NAME; + case Script: + return BAKED_SCRIPT_SIMPLE_NAME; + default: + return ""; + } +} + +BakeVersion currentBakeVersionForAssetType(BakedAssetType type) { + switch (type) { + case Model: + return (BakeVersion)CURRENT_MODEL_BAKE_VERSION; + case Texture: + return (BakeVersion)CURRENT_TEXTURE_BAKE_VERSION; + case Script: + return (BakeVersion)CURRENT_SCRIPT_BAKE_VERSION; + default: + return 0; + } +} + +const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; + void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) { qDebug() << "Starting bake for: " << assetPath << assetHash; auto it = _pendingBakes.find(assetHash); @@ -167,36 +220,38 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU return false; } - auto dotIndex = path.lastIndexOf("."); - if (dotIndex == -1) { + BakedAssetType type = assetTypeForFilename(path); + + if (type == Undefined) { return false; } - auto extension = path.mid(dotIndex + 1); + QString bakedFilename = bakedFilenameForAssetType(type); + auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; + auto mappingIt = _fileMappings.find(bakedPath); + bool bakedMappingExists = mappingIt != _fileMappings.end(); - QString bakedFilename; + // If the path is mapped to the original file's hash, baking has been disabled for this + // asset + if (bakedMappingExists && mappingIt->second == assetHash) { + return false; + } bool loaded; AssetMeta meta; std::tie(loaded, meta) = readMetaFile(assetHash); - // TODO: Allow failed bakes that happened on old versions to be re-baked - if (loaded && meta.failedLastBake) { + if (type == Texture && !loaded) { return false; } - if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; - } else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) { - bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; - } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; - } else { + auto currentVersion = currentBakeVersionForAssetType(type); + + if (loaded && (meta.failedLastBake && meta.bakeVersion >= currentVersion)) { return false; } - auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; - return _fileMappings.find(bakedPath) == _fileMappings.end(); + return !bakedMappingExists || (meta.bakeVersion < currentVersion); } bool interfaceRunning() { @@ -598,15 +653,9 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi // first, figure out from the mapping extension what type of file this is auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower(); - QString bakedRootFile; - if (BAKEABLE_MODEL_EXTENSIONS.contains(assetPathExtension)) { - bakedRootFile = BAKED_MODEL_SIMPLE_NAME; - } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) { - bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME; - } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(assetPathExtension)) { - bakedRootFile = BAKED_SCRIPT_SIMPLE_NAME; - } + auto type = assetTypeForFilename(assetPath); + QString bakedRootFile = bakedFilenameForAssetType(type); auto originalAssetHash = it->second; QString redirectedAssetHash; @@ -653,9 +702,19 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi auto query = QUrlQuery(url.query()); bool isSkybox = query.hasQueryItem("skybox"); if (isSkybox) { - writeMetaFile(originalAssetHash); - if (!bakingDisabled) { - maybeBake(assetPath, originalAssetHash); + bool loaded; + AssetMeta meta; + std::tie(loaded, meta) = readMetaFile(originalAssetHash); + + if (!loaded) { + AssetMeta needsBakingMeta; + needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION; + + writeMetaFile(originalAssetHash, needsBakingMeta); + if (!bakingDisabled) { + maybeBake(assetPath, originalAssetHash); + } + } } } @@ -1275,15 +1334,19 @@ QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativ } void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) { - qDebug() << "Failed: " << originalAssetHash << assetPath << errors; + qDebug() << "Failed to bake: " << originalAssetHash << assetPath << "(" << errors << ")"; bool loaded; AssetMeta meta; std::tie(loaded, meta) = readMetaFile(originalAssetHash); + auto type = assetTypeForFilename(assetPath); + auto currentTypeVersion = currentBakeVersionForAssetType(type); + meta.failedLastBake = true; meta.lastBakeErrors = errors; + meta.bakeVersion = currentTypeVersion; writeMetaFile(originalAssetHash, meta); @@ -1373,17 +1436,20 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir; } - if (!errorCompletingBake) { - // create the meta file to store which version of the baking process we just completed - writeMetaFile(originalAssetHash); - } else { + auto type = assetTypeForFilename(originalAssetPath); + auto currentTypeVersion = currentBakeVersionForAssetType(type); + + AssetMeta meta; + meta.bakeVersion = currentTypeVersion; + meta.failedLastBake = errorCompletingBake; + + if (errorCompletingBake) { qWarning() << "Could not complete bake for" << originalAssetHash; - AssetMeta meta; - meta.failedLastBake = true; meta.lastBakeErrors = errorReason; - writeMetaFile(originalAssetHash, meta); } + writeMetaFile(originalAssetHash, meta); + _pendingBakes.remove(originalAssetHash); } @@ -1420,16 +1486,16 @@ std::pair AssetServer::readMetaFile(AssetUtils::AssetHash hash) if (error.error == QJsonParseError::NoError && doc.isObject()) { auto root = doc.object(); - auto bakeVersion = root[BAKE_VERSION_KEY].toInt(-1); + auto bakeVersion = root[BAKE_VERSION_KEY]; auto failedLastBake = root[FAILED_LAST_BAKE_KEY]; auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY]; - if (bakeVersion != -1 + if (bakeVersion.isDouble() && failedLastBake.isBool() && lastBakeErrors.isString()) { AssetMeta meta; - meta.bakeVersion = bakeVersion; + meta.bakeVersion = bakeVersion.toInt(); meta.failedLastBake = failedLastBake.toBool(); meta.lastBakeErrors = lastBakeErrors.toString(); @@ -1447,7 +1513,7 @@ bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const A // construct the JSON that will be in the meta file QJsonObject metaFileObject; - metaFileObject[BAKE_VERSION_KEY] = meta.bakeVersion; + metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion; metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake; metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors; @@ -1479,27 +1545,13 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool for (const auto& path : paths) { auto it = _fileMappings.find(path); if (it != _fileMappings.end()) { + auto type = assetTypeForFilename(path); + if (type == Undefined) { + continue; + } + QString bakedFilename = bakedFilenameForAssetType(type); + auto hash = it->second; - - auto dotIndex = path.lastIndexOf("."); - if (dotIndex == -1) { - continue; - } - - auto extension = path.mid(dotIndex + 1); - - QString bakedFilename; - - if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; - } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) { - bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; - } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; - } else { - continue; - } - auto bakedMapping = getBakeMapping(hash, bakedFilename); auto it = _fileMappings.find(bakedMapping); diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 00ab27c74d..a55a15e6fc 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -23,8 +23,47 @@ #include "RegisteredMetaTypes.h" +using BakeVersion = int; +static const BakeVersion INITIAL_BAKE_VERSION = 0; +static const BakeVersion NEEDS_BAKING_BAKE_VERSION = -1; + +enum BakedAssetType : int { + Model = 0, + Texture, + Script, + + NUM_ASSET_TYPES, + Undefined +}; + +// ATTENTION! If you change the current version for an asset type, you will also +// need to update the function currentBakeVersionForAssetType() inside of AssetServer.cpp. +enum class ModelBakeVersion : BakeVersion { + Initial = INITIAL_BAKE_VERSION, + + COUNT +}; + +// ATTENTION! See above. +enum class TextureBakeVersion : BakeVersion { + Initial = INITIAL_BAKE_VERSION, + + COUNT +}; + +// ATTENTION! See above. +enum class ScriptBakeVersion : BakeVersion { + Initial = INITIAL_BAKE_VERSION, + FixEmptyScripts, + + COUNT +}; + struct AssetMeta { - int bakeVersion { 0 }; + AssetMeta() { + } + + BakeVersion bakeVersion; bool failedLastBake { false }; QString lastBakeErrors; }; diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index ccdd2fca4f..3c8e38f278 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -322,8 +322,16 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU // stereo sources are not passed through HRTF if (streamToAdd.isStereo()) { - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) { - _mixSamples[i] += float(streamPopOutput[i] * gain / AudioConstants::MAX_SAMPLE_VALUE); + + // apply the avatar gain adjustment + auto& hrtf = listenerNodeData.hrtfForStream(sourceNodeID, streamToAdd.getStreamIdentifier()); + gain *= hrtf.getGainAdjustment(); + + const float scale = 1/32768.0f; // int16_t to float + + for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { + _mixSamples[2*i+0] += (float)streamPopOutput[2*i+0] * gain * scale; + _mixSamples[2*i+1] += (float)streamPopOutput[2*i+1] * gain * scale; } ++stats.manualStereoMixes; @@ -332,10 +340,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU // echo sources are not passed through HRTF if (isEcho) { - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i += 2) { - auto monoSample = float(streamPopOutput[i / 2] * gain / AudioConstants::MAX_SAMPLE_VALUE); - _mixSamples[i] += monoSample; - _mixSamples[i + 1] += monoSample; + + const float scale = 1/32768.0f; // int16_t to float + + for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { + float sample = (float)streamPopOutput[i] * gain * scale; + _mixSamples[2*i+0] += sample; + _mixSamples[2*i+1] += sample; } ++stats.manualEchoMixes; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3ca924b007..929941c05c 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -42,7 +42,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message) { // make sure we hear about node kills so we can tell the other nodes - connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); + connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket"); @@ -423,14 +423,15 @@ void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { } } -void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { - if (killedNode->getType() == NodeType::Agent - && killedNode->getLinkedData()) { + +void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { + if (avatarNode->getType() == NodeType::Agent + && avatarNode->getLinkedData()) { auto nodeList = DependencyManager::get(); { // decrement sessionDisplayNames table and possibly remove - QMutexLocker nodeDataLocker(&killedNode->getLinkedData()->getMutex()); - AvatarMixerClientData* nodeData = dynamic_cast(killedNode->getLinkedData()); + QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex()); + AvatarMixerClientData* nodeData = dynamic_cast(avatarNode->getLinkedData()); const QString& baseDisplayName = nodeData->getBaseDisplayName(); // No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case. if (--_sessionDisplayNames[baseDisplayName].second <= 0) { @@ -447,12 +448,12 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { // we relay avatar kill packets to agents that are not upstream // and downstream avatar mixers, if the node that was just killed was being replicated return (node->getType() == NodeType::Agent && !node->isUpstream()) || - (killedNode->isReplicated() && shouldReplicateTo(*killedNode, *node)); + (avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node)); }, [&](const SharedNodePointer& node) { if (node->getType() == NodeType::Agent) { if (!killPacket) { killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); - killPacket->write(killedNode->getUUID().toRfc4122()); + killPacket->write(avatarNode->getUUID().toRfc4122()); killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); } @@ -462,7 +463,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { if (!replicatedKillPacket) { replicatedKillPacket = NLPacket::create(PacketType::ReplicatedKillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); - replicatedKillPacket->write(killedNode->getUUID().toRfc4122()); + replicatedKillPacket->write(avatarNode->getUUID().toRfc4122()); replicatedKillPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); } @@ -479,7 +480,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { return false; } - if (node->getUUID() == killedNode->getUUID()) { + if (node->getUUID() == avatarNode->getUUID()) { return false; } @@ -489,7 +490,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { QMetaObject::invokeMethod(node->getLinkedData(), "cleanupKilledNode", Qt::AutoConnection, - Q_ARG(const QUuid&, QUuid(killedNode->getUUID()))); + Q_ARG(const QUuid&, QUuid(avatarNode->getUUID()))); } ); } @@ -605,7 +606,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes void AvatarMixer::handleKillAvatarPacket(QSharedPointer message, SharedNodePointer node) { auto start = usecTimestampNow(); - DependencyManager::get()->processKillNode(*message); + handleAvatarKilled(node); + + node->setLinkedData(nullptr); auto end = usecTimestampNow(); _handleKillAvatarPacketElapsedTime += (end - start); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index cb5f536faa..1fbfd7338b 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -39,7 +39,7 @@ public slots: /// runs the avatar mixer void run() override; - void nodeKilled(SharedNodePointer killedNode); + void handleAvatarKilled(SharedNodePointer killedNode); void sendStatsPacket() override; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 288652715a..268aba62d6 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) return 0; } -void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) { +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) { std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { itr->second = time; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 7a7210a0e8..3c2e660cbc 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -113,7 +113,7 @@ public: ViewFrustum getViewFrustum() const { return _currentViewFrustum; } uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; - void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time); + void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar]; diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index e394884dc2..d2bcbf2886 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -470,7 +470,6 @@ void EntityServer::startDynamicDomainVerification() { // Delete the entity if it doesn't pass static certificate verification tree->deleteEntity(i.value(), true); } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -490,9 +489,13 @@ void EntityServer::startDynamicDomainVerification() { if (networkReply->error() == QNetworkReply::NoError) { if (jsonObject["domain_id"].toString() != thisDomainID) { - qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() - << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value(); - tree->deleteEntity(i.value(), true); + if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) { + qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() + << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value(); + tree->deleteEntity(i.value(), true); + } else { + qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << i.value(); + } } else { qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value(); } diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index e5cee84f1b..4aa52922c0 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -311,7 +311,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } - } else if (entity->getLastEdited() > knownTimestamp->second) { + } else if (entity->getLastEdited() > knownTimestamp->second + || entity->getLastChangedOnServer() > knownTimestamp->second) { // it is known and it changed --> put it on the queue with any priority // TODO: sort these correctly _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); @@ -330,7 +331,9 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree return; } auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { + if (knownTimestamp == _knownState.end() + || entity->getLastEdited() > knownTimestamp->second + || entity->getLastChangedOnServer() > knownTimestamp->second) { _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } @@ -382,7 +385,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } - } else if (entity->getLastEdited() > knownTimestamp->second) { + } else if (entity->getLastEdited() > knownTimestamp->second + || entity->getLastChangedOnServer() > knownTimestamp->second) { // it is known and it changed --> put it on the queue with any priority // TODO: sort these correctly _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); @@ -442,12 +446,16 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream PrioritizedEntity queuedItem = _sendQueue.top(); EntityItemPointer entity = queuedItem.getEntity(); if (entity) { - // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again + const QUuid& entityID = entity->getID(); + // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again; + // also send if we previously matched since this represents change to a matched item. bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); - if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { + bool entityPreviouslyMatchedFilter = entityNodeData->sentFilteredEntity(entityID); + + if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entityID) || entityPreviouslyMatchedFilter) { if (!jsonFilters.isEmpty() && entityMatchesFilters) { // Record explicitly filtered-in entity so that extra entities can be flagged. - entityNodeData->insertSentFilteredEntity(entity->getID()); + entityNodeData->insertSentFilteredEntity(entityID); } OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); @@ -458,6 +466,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream params.stopReason = EncodeBitstreamParams::DIDNT_FIT; break; } + + if (entityPreviouslyMatchedFilter && !entityMatchesFilters) { + entityNodeData->removeSentFilteredEntity(entityID); + } ++_numEntities; } if (queuedItem.shouldForceRemove()) { diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 60cb1e349b..d242b393bf 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -105,8 +105,6 @@ EntityScriptServer::~EntityScriptServer() { static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server"; void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) { - // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them - // about each other. if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) { auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID)); @@ -119,8 +117,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) { - // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them - // about each other. if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) { MessageID messageID; message->readPrimitive(&messageID); @@ -190,15 +186,14 @@ void EntityScriptServer::updateEntityPPS() { } void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) { - // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them - // about each other. + bool canRezAny = senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified(); bool enable = false; message->readPrimitive(&enable); auto senderUUID = senderNode->getUUID(); auto it = _logListeners.find(senderUUID); - if (enable && senderNode->getCanRez()) { + if (enable && canRezAny) { if (it == std::end(_logListeners)) { _logListeners.insert(senderUUID); qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream"; @@ -476,6 +471,7 @@ void EntityScriptServer::clear() { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread _entitiesScriptEngine->unloadAllEntityScripts(); _entitiesScriptEngine->stop(); + _entitiesScriptEngine->waitTillDoneRunning(); } _entityViewer.clear(); @@ -565,8 +561,15 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer mess void EntityScriptServer::aboutToFinish() { shutdownScriptEngine(); + auto entityScriptingInterface = DependencyManager::get(); // our entity tree is going to go away so tell that to the EntityScriptingInterface - DependencyManager::get()->setEntityTree(nullptr); + entityScriptingInterface->setEntityTree(nullptr); + + // Should always be true as they are singletons. + if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { + // The packet sender is about to go away. + entityScriptingInterface->setPacketSender(nullptr); + } DependencyManager::get()->cleanup(); diff --git a/cmake/externals/crashpad/CMakeLists.txt b/cmake/externals/crashpad/CMakeLists.txt index c464dcbc1b..e509e115e4 100644 --- a/cmake/externals/crashpad/CMakeLists.txt +++ b/cmake/externals/crashpad/CMakeLists.txt @@ -6,8 +6,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://backtrace.io/download/crashpad_062317.zip - URL_MD5 65817e564b3628492abfc1dbd2a1e98b + URL http://public.highfidelity.com/dependencies/crashpad_062317.1.zip + URL_MD5 9c84b77f5f23daf939da1371825ed2b1 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/externals/nvtt/CMakeLists.txt b/cmake/externals/nvtt/CMakeLists.txt index 00722bd1e0..3076217c33 100644 --- a/cmake/externals/nvtt/CMakeLists.txt +++ b/cmake/externals/nvtt/CMakeLists.txt @@ -29,8 +29,8 @@ else () ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip - URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb + URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi-83462e4.zip + URL_MD5 602776e08515b54bfa1b8dc455003f0f CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt index 0d66b365a2..f2690e0a7d 100644 --- a/cmake/externals/quazip/CMakeLists.txt +++ b/cmake/externals/quazip/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0046 OLD) include(ExternalProject) -set(QUAZIP_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON) +set(QUAZIP_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON) if (APPLE) else () diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt new file mode 100644 index 0000000000..a08b589ec2 --- /dev/null +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -0,0 +1,16 @@ +include(ExternalProject) + +set(EXTERNAL_NAME serverless-content) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v4.zip + URL_MD5 d4f42f630986c83427ff39e1fe9908c6 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for IDE users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 702636dd01..acafd9b6c7 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -14,16 +14,28 @@ macro(GENERATE_INSTALLERS) set(CPACK_MODULE_PATH ${CPACK_MODULE_PATH} "${HF_CMAKE_DIR}/templates") - set(_DISPLAY_NAME ${BUILD_ORGANIZATION}) + + if (CLIENT_ONLY) + set(_PACKAGE_NAME_EXTRA "-Interface") + set(INSTALLER_TYPE "client_only") + string(REGEX REPLACE "High Fidelity" "High Fidelity Interface" _DISPLAY_NAME ${BUILD_ORGANIZATION}) + elseif (SERVER_ONLY) + set(_PACKAGE_NAME_EXTRA "-Sandbox") + set(INSTALLER_TYPE "server_only") + string(REGEX REPLACE "High Fidelity" "High Fidelity Sandbox" _DISPLAY_NAME ${BUILD_ORGANIZATION}) + else () + set(_DISPLAY_NAME ${BUILD_ORGANIZATION}) + set(INSTALLER_TYPE "full") + endif () set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME}) set(CPACK_PACKAGE_VENDOR "High Fidelity") set(CPACK_PACKAGE_VERSION ${BUILD_VERSION}) - set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta-${BUILD_VERSION}") + set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}") set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME}) if (PR_BUILD) - set(CPACK_NSIS_COMPRESSOR "/SOLID bzip2") + set(CPACK_NSIS_COMPRESSOR "bzip2") endif () set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME}) @@ -46,9 +58,35 @@ macro(GENERATE_INSTALLERS) set(UNINSTALLER_HEADER_IMAGE "") fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE) - # grab the latest VC redist (2017) and add it to the installer, our NSIS template - # will call it during the install - install(CODE "file(DOWNLOAD https://go.microsoft.com/fwlink/?LinkId=746572 \"\${CMAKE_INSTALL_PREFIX}/vcredist_x64.exe\")") + # we use external libraries that still need the 120 (VS2013) redistributables + # so we include them as well until those external libraries are updated + # to use the redistributables that match what we build our applications for + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS + "C:/Windows/System32/msvcp120.dll" + "C:/Windows/System32/msvcr120.dll" + ) + + set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR}) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT ${CLIENT_COMPONENT}) + include(InstallRequiredSystemLibraries) + + if (CLIENT_ONLY OR SERVER_ONLY) + set(CPACK_MONOLITHIC_INSTALL 1) + endif () + + # setup conditional checks for server component selection depending on + # the inclusion of the server component at all + if (CLIENT_ONLY) + set(SERVER_COMPONENT_CONDITIONAL "0 == 1") + set(CLIENT_COMPONENT_CONDITIONAL "1 == 1") + elseif (SERVER_ONLY) + set(SERVER_COMPONENT_CONDITIONAL "1 == 1") + set(CLIENT_COMPONENT_CONDITIONAL "0 == 1") + else () + set(SERVER_COMPONENT_CONDITIONAL "\\\${SectionIsSelected} \\\${${SERVER_COMPONENT}}") + set(CLIENT_COMPONENT_CONDITIONAL "\\\${SectionIsSelected} \\\${${CLIENT_COMPONENT}}") + endif () elseif (APPLE) # produce a drag and drop DMG on OS X set(CPACK_GENERATOR "DragNDrop") @@ -79,8 +117,13 @@ macro(GENERATE_INSTALLERS) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") - cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface") - cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox") + if (BUILD_CLIENT) + cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface") + endif () + + if (BUILD_SERVER) + cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox") + endif () include(CPack) endmacro() diff --git a/cmake/macros/PackageLibrariesForDeployment.cmake b/cmake/macros/PackageLibrariesForDeployment.cmake index d324776572..29f4617a6f 100644 --- a/cmake/macros/PackageLibrariesForDeployment.cmake +++ b/cmake/macros/PackageLibrariesForDeployment.cmake @@ -39,7 +39,9 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT) add_custom_command( TARGET ${TARGET_NAME} POST_BUILD - COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release> \"$\"" + COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND}\ + ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release>\ + --no-compiler-runtime --no-opengl-sw --no-angle -no-system-d3d-compiler \"$\"" ) set(QTAUDIO_PATH "$/audio") diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index e26f81edd9..3ca5cb0e54 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -27,6 +27,11 @@ macro(SET_PACKAGING_PARAMETERS) message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}") message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}") + # setup component categories for installer + set(DDE_COMPONENT dde) + set(CLIENT_COMPONENT client) + set(SERVER_COMPONENT server) + if (RELEASE_TYPE STREQUAL "PRODUCTION") set(DEPLOY_PACKAGE TRUE) set(PRODUCTION_BUILD 1) @@ -68,6 +73,11 @@ macro(SET_PACKAGING_PARAMETERS) add_definitions(-DDEV_BUILD) endif () + if (DEPLOY_PACKAGE) + # for deployed packages always grab the serverless content + set(DOWNLOAD_SERVERLESS_CONTENT ON) + endif () + if (APPLE) set(DMG_SUBFOLDER_NAME "${BUILD_ORGANIZATION}") @@ -149,13 +159,10 @@ macro(SET_PACKAGING_PARAMETERS) set(CLIENT_LAUNCH_NOW_REG_KEY "ClientLaunchAfterInstall") set(SERVER_LAUNCH_NOW_REG_KEY "ServerLaunchAfterInstall") set(CUSTOM_INSTALL_REG_KEY "CustomInstall") + set(CLIENT_ID_REG_KEY "ClientGUID") + set(GA_TRACKING_ID $ENV{GA_TRACKING_ID}) endif () - # setup component categories for installer - set(DDE_COMPONENT dde) - set(CLIENT_COMPONENT client) - set(SERVER_COMPONENT server) - # print out some results for testing this new build feature message(STATUS "The BUILD_GLOBAL_SERVICES variable is: ${BUILD_GLOBAL_SERVICES}") message(STATUS "The USE_STABLE_GLOBAL_SERVICES variable is: ${USE_STABLE_GLOBAL_SERVICES}") diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake index 00a398761b..71f5314d2f 100644 --- a/cmake/macros/SetupQt.cmake +++ b/cmake/macros/SetupQt.cmake @@ -1,10 +1,10 @@ -# +# # Created by Bradley Austin Davis on 2017/09/02 # Copyright 2013-2017 High Fidelity, Inc. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# # Construct a default QT location from a root path, a version and an architecture function(calculate_default_qt_dir _RESULT_NAME) @@ -27,7 +27,7 @@ function(calculate_default_qt_dir _RESULT_NAME) endif() set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT}) - set_from_env(QT_VERSION QT_VERSION "5.9.1") + set_from_env(QT_VERSION QT_VERSION "5.10.1") set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH}) set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE) @@ -60,11 +60,11 @@ macro(setup_qt) #if (NOT EXISTS "${QT_DIR}/include/QtCore/QtGlobal") # message(FATAL_ERROR "Unable to locate Qt includes in ${QT_DIR}") #endif() - + if (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}/Qt5Core/Qt5CoreConfig.cmake") message(FATAL_ERROR "Unable to locate Qt cmake config in ${QT_CMAKE_PREFIX_PATH}") endif() - + message(STATUS "The Qt build in use is: \"${QT_DIR}\"") # Instruct CMake to run moc automatically when needed. @@ -72,7 +72,7 @@ macro(setup_qt) # Instruct CMake to run rcc automatically when needed set(CMAKE_AUTORCC ON) - + if (WIN32) add_paths_to_fixup_libs("${QT_DIR}/bin") endif () diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index b91d78f628..80d86ac030 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -41,6 +41,11 @@ set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@") set(SERVER_LAUNCH_NOW_REG_KEY "@SERVER_LAUNCH_NOW_REG_KEY@") set(CLIENT_LAUNCH_NOW_REG_KEY "@CLIENT_LAUNCH_NOW_REG_KEY@") set(CUSTOM_INSTALL_REG_KEY "@CUSTOM_INSTALL_REG_KEY@") +set(GA_TRACKING_ID "@GA_TRACKING_ID@") +set(CLIENT_ID_REG_KEY "@CLIENT_ID_REG_KEY@") set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@") set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@") set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@") +set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@") +set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@") +set(INSTALLER_TYPE "@INSTALLER_TYPE@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index c1bfebe2c4..28ac320e42 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -319,6 +319,78 @@ Function DownloadFile FunctionEnd !endif + +!include NSISpcre.nsh +!insertmacro REMatches + +Var CampaignName + +!macro GetCampaignName RetVar + Call GetCampaignName + Pop ${RetVar} +!macroend + +Function GetCampaignName + Push $0 ; Stash $0 + + ; Parse filename out of the path + ${RECaptureMatches} $0 "([^\\]*\\)*(.*)\.exe" $EXEPATH 0 + ${If} $0 == 2 + Pop $0 ; Discard Path + Pop $0 ; Recover filename + ; Parse campaign out of the filename + ${RECaptureMatches} $0 "HighFidelity-([^-]*-)Beta-.*" $0 0 + ${If} $0 == 1 + Pop $0 ; Recover campaign name + StrCpy $0 $0 -1 0 ; Remove trailing - and copy to _RetVar + ${Else} + StrCpy $0 "" + ${EndIf} + ${Else} + StrCpy $0 "" + ${EndIf} + + Exch $0 ; Restore $0 and push result +FunctionEnd + +!macro CreateGUID RetVar + System::Call 'ole32::CoCreateGuid(g .s)' + Pop ${RetVar} + ; Strip opening and closing braces + StrCpy ${RetVar} ${RetVar} -1 1 +!macroend + +Var GAClientID + +!macro InitGAClientID + ; Generate a new GUID on every run for now + !insertmacro CreateGUID $GAClientID +!macroend + +!macro GoogleAnalytics Category Action Label Value + ${If} "@GA_TRACKING_ID@" != "" + Push $0 + Push $1 + + StrCpy $0 "https://google-analytics.com/collect?v=1&tid=@GA_TRACKING_ID@" + StrCpy $0 "$0&cid=$GAClientID&t=event&ec=${Category}&ea=${Action}" + + ${If} "${Label}" != "" + StrCpy $0 "$0&el=${Label}" + ${EndIf} + ${If} "${Value}" != "" + StrCpy $0 "$0&ev=${Value}" + ${EndIf} + + GetTempFileName $1 + inetc::get /SILENT $0 $1 /END + Delete $1 + + Pop $1 + Pop $0 + ${EndIf} +!macroend + ;-------------------------------- ; Installation types @@ -342,28 +414,38 @@ SectionEnd ;-------------------------------- ;Pages + !define MUI_CUSTOMFUNCTION_ABORT OnUserAbort + + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomePre !insertmacro MUI_PAGE_WELCOME + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageLicensePre !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" Page custom InstallTypesPage ReadInstallTypes - !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre !insertmacro MUI_PAGE_DIRECTORY ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" - - !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER - !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageComponentsPre @CPACK_NSIS_PAGE_COMPONENTS@ + ; the MUI_PAGE_CUSTOMFUNCTION_PRE shouldn't be defined here + ; which can happen for a component-less (like client only) install + !ifdef MUI_PAGE_CUSTOMFUNCTION_PRE + !undef MUI_PAGE_CUSTOMFUNCTION_PRE + !endif + Page custom PostInstallOptionsPage ReadPostInstallOptions + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM @@ -452,8 +534,40 @@ Var CopyFromProductionCheckbox Var ExpressInstallRadioButton Var CustomInstallRadioButton Var InstallTypeDialog -Var Express Var CustomInstallTemporaryState +Var Express + +!macro MaybeSkipPage + ; Check if Express is set, if so, abort the post install options page + ${If} $Express == "1" + Abort + ${EndIf} +!macroend + +Function OnUserAbort + !insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" "" +FunctionEnd +Function PageWelcomePre + !insertmacro GoogleAnalytics "Installer" "Welcome" "" "" +FunctionEnd +Function PageLicensePre + !insertmacro GoogleAnalytics "Installer" "License" "" "" +FunctionEnd +Function PageDirectoryPre + !insertmacro MaybeSkipPage + !insertmacro GoogleAnalytics "Installer" "Directory" "" "" +FunctionEnd +Function PageStartMenuPre + !insertmacro MaybeSkipPage + !insertmacro GoogleAnalytics "Installer" "StartMenu" "" "" +FunctionEnd +Function PageComponentsPre + !insertmacro MaybeSkipPage + !insertmacro GoogleAnalytics "Installer" "Components" "" "" +FunctionEnd +Function PageInstallFilesPre + !insertmacro GoogleAnalytics "Installer" "Install" "" "" +FunctionEnd !macro SetInstallOption Checkbox OptionName Default ; reads the value for the given install option to the registry @@ -472,6 +586,8 @@ Var CustomInstallTemporaryState !macroend Function InstallTypesPage + !insertmacro GoogleAnalytics "Installer" "Install Types" "" "" + !insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install" nsDialogs::Create 1018 @@ -502,10 +618,11 @@ Function InstallTypesPage ${If} $CustomInstallTemporaryState == ${BST_UNCHECKED} ${NSD_Check} $ExpressInstallRadioButton + Call ChangeExpressLabel + ${Else} + Call ChangeCustomLabel ${EndIf} - Call ChangeExpressLabel - nsDialogs::Show FunctionEnd @@ -523,14 +640,10 @@ Function ChangeCustomLabel Pop $R1 FunctionEnd -Function AbortFunction - ; Check if Express is set, if so, abort the post install options page - StrCmp $Express "1" 0 end - Abort - end: -FunctionEnd - Function PostInstallOptionsPage + !insertmacro MaybeSkipPage + !insertmacro GoogleAnalytics "Installer" "Post Install Options" "" "" + !insertmacro MUI_HEADER_TEXT "Setup Options" "" nsDialogs::Create 1018 @@ -540,15 +653,10 @@ Function PostInstallOptionsPage Abort ${EndIf} - ; Check if Express is set, if so, abort the post install options page - StrCmp $Express "1" 0 end - Abort - end: - StrCpy $CurrentOffset 0 StrCpy $OffsetUnits u - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_HF_SHORTCUT_NAME@" Pop $DesktopClientCheckbox IntOp $CurrentOffset $CurrentOffset + 15 @@ -557,7 +665,7 @@ Function PostInstallOptionsPage !insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" Pop $DesktopServerCheckbox IntOp $CurrentOffset $CurrentOffset + 15 @@ -566,7 +674,7 @@ Function PostInstallOptionsPage !insertmacro SetInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install" Pop $LaunchServerNowCheckbox @@ -580,7 +688,7 @@ Function PostInstallOptionsPage IntOp $CurrentOffset $CurrentOffset + 15 ${EndIf} - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" Pop $LaunchClientNowCheckbox IntOp $CurrentOffset $CurrentOffset + 30 @@ -593,7 +701,7 @@ Function PostInstallOptionsPage ${EndIf} ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" Pop $ServerStartupCheckbox IntOp $CurrentOffset $CurrentOffset + 15 @@ -602,19 +710,17 @@ Function PostInstallOptionsPage !insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} ${EndIf} - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" - Pop $CleanInstallCheckbox - IntOp $CurrentOffset $CurrentOffset + 15 - ${EndIf} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" + Pop $CleanInstallCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 ${If} @PR_BUILD@ == 1 ; a PR build defaults all install options expect LaunchServerNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED} ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED} ${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED} ${EndIf} @@ -673,12 +779,12 @@ Function ReadInstallTypes FunctionEnd Function ReadPostInstallOptions - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if the user asked for a desktop shortcut to High Fidelity ${NSD_GetState} $DesktopClientCheckbox $DesktopClientState ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ; check if the user asked for a desktop shortcut to Sandbox ${NSD_GetState} $DesktopServerCheckbox $DesktopServerState @@ -691,24 +797,22 @@ Function ReadPostInstallOptions ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ; check if we need to launch the server post-install ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState ${EndIf} - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if we need to launch the client post-install ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState ${EndIf} - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ; check if the user asked for a clean install - ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState - ${EndIf} + ; check if the user asked for a clean install + ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState FunctionEnd Function HandlePostInstallOptions - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if the user asked for a desktop shortcut to High Fidelity ${If} $DesktopClientState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" @@ -719,7 +823,7 @@ Function HandlePostInstallOptions ${EndIf} - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ ; check if the user asked for a desktop shortcut to Sandbox ${If} $DesktopServerState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" @@ -748,13 +852,23 @@ Function HandlePostInstallOptions ${EndIf} ${EndIf} - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ; check if the user asked for a clean install - ${If} $CleanInstallState == ${BST_CHECKED} - SetShellVarContext current - RMDir /r "$APPDATA\@BUILD_ORGANIZATION@" - RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@" + ; check if the user asked for a clean install + ${If} $CleanInstallState == ${BST_CHECKED} + SetShellVarContext current + + ${If} @SERVER_COMPONENT_CONDITIONAL@ + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\Server Console" + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\assignment-client" + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\domain-server" + Delete "$APPDATA\@BUILD_ORGANIZATION@\domain-server.json" ${EndIf} + + ${If} @CLIENT_COMPONENT_CONDITIONAL@ + Delete "$APPDATA\@BUILD_ORGANIZATION@\Interface\AccountInfo.bin" + Delete "$APPDATA\@BUILD_ORGANIZATION@\Interface.json" + ${EndIf} + + RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@" ${EndIf} ${If} @PR_BUILD@ == 1 @@ -785,7 +899,8 @@ Function HandlePostInstallOptions ${EndIf} ${EndIf} - ${If} $LaunchServerNowState == ${BST_CHECKED} + ${If} @SERVER_COMPONENT_CONDITIONAL@ + ${AndIf} $LaunchServerNowState == ${BST_CHECKED} !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES ; both launches use the explorer trick in case the user has elevated permissions for the installer @@ -799,7 +914,7 @@ Function HandlePostInstallOptions Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' ${EndIf} - ${Else} + ${ElseIf} @CLIENT_COMPONENT_CONDITIONAL@ !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO ; launch uses the explorer trick in case the user has elevated permissions for the installer @@ -837,9 +952,6 @@ Section "-Core installation" Delete "$INSTDIR\ui_resources_200_percent.pak" Delete "$INSTDIR\vccorlib120.dll" Delete "$INSTDIR\version" - Delete "$INSTDIR\msvcr140.dll" - Delete "$INSTDIR\msvcp140.dll" - Delete "$INSTDIR\vcruntime140.dll" Delete "$INSTDIR\xinput1_3.dll" ; Delete old desktop shortcuts before they were renamed during Sandbox rename @@ -858,11 +970,8 @@ Section "-Core installation" ; Rename the incorrectly cased Raleway font Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml" - ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart" - ; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console) RMDir /r "$INSTDIR\Interface" - Delete "$INSTDIR\vcredist_x64.exe" ;Use the entire tree produced by the INSTALL target. Keep the ;list of directories here in sync with the RMDir commands below. @@ -873,6 +982,13 @@ Section "-Core installation" ;Store installation folder WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + ;Write some information about this install to the installation folder + FileOpen $0 "$INSTDIR\installer.ini" w + FileWrite $0 "type=@INSTALLER_TYPE@$\r$\n" + FileWrite $0 "campaign=$CampaignName$\r$\n" + FileWrite $0 "exepath=$EXEPATH$\r$\n" + FileClose $0 + ;Package the signed uninstaller produced by the inner loop !ifndef INNER ; this packages the signed uninstaller @@ -931,7 +1047,7 @@ Section "-Core installation" @CPACK_NSIS_CREATE_ICONS_EXTRA@ ; Conditional handling for Interface specific options - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@INTERFACE_SHORTCUT_NAME@.lnk" \ "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" @@ -946,7 +1062,7 @@ Section "-Core installation" ${EndIf} ; Conditional handling for server console shortcut - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${If} @SERVER_COMPONENT_CONDITIONAL@ CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" ${EndIf} @@ -965,6 +1081,7 @@ Section "-Core installation" ; Handle whichever post install options were set Call HandlePostInstallOptions + !insertmacro GoogleAnalytics "Installer" "Done" "" "" SectionEnd !include nsProcess.nsh @@ -979,7 +1096,7 @@ SectionEnd ${If} $R0 == 0 ; the process is running, ask the user to close it - + ${If} "${displayName}" == "@CONSOLE_DISPLAY_NAME@" MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \ "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the system tray and click Retry to continue." \ @@ -992,6 +1109,8 @@ SectionEnd /SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0 ${EndIf} + !insertmacro GoogleAnalytics "Installer" "Abort" "${displayName} Running" "" + ; If the user decided to cancel, stop the current installer/uninstaller Abort @@ -1087,8 +1206,8 @@ Function .onSelChange !insertmacro SectionList MaybeSelectionChanged ; if neither component is selected, disable the install button - ${IfNot} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${AndIfNot} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${IfNot} @CLIENT_COMPONENT_CONDITIONAL@ + ${AndIfNot} @SERVER_COMPONENT_CONDITIONAL@ GetDlgItem $0 $HWNDPARENT 1 EnableWindow $0 0 ${Else} @@ -1115,6 +1234,9 @@ Section "Uninstall" @CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + ;Remove the installer information file + Delete "$INSTDIR\installer.ini" + ;Remove files we installed. ;Keep the list of directories here in sync with the File commands above. @CPACK_NSIS_DELETE_FILES@ @@ -1219,6 +1341,11 @@ Function .onInit Quit !endif + !insertmacro InitGAClientID + !insertmacro GetCampaignName $CampaignName + + !insertmacro GoogleAnalytics "Installer" "Start" "$CampaignName" "" + ; make sure none of the installed applications are still running !insertmacro CheckForRunningApplications "installed" "Installer" ${nsProcess::Unload} diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 3fec5a9000..853e87ae23 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -132,6 +132,41 @@ $(document).ready(function(){ var ACTIVE_BACKUP_ROW_CLASS = 'active-backup'; var CORRUPTED_ROW_CLASS = 'danger'; + $('body').on('click', '.' + BACKUP_DOWNLOAD_LINK_CLASS, function(ev) { + ev.preventDefault(); + var backupID = $(this).data('backup-id'); + + showSpinnerAlert("Preparing backup..."); + function checkBackupStatus() { + $.ajax({ + url: "/api/backups/" + backupID, + dataType: 'json', + success: function(data) { + if (data.complete) { + if (data.error == '') { + location.href = "/api/backups/download/" + backupID; + swal.close(); + } else { + showErrorMessage( + "Error", + "There was an error preparing your backup. Please refresh the page and try again." + ); + } + } else { + setTimeout(checkBackupStatus, 500); + } + }, + error: function() { + showErrorMessage( + "Error", + "There was an error preparing your backup." + ); + }, + }); + } + checkBackupStatus(); + }); + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ @@ -164,7 +199,7 @@ $(document).ready(function(){ + ""; } diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 84bba4de56..1647da045f 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -2,7 +2,7 @@ if (typeof Settings === "undefined") { Settings = {}; } -Object.assign(Settings, { +$.extend(Settings, { DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 85040d8c35..518ed73f9e 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -55,15 +55,20 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire const QVariantList& backupRules, std::chrono::milliseconds persistInterval, bool debugTimestampNow) : + _consolidatedBackupDirectory(PathUtils::generateTemporaryDir()), _backupDirectory(backupDirectory), _persistInterval(persistInterval), _lastCheck(p_high_resolution_clock::now()) { - setObjectName("DomainContentBackupManager"); // Make sure the backup directory exists. QDir(_backupDirectory).mkpath("."); parseBackupRules(backupRules); + + constexpr int CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS = 30 * 1000; + _consolidatedBackupCleanupTimer.setInterval(CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS); + connect(&_consolidatedBackupCleanupTimer, &QTimer::timeout, this, &DomainContentBackupManager::removeOldConsolidatedBackups); + _consolidatedBackupCleanupTimer.start(); } void DomainContentBackupManager::parseBackupRules(const QVariantList& backupRules) { @@ -498,23 +503,87 @@ void DomainContentBackupManager::backup() { } } -void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, QString fileName) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "consolidateBackup", Q_ARG(MiniPromise::Promise, promise), - Q_ARG(QString, fileName)); - return; +void DomainContentBackupManager::removeOldConsolidatedBackups() { + constexpr std::chrono::minutes MAX_TIME_TO_KEEP_CONSOLIDATED_BACKUP { 30 }; + auto now = std::chrono::system_clock::now(); + auto it = _consolidatedBackups.begin(); + while (it != _consolidatedBackups.end()) { + auto& backup = it->second; + auto diff = now - backup.createdAt; + if (diff > MAX_TIME_TO_KEEP_CONSOLIDATED_BACKUP) { + QFile oldBackup(backup.absoluteFilePath); + if (!oldBackup.exists() || oldBackup.remove()) { + qDebug() << "Removed old consolidated backup: " << backup.absoluteFilePath; + it = _consolidatedBackups.erase(it); + } else { + qDebug() << "Failed to remove old consolidated backup: " << backup.absoluteFilePath; + it++; + } + } else { + it++; + } + } +} + +ConsolidatedBackupInfo DomainContentBackupManager::consolidateBackup(QString fileName) { + { + std::lock_guard lock { _consolidatedBackupsMutex }; + auto it = _consolidatedBackups.find(fileName); + + if (it != _consolidatedBackups.end()) { + return it->second; + } + } + QMetaObject::invokeMethod(this, "consolidateBackupInternal", Q_ARG(QString, fileName)); + return { + ConsolidatedBackupInfo::CONSOLIDATING, + "", + "", + std::chrono::system_clock::now() + }; +} + +void DomainContentBackupManager::consolidateBackupInternal(QString fileName) { + auto markFailure = [this, &fileName](QString error) { + qWarning() << "Failed to consolidate backup:" << fileName << error; + { + std::lock_guard lock { _consolidatedBackupsMutex }; + auto& consolidatedBackup = _consolidatedBackups[fileName]; + consolidatedBackup.state = ConsolidatedBackupInfo::COMPLETE_WITH_ERROR; + consolidatedBackup.error = error; + } + }; + + { + std::lock_guard lock { _consolidatedBackupsMutex }; + + auto it = _consolidatedBackups.find(fileName); + if (it != _consolidatedBackups.end()) { + return; + } + + _consolidatedBackups[fileName] = { + ConsolidatedBackupInfo::CONSOLIDATING, + "", + "", + std::chrono::system_clock::now() + }; } QDir backupDir { _backupDirectory }; if (!backupDir.exists()) { - qCritical() << "Backup directory does not exist, bailing consolidation of backup"; - promise->resolve({ { "success", false } }); + markFailure("Backup directory does not exist, bailing consolidation of backup"); return; } auto filePath = backupDir.absoluteFilePath(fileName); + + if (!QFile::exists(filePath)) { + markFailure("Backup does not exist"); + return; + } - auto copyFilePath = QDir::tempPath() + "/" + fileName; + auto copyFilePath = _consolidatedBackupDirectory + "/" + fileName; { QFile copyFile(copyFilePath); @@ -523,8 +592,7 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, } auto copySuccess = QFile::copy(filePath, copyFilePath); if (!copySuccess) { - qCritical() << "Failed to create copy of backup."; - promise->resolve({ { "success", false } }); + markFailure("Failed to create copy of backup."); return; } @@ -532,7 +600,7 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, if (!zip.open(QuaZip::mdAdd)) { qCritical() << "Could not open backup archive:" << filePath; qCritical() << " ERROR:" << zip.getZipError(); - promise->resolve({ { "success", false } }); + markFailure("Could not open backup archive"); return; } @@ -544,14 +612,17 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, if (zip.getZipError() != UNZ_OK) { qCritical() << "Failed to consolidate backup: " << zip.getZipError(); - promise->resolve({ { "success", false } }); + markFailure("Failed to consolidate backup"); return; } - promise->resolve({ - { "success", true }, - { "backupFilePath", copyFilePath } - }); + { + std::lock_guard lock { _consolidatedBackupsMutex }; + auto& consolidatedBackup = _consolidatedBackups[fileName]; + consolidatedBackup.state = ConsolidatedBackupInfo::COMPLETE_WITH_SUCCESS; + consolidatedBackup.absoluteFilePath = copyFilePath; + } + } void DomainContentBackupManager::createManualBackup(MiniPromise::Promise promise, const QString& name) { diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index fbc8084ac6..2b07afe0b3 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -15,9 +15,15 @@ #ifndef hifi_DomainContentBackupManager_h #define hifi_DomainContentBackupManager_h +#include + #include #include #include +#include + +#include +#include #include @@ -38,6 +44,18 @@ struct BackupItemInfo { bool isManualBackup; }; +struct ConsolidatedBackupInfo { + enum State { + CONSOLIDATING, + COMPLETE_WITH_ERROR, + COMPLETE_WITH_SUCCESS + }; + State state; + QString error; + QString absoluteFilePath; + std::chrono::system_clock::time_point createdAt; +}; + class DomainContentBackupManager : public GenericThread { Q_OBJECT public: @@ -61,6 +79,7 @@ public: void addBackupHandler(BackupHandlerPointer handler); void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist void replaceData(QByteArray data); + ConsolidatedBackupInfo consolidateBackup(QString fileName); public slots: void getAllBackupsAndStatus(MiniPromise::Promise promise); @@ -68,7 +87,6 @@ public slots: void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); - void consolidateBackup(MiniPromise::Promise promise, QString fileName); signals: void loadCompleted(); @@ -91,11 +109,21 @@ protected: bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip); +private slots: + void removeOldConsolidatedBackups(); + void consolidateBackupInternal(QString fileName); + private: + QTimer _consolidatedBackupCleanupTimer; + + const QString _consolidatedBackupDirectory; const QString _backupDirectory; std::vector _backupHandlers; std::chrono::milliseconds _persistInterval { 0 }; + std::mutex _consolidatedBackupsMutex; + std::unordered_map _consolidatedBackups; + std::atomic _isRecovering { false }; QString _recoveryFilename { }; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d2ef1a4156..dbf2907cc0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -163,6 +164,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : _iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME), _iceServerPort(ICE_SERVER_DEFAULT_PORT) { + PathUtils::removeTemporaryApplicationDirs(); + parseCommandLine(); DependencyManager::set(); @@ -405,7 +408,7 @@ void DomainServer::restart() { exit(DomainServer::EXIT_CODE_REBOOT); } -const QUuid& DomainServer::getID() { +QUuid DomainServer::getID() { return DependencyManager::get()->getSessionUUID(); } @@ -727,7 +730,7 @@ void DomainServer::setupNodeListAndAssignments() { packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage"); packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest"); - packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURLRequest"); + packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, this, "handleDomainContentReplacementFromURLRequest"); // set a custom packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); @@ -736,7 +739,6 @@ void DomainServer::setupNodeListAndAssignments() { auto assetClient = DependencyManager::set(); assetClient->moveToThread(&_assetClientThread); _assetClientThread.start(); - // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); } @@ -1040,41 +1042,7 @@ void DomainServer::processListRequestPacket(QSharedPointer mess bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { auto nodeAData = static_cast(nodeA->getLinkedData()); - auto nodeBData = static_cast(nodeB->getLinkedData()); - - // if we have no linked data for node A then B can't possibly be in the interest set - if (!nodeAData) { - return false; - } - - // first check if the general interest set A contains the type for B - if (nodeAData->getNodeInterestSet().contains(nodeB->getType())) { - // given that there is a match in the general interest set, do any special checks - - // (1/19/17) Agents only need to connect to Entity Script Servers to perform administrative tasks - // related to entity server scripts. Only agents with rez permissions should be doing that, so - // if the agent does not have those permissions, we do not want them and the server to incur the - // overhead of connecting to one another. Additionally we exclude agents that do not care about the - // Entity Script Server and won't attempt to connect to it. - - bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent - && nodeB->getType() == NodeType::EntityScriptServer - && !nodeA->getCanRez() && !nodeA->getCanRezTmp() - && !nodeA->getCanRezCertified() && !nodeA->getCanRezTmpCertified(); - - if (isAgentWithoutRights) { - return false; - } - - bool isScriptServerForIneffectiveAgent = - (nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent) - && ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer)) - || (!nodeB->getCanRez() && !nodeB->getCanRezTmp() && !nodeB->getCanRezCertified() && !nodeB->getCanRezTmpCertified())); - - return !isScriptServerForIneffectiveAgent; - } else { - return false; - } + return nodeAData && nodeAData->getNodeInterestSet().contains(nodeB->getType()); } unsigned int DomainServer::countConnectedUsers() { @@ -1933,13 +1901,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString URI_API_DOMAINS_ID = "/api/domains/"; const QString URI_API_BACKUPS = "/api/backups"; const QString URI_API_BACKUPS_ID = "/api/backups/"; + const QString URI_API_BACKUPS_DOWNLOAD_ID = "/api/backups/download/"; const QString URI_API_BACKUPS_RECOVER = "/api/backups/recover/"; const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; + QPointer connectionPtr { connection }; + auto nodeList = DependencyManager::get(); - auto getSetting = [this](QString keyPath, QVariant value) -> bool { + auto getSetting = [this](QString keyPath, QVariant& value) -> bool { value = _settingsManager.valueForKeyPath(keyPath); if (!value.isValid()) { @@ -2120,33 +2091,51 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == URI_API_BACKUPS) { auto deferred = makePromise("getAllBackupsAndStatus"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) { + if (!connectionPtr) { + return; + } + QJsonDocument docJSON(QJsonObject::fromVariantMap(result)); - connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + connectionPtr->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); _contentManager->getAllBackupsAndStatus(deferred); return true; + } else if (url.path().startsWith(URI_API_BACKUPS_DOWNLOAD_ID)) { + auto id = url.path().mid(QString(URI_API_BACKUPS_DOWNLOAD_ID).length()); + auto info = _contentManager->consolidateBackup(id); + + if (info.state == ConsolidatedBackupInfo::COMPLETE_WITH_SUCCESS) { + auto file { std::unique_ptr(new QFile(info.absoluteFilePath)) }; + if (file->open(QIODevice::ReadOnly)) { + constexpr const char* CONTENT_TYPE_ZIP = "application/zip"; + auto downloadedFilename = id; + downloadedFilename.replace(QRegularExpression(".zip$"), ".content.zip"); + auto contentDisposition = "attachment; filename=\"" + downloadedFilename + "\""; + connectionPtr->respond(HTTPConnection::StatusCode200, std::move(file), CONTENT_TYPE_ZIP, { + { "Content-Disposition", contentDisposition.toUtf8() } + }); + } else { + qCritical(domain_server) << "Unable to load consolidated backup at:" << info.absoluteFilePath; + connectionPtr->respond(HTTPConnection::StatusCode500, "Error opening backup"); + } + } else if (info.state == ConsolidatedBackupInfo::COMPLETE_WITH_ERROR) { + connectionPtr->respond(HTTPConnection::StatusCode500, ("Error creating backup: " + info.error).toUtf8()); + } else { + connectionPtr->respond(HTTPConnection::StatusCode400, "Backup unavailable"); + } + return true; } else if (url.path().startsWith(URI_API_BACKUPS_ID)) { auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); - auto deferred = makePromise("consolidateBackup"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { - QJsonObject rootJSON; - auto success = result["success"].toBool(); - if (success) { - auto path = result["backupFilePath"].toString(); - auto file { std::unique_ptr(new QFile(path)) }; - if (file->open(QIODevice::ReadOnly)) { - connection->respond(HTTPConnection::StatusCode200, std::move(file)); - } else { - qCritical(domain_server) << "Unable to load consolidated backup at:" << path << result; - connection->respond(HTTPConnection::StatusCode500, "Error opening backup"); - } - } else { - connection->respond(HTTPConnection::StatusCode400); - } - }); - _contentManager->consolidateBackup(deferred, id); + auto info = _contentManager->consolidateBackup(id); + + QJsonObject rootJSON { + { "complete", info.state == ConsolidatedBackupInfo::COMPLETE_WITH_SUCCESS }, + { "error", info.error } + }; + QJsonDocument docJSON { rootJSON }; + connectionPtr->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); return true; } else if (url.path() == URI_RESTART) { @@ -2199,7 +2188,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES"; const QString ASSIGNMENT_POOL_HEADER = "ASSIGNMENT-POOL"; - QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); + QByteArray assignmentInstancesValue = connection->requestHeader(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); int numInstances = 1; @@ -2211,7 +2200,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } QString assignmentPool = emptyPool; - QByteArray assignmentPoolValue = connection->requestHeaders().value(ASSIGNMENT_POOL_HEADER.toLocal8Bit()); + QByteArray assignmentPoolValue = connection->requestHeader(ASSIGNMENT_POOL_HEADER.toLocal8Bit()); if (!assignmentPoolValue.isEmpty()) { // specific pool requested, set that on the created assignment @@ -2264,12 +2253,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } else if (uploadedFilename.endsWith(".zip", Qt::CaseInsensitive)) { auto deferred = makePromise("recoverFromUploadedBackup"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) { + if (!connectionPtr) { + return; + } + QJsonObject rootJSON; auto success = result["success"].toBool(); rootJSON["success"] = success; QJsonDocument docJSON(rootJSON); - connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); @@ -2297,12 +2290,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } auto deferred = makePromise("createManualBackup"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) { + if (!connectionPtr) { + return; + } + QJsonObject rootJSON; auto success = result["success"].toBool(); rootJSON["success"] = success; QJsonDocument docJSON(rootJSON); - connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); _contentManager->createManualBackup(deferred, it.value()); @@ -2322,12 +2319,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) { auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length()); auto deferred = makePromise("recoverFromBackup"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) { + if (!connectionPtr) { + return; + } + QJsonObject rootJSON; auto success = result["success"].toBool(); rootJSON["success"] = success; QJsonDocument docJSON(rootJSON); - connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); _contentManager->recoverFromBackup(deferred, id); @@ -2423,12 +2424,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url if (url.path().startsWith(URI_API_BACKUPS_ID)) { auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); auto deferred = makePromise("deleteBackup"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) { + if (!connectionPtr) { + return; + } + QJsonObject rootJSON; auto success = result["success"].toBool(); rootJSON["success"] = success; QJsonDocument docJSON(rootJSON); - connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); _contentManager->deleteBackup(deferred, id); @@ -2593,7 +2598,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl if (!_oauthProviderURL.isEmpty() && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) { - QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY); + QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY); const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING); @@ -2638,7 +2643,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With"; static const QString XML_REQUESTED_WITH = "XMLHttpRequest"; - if (connection->requestHeaders().value(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) { + if (connection->requestHeader(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) { // unauthorized XHR requests get a 401 and not a 302, since there isn't an XHR // path to OAuth authorize connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY); @@ -2669,7 +2674,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization"; // check if a username and password have been provided with the request - QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY); + QString basicAuthString = connection->requestHeader(BASIC_AUTH_HEADER_KEY); if (!basicAuthString.isEmpty()) { QStringList splitAuthString = basicAuthString.split(' '); @@ -3403,13 +3408,10 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { } } -void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer message) { +void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer message) { qInfo() << "Received request to replace content from a url"; auto node = DependencyManager::get()->findNodeWithAddr(message->getSenderSockAddr()); - if (node) { - qDebug() << "Found node: " << node->getCanReplaceContent(); - } - if (node->getCanReplaceContent()) { + if (node && node->getCanReplaceContent()) { // Convert message data into our URL QString url(message->getMessage()); QUrl modelsURL = QUrl(url, QUrl::StrictMode); @@ -3422,7 +3424,12 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointererror(); if (networkError == QNetworkReply::NoError) { - handleOctreeFileReplacement(reply->readAll()); + if (modelsURL.fileName().endsWith(".json.gz")) { + handleOctreeFileReplacement(reply->readAll()); + } else if (modelsURL.fileName().endsWith(".zip")) { + auto deferred = makePromise("recoverFromUploadedBackup"); + _contentManager->recoverFromUploadedBackup(deferred, reply->readAll()); + } } else { qDebug() << "Error downloading JSON from specified file: " << modelsURL; } @@ -3430,9 +3437,6 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer message) { auto node = DependencyManager::get()->nodeWithUUID(message->getSourceID()); if (node->getCanReplaceContent()) { diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index afe2a1cc7c..b118008d3d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -91,7 +91,7 @@ private slots: void processICEServerHeartbeatDenialPacket(QSharedPointer message); void processICEServerHeartbeatACK(QSharedPointer message); - void handleOctreeFileReplacementFromURLRequest(QSharedPointer message); + void handleDomainContentReplacementFromURLRequest(QSharedPointer message); void handleOctreeFileReplacementRequest(QSharedPointer message); void handleOctreeFileReplacement(QByteArray octreeFile); @@ -135,7 +135,7 @@ signals: void userDisconnected(); private: - const QUuid& getID(); + QUuid getID(); void parseCommandLine(); QString getContentBackupDir(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 3ed5445493..fe00d86c3a 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -23,17 +23,26 @@ set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc) generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *) -add_custom_command( +if (ANDROID) + # on Android, don't compress the rcc binary + add_custom_command( + OUTPUT ${RESOURCES_RCC} + DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} + COMMAND "${QT_DIR}/bin/rcc" + ARGS ${RESOURCES_QRC} -no-compress -binary -o ${RESOURCES_RCC} + ) +else () + add_custom_command( OUTPUT ${RESOURCES_RCC} DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} COMMAND "${QT_DIR}/bin/rcc" ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC} -) + ) +endif() list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC}) add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) - # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -181,7 +190,11 @@ add_dependencies(${TARGET_NAME} resources) if (WIN32) # These are external plugins, but we need to do the 'add dependency' here so that their # binary directories get added to the fixup path - add_dependency_external_projects(sixense) + + if (USE_SIXENSE) + add_dependency_external_projects(sixense) + endif () + add_dependency_external_projects(sdl2) add_dependency_external_projects(OpenVR) add_dependency_external_projects(neuron) @@ -189,12 +202,6 @@ if (WIN32) add_dependency_external_projects(steamworks) endif() -# include OPENSSL -include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") - -# append OpenSSL to our list of libraries to link -target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) - # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification @@ -218,6 +225,9 @@ link_hifi_libraries( # include the binary directory of render-utils for shader includes target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") +# include OpenSSL +target_openssl() + target_bullet() target_opengl() add_crashpad() @@ -303,35 +313,41 @@ if (APPLE) ) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") - + set(RESOURCES_DEV_DIR "$/../Resources") # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" - "$/../Resources/scripts" + "${RESOURCES_DEV_DIR}/scripts" ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() else() + set(INTERFACE_EXEC_DIR "$") + set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") + # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${RESOURCES_RCC}" - "$" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${RESOURCES_RCC}" + "${INTERFACE_EXEC_DIR}" # FIXME, the edit script code loads HTML from the scripts folder # which in turn relies on CSS that refers to the fonts. In theory - # we should be able to modify the CSS to reference the QRC path to - # the ttf files, but doing so generates a CORS policy violation, + # we should be able to modify the CSS to reference the QRC path to + # the ttf files, but doing so generates a CORS policy violation, # so we have to retain a copy of the fonts outside of the resources binary COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/resources/fonts" - "$/resources/fonts" + "${PROJECT_SOURCE_DIR}/resources/fonts" + "${RESOURCES_DEV_DIR}/fonts" COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_SOURCE_DIR}/scripts" - "$/scripts" + "${CMAKE_SOURCE_DIR}/scripts" + "${INTERFACE_EXEC_DIR}/scripts" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" + "${RESOURCES_DEV_DIR}/serverless/tutorial.json" ) # link target to external libraries @@ -357,7 +373,6 @@ else() endif() if (SCRIPTS_INSTALL_DIR) - # setup install of scripts beside interface executable install( DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/" @@ -366,6 +381,19 @@ if (SCRIPTS_INSTALL_DIR) ) endif() +if (DOWNLOAD_SERVERLESS_CONTENT) + add_dependency_external_projects(serverless-content) + + ExternalProject_Get_Property(serverless-content SOURCE_DIR) + + # for dev builds, copy the serverless content to the resources folder + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${SOURCE_DIR}" + "${RESOURCES_DEV_DIR}/serverless" + ) +endif () + if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") @@ -379,3 +407,6 @@ endif() add_dependency_external_projects(GifCreator) find_package(GifCreator REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS}) + +# tell CMake to exclude ui_console.h for policy CMP0071 +set_property(SOURCE ui_console.h PROPERTY SKIP_AUTOMOC ON) diff --git a/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml b/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml index e6dc03fa55..bf72869752 100644 --- a/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml @@ -1,6 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.0 import "../../qml/dialogs" diff --git a/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml b/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml index cb6552b075..55f7d27534 100644 --- a/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml +++ b/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml @@ -1,6 +1,4 @@ import QtQuick 2.4 -import QtQuick.Dialogs 1.1 -import QtQuick.Controls 1.4 import "../../qml/dialogs" diff --git a/interface/resources/QtWebEngine/UIDelegates/Menu.qml b/interface/resources/QtWebEngine/UIDelegates/Menu.qml index 1bbbbd6cbe..46c00e758e 100644 --- a/interface/resources/QtWebEngine/UIDelegates/Menu.qml +++ b/interface/resources/QtWebEngine/UIDelegates/Menu.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 as Controls import "../../qml/controls-uit" import "../../qml/styles-uit" diff --git a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml index 1890fcb81d..6014b6834b 100644 --- a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml +++ b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml @@ -1,6 +1,4 @@ - import QtQuick 2.5 -import QtQuick.Controls 1.4 as Controls import "../../qml/controls-uit" import "../../qml/styles-uit" diff --git a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml index 01d3262bc0..e4ab3037ef 100644 --- a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml @@ -1,6 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.0 import "../../qml/controls-uit" import "../../qml/styles-uit" diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index b3f16a115e..660bc281e3 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -1,15 +1,10 @@ { "name": "Keyboard/Mouse to Actions", "channels": [ - - { "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, - { "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.A", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.D", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" }, - { "from": "Keyboard.E", "when": "Keyboard.Shift", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.05 } ] }, - { "from": "Keyboard.C", "when": "Keyboard.Shift", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.05 } ] }, - { "from": "Keyboard.S", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, - { "from": "Keyboard.W", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" }, + { "from": "Keyboard.E", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.Q", "to": "Actions.LATERAL_LEFT" }, { "comment" : "Mouse turn need to be small continuous increments", @@ -44,9 +39,24 @@ ] }, + { "from": { "makeAxis" : [ - ["Keyboard.A", "Keyboard.Left" ], - ["Keyboard.D", "Keyboard.Right"] + ["Keyboard.Left" ], + ["Keyboard.Right"] + ] + }, + "when": ["Application.InHMD", "Application.SnapTurn", "!Keyboard.Shift"], + "to": "Actions.StepYaw", + "filters": + [ + { "type": "pulse", "interval": 0.5, "resetOnZero": true }, + { "type": "scale", "scale": 22.5 } + ] + }, + + { "from": { "makeAxis" : [ + ["Keyboard.A"], + ["Keyboard.D"] ] }, "when": [ "Application.InHMD", "Application.SnapTurn" ], @@ -59,26 +69,39 @@ }, { "from": { "makeAxis" : [ - ["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"], - ["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"] + ["Keyboard.Left"], + ["Keyboard.Right"] + ] + }, + "when": ["Application.CameraFirstPerson", "!Keyboard.Shift"], + "to": "Actions.Yaw" + }, + + { "from": { "makeAxis" : [ + ["Keyboard.Left"], + ["Keyboard.Right"] + ] + }, + "when": ["Application.CameraThirdPerson", "!Keyboard.Shift"], + "to": "Actions.Yaw" + }, + + { "from": { "makeAxis" : [ + ["Keyboard.A", "Keyboard.TouchpadLeft"], + ["Keyboard.D", "Keyboard.TouchpadRight"] ] }, "when": "Application.CameraFirstPerson", "to": "Actions.Yaw" }, { "from": { "makeAxis" : [ - ["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"], - ["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"] + ["Keyboard.A", "Keyboard.TouchpadLeft"], + ["Keyboard.D", "Keyboard.TouchpadRight"] ] }, "when": "Application.CameraThirdPerson", "to": "Actions.Yaw" }, - { "from": { "makeAxis" : [ ["Keyboard.A"], ["Keyboard.D"] ] }, - "when": "Application.CameraFSM", - "to": "Actions.Yaw" - }, - { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, "when": "Keyboard.RightMouseButton", "to": "Actions.Yaw", @@ -90,14 +113,10 @@ { "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" }, + { "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" }, { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, - { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, - { "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, - { "from": "Keyboard.Right", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, - { "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, - { "from": "Keyboard.Up", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" }, { "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" }, @@ -128,7 +147,7 @@ { "from": "Keyboard.MouseWheelLeft", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.02 } ]}, { "from": "Keyboard.MouseWheelRight", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.02 } ]}, - { "from": "Keyboard.Space", "to": "Actions.SHIFT" }, + { "from": "Keyboard.Space", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" }, { "from": "Keyboard.T", "to": "Actions.ACTION2" }, { "from": "Keyboard.Tab", "to": "Actions.ContextMenu" } diff --git a/interface/resources/controllers/touchscreenvirtualpad.json b/interface/resources/controllers/touchscreenvirtualpad.json index 8c21044c3b..bae1172152 100644 --- a/interface/resources/controllers/touchscreenvirtualpad.json +++ b/interface/resources/controllers/touchscreenvirtualpad.json @@ -5,8 +5,25 @@ { "from": "TouchscreenVirtualPad.LX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, - { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.Yaw" }, + { "from": "TouchscreenVirtualPad.JUMP_BUTTON_PRESS", "when": "!Application.CameraIndependent", "to": "Actions.VERTICAL_UP" }, + + { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", + "filters": [ + { "type": "deadZone", "min": 0.000 }, + { "type": "scale", "scale": 0.06 }, + "invert" + ], + "to": "Actions.Yaw" + }, + + { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", + "filters": [ + { "type": "deadZone", "min": 0.000 }, + { "type": "scale", "scale": 0.06 }, + "invert" + ], + "to": "Actions.Pitch" + } - { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "to": "Actions.Pitch" } ] } diff --git a/interface/resources/html/img/tablet-help-keyboard.jpg b/interface/resources/html/img/tablet-help-keyboard.jpg index a62fbe9450..d0f84c17c7 100644 Binary files a/interface/resources/html/img/tablet-help-keyboard.jpg and b/interface/resources/html/img/tablet-help-keyboard.jpg differ diff --git a/interface/resources/icons/+android/avatar-a.svg b/interface/resources/icons/+android/avatar-a.svg new file mode 100755 index 0000000000..165b39943e --- /dev/null +++ b/interface/resources/icons/+android/avatar-a.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/+android/avatar-i.svg b/interface/resources/icons/+android/avatar-i.svg new file mode 100755 index 0000000000..c1557487ea --- /dev/null +++ b/interface/resources/icons/+android/avatar-i.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/+android/bubble-a.svg b/interface/resources/icons/+android/bubble-a.svg new file mode 100644 index 0000000000..fccc9c07ff --- /dev/null +++ b/interface/resources/icons/+android/bubble-a.svg @@ -0,0 +1,1022 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/resources/icons/+android/bubble-i.svg b/interface/resources/icons/+android/bubble-i.svg new file mode 100644 index 0000000000..80c97d2704 --- /dev/null +++ b/interface/resources/icons/+android/bubble-i.svg @@ -0,0 +1,1022 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/resources/icons/+android/button-a.svg b/interface/resources/icons/+android/button-a.svg new file mode 100644 index 0000000000..d469154775 --- /dev/null +++ b/interface/resources/icons/+android/button-a.svg @@ -0,0 +1,949 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/button.svg b/interface/resources/icons/+android/button.svg new file mode 100644 index 0000000000..8c19332064 --- /dev/null +++ b/interface/resources/icons/+android/button.svg @@ -0,0 +1,949 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/login-a.svg b/interface/resources/icons/+android/login-a.svg new file mode 100755 index 0000000000..8a7f097ed7 --- /dev/null +++ b/interface/resources/icons/+android/login-a.svg @@ -0,0 +1,990 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/login-i.svg b/interface/resources/icons/+android/login-i.svg new file mode 100755 index 0000000000..6f011e1d13 --- /dev/null +++ b/interface/resources/icons/+android/login-i.svg @@ -0,0 +1,990 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/stats.svg b/interface/resources/icons/+android/stats.svg new file mode 100644 index 0000000000..f642650c66 --- /dev/null +++ b/interface/resources/icons/+android/stats.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/interface/resources/icons/+android/tick.svg b/interface/resources/icons/+android/tick.svg new file mode 100644 index 0000000000..2c451c0994 --- /dev/null +++ b/interface/resources/icons/+android/tick.svg @@ -0,0 +1,950 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-a-msg.svg b/interface/resources/icons/tablet-icons/market-a-msg.svg new file mode 100644 index 0000000000..0ab93f3cc8 --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-a-msg.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-a.svg b/interface/resources/icons/tablet-icons/market-a.svg index f8ba17301e..db2d948d7b 100644 --- a/interface/resources/icons/tablet-icons/market-a.svg +++ b/interface/resources/icons/tablet-icons/market-a.svg @@ -1,64 +1,15 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-i-msg.svg b/interface/resources/icons/tablet-icons/market-i-msg.svg new file mode 100644 index 0000000000..488c507c6e --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-i-msg.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-i.svg b/interface/resources/icons/tablet-icons/market-i.svg index bf9aa9335f..7d11507cdb 100644 --- a/interface/resources/icons/tablet-icons/market-i.svg +++ b/interface/resources/icons/tablet-icons/market-i.svg @@ -1,23 +1,19 @@ - - + - - - - - - - - + + + + diff --git a/interface/resources/images/analog_stick.png b/interface/resources/images/analog_stick.png index e9457c7307..238c2c74f4 100644 Binary files a/interface/resources/images/analog_stick.png and b/interface/resources/images/analog_stick.png differ diff --git a/interface/resources/images/analog_stick_base.png b/interface/resources/images/analog_stick_base.png index 3b7b8aa8a9..ac8c1b9ae8 100644 Binary files a/interface/resources/images/analog_stick_base.png and b/interface/resources/images/analog_stick_base.png differ diff --git a/interface/resources/images/fly.png b/interface/resources/images/fly.png new file mode 100644 index 0000000000..0edfcab21b Binary files /dev/null and b/interface/resources/images/fly.png differ diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml index 0b12301561..4477d512fc 100644 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ b/interface/resources/qml/+android/AddressBarDialog.qml @@ -33,7 +33,8 @@ Item { bar.visible = shown; sendToScript({method: 'shownChanged', params: { shown: shown }}); if (shown) { - updateLocationText(false); + addressLine.text=""; + updateLocationText(addressLine.text.length > 0); } } @@ -43,7 +44,7 @@ Item { } Component.onCompleted: { - updateLocationText(false); + updateLocationText(addressLine.text.length > 0); } HifiConstants { id: hifi } @@ -66,6 +67,10 @@ Item { fill: parent } + MouseArea { + anchors.fill: parent + } + QmlHifi.WindowHeader { id: header iconSource: "../../../icons/goto-i.svg" @@ -75,33 +80,33 @@ Item { HifiStyles.RalewayRegular { id: notice text: "YOUR LOCATION" - font.pixelSize: hifi.fonts.pixelSize * 2.15; + font.pixelSize: (hifi.fonts.pixelSize * 2.15) * (android.dimen.atLeast1440p ? 1 : .75); color: "#2CD7FF" anchors { bottom: addressBackground.top - bottomMargin: 45 + bottomMargin: android.dimen.atLeast1440p ? 45 : 34 left: addressBackground.left - leftMargin: 60 + leftMargin: android.dimen.atLeast1440p ? 60 : 45 } } - property int inputAreaHeight: 210 + property int inputAreaHeight: android.dimen.atLeast1440p ? 210 : 156 property int inputAreaStep: (height - inputAreaHeight) / 2 ToolbarButton { id: homeButton - y: 280 + y: android.dimen.atLeast1440p ? 280 : 210 imageURL: "../../icons/home.svg" onClicked: { addressBarDialog.loadHome(); bar.shown = false; } anchors { - leftMargin: 75 + leftMargin: android.dimen.atLeast1440p ? 75 : 56 left: parent.left } - size: 150 + size: android.dimen.atLeast1440p ? 150 : 150//112 } ToolbarButton { @@ -110,10 +115,10 @@ Item { onClicked: addressBarDialog.loadBack(); anchors { left: homeButton.right - leftMargin: 70 + leftMargin: android.dimen.atLeast1440p ? 70 : 52 verticalCenter: homeButton.verticalCenter } - size: 150 + size: android.dimen.atLeast1440p ? 150 : 150 } ToolbarButton { id: forwardArrow; @@ -121,16 +126,16 @@ Item { onClicked: addressBarDialog.loadForward(); anchors { left: backArrow.right - leftMargin: 60 + leftMargin: android.dimen.atLeast1440p ? 60 : 45 verticalCenter: homeButton.verticalCenter } - size: 150 + size: android.dimen.atLeast1440p ? 150 : 150 } HifiStyles.FiraSansRegular { id: location; font.pixelSize: addressLine.font.pixelSize; - color: "gray"; + color: "lightgray"; clip: true; anchors.fill: addressLine; visible: addressLine.text.length === 0 @@ -139,25 +144,22 @@ Item { Rectangle { id: addressBackground - x: 780 - y: 280 - width: 1440 - height: 150 + x: android.dimen.atLeast1440p ? 780 : 585 + y: android.dimen.atLeast1440p ? 280 : 235 // tweaking by hand + width: android.dimen.atLeast1440p ? 1270 : 952 + height: android.dimen.atLeast1440p ? 150 : 112 color: "#FFFFFF" } TextInput { id: addressLine focus: true - x: 870 - y: 450 - width: 1350 - height: 120 + x: android.dimen.atLeast1440p ? 870 : 652 + y: android.dimen.atLeast1440p ? 300 : 245 // tweaking by hand + width: android.dimen.atLeast1440p ? 1200 : 900 + height: android.dimen.atLeast1440p ? 120 : 90 inputMethodHints: Qt.ImhNoPredictiveText //helperText: "Hint is here" - anchors { - verticalCenter: homeButton.verticalCenter - } font.pixelSize: hifi.fonts.pixelSize * 3.75 onTextChanged: { //filterChoicesByText(); @@ -227,4 +229,4 @@ Item { } } -} \ No newline at end of file +} diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml new file mode 100644 index 0000000000..567cca9bcf --- /dev/null +++ b/interface/resources/qml/+android/LoginDialog.qml @@ -0,0 +1,95 @@ +// +// LoginDialog.qml +// +// Created by David Rowe on 3 Jun 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.4 + +import "controls-uit" +import "styles-uit" +import "windows" + +import "LoginDialog" + +ModalWindow { + id: root + HifiConstants { id: hifi } + + objectName: "LoginDialog" + implicitWidth: 1560 + implicitHeight: 450 + y:0 + destroyOnCloseButton: true + destroyOnHidden: true + visible: true + + property string iconText: "" + property int iconSize: 105 + + property string title: "" + property int titleWidth: 0 + + keyboardOverride: true // Disable ModalWindow's keyboard. + + function tryDestroy() { + Controller.setVPadHidden(false); + root.destroy(); + } + + LoginDialog { + id: loginDialog + + Loader { + id: bodyLoader + source: loginDialog.isSteamRunning() ? "LoginDialog/+android/SignInBody.qml" : "LoginDialog/+android/LinkAccountBody.qml" + } + } + + Component.onCompleted: { + this.anchors.centerIn = undefined; + this.y = 150; + this.x = (parent.width - this.width) / 2; + Controller.setVPadHidden(true); + } + + Keys.onPressed: { + if (!visible) { + return + } + + if (event.modifiers === Qt.ControlModifier) + switch (event.key) { + case Qt.Key_A: + event.accepted = true + detailedText.selectAll() + break + case Qt.Key_C: + event.accepted = true + detailedText.copy() + break + case Qt.Key_Period: + if (Qt.platform.os === "osx") { + event.accepted = true + content.reject() + } + break + } else switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + event.accepted = true + destroy() + break + + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + break + } + } +} diff --git a/interface/resources/qml/+android/StatText.qml b/interface/resources/qml/+android/StatText.qml new file mode 100644 index 0000000000..5dc8377030 --- /dev/null +++ b/interface/resources/qml/+android/StatText.qml @@ -0,0 +1,9 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +Text { + color: "white"; + style: Text.Outline; + styleColor: "black"; + font.pixelSize: 15; +} diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index d961285a46..fe827f6ece 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -256,7 +256,7 @@ Item { id: octreeCol spacing: 4; x: 4; y: 4; StatText { - text: "Engine: " + root.engineFrameTime.toFixed(1) + " ms" + text: "Render Engine: " + root.engineFrameTime.toFixed(1) + " ms" } StatText { text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms" diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScopeUI.qml similarity index 100% rename from interface/resources/qml/AudioScope.qml rename to interface/resources/qml/AudioScopeUI.qml diff --git a/interface/resources/qml/AvatarInputs.qml b/interface/resources/qml/AvatarInputsBar.qml similarity index 77% rename from interface/resources/qml/AvatarInputs.qml rename to interface/resources/qml/AvatarInputsBar.qml index be4bf03465..4a071d2d04 100644 --- a/interface/resources/qml/AvatarInputs.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -8,15 +8,12 @@ import Hifi 1.0 as Hifi import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtGraphicalEffects 1.0 -import Qt.labs.settings 1.0 import "./hifi/audio" as HifiAudio -Hifi.AvatarInputs { +Item { id: root; - objectName: "AvatarInputs" + objectName: "AvatarInputsBar" property int modality: Qt.NonModal width: audio.width; height: audio.height; @@ -26,7 +23,7 @@ Hifi.AvatarInputs { HifiAudio.MicBar { id: audio; - visible: root.showAudioTools; + visible: AvatarInputs.showAudioTools; standalone: true; dragTarget: parent; } diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index f1dd3c8350..f0475dfebd 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.2 import QtWebChannel 1.0 import QtWebEngine 1.5 diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml index d9255e51eb..96bfb5c36b 100644 --- a/interface/resources/qml/CurrentAPI.qml +++ b/interface/resources/qml/CurrentAPI.qml @@ -33,8 +33,6 @@ Item { width: parent.width height: parent.height } - - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } Timer { id: updateList diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml new file mode 100644 index 0000000000..38e65af4ca --- /dev/null +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -0,0 +1,290 @@ +// +// LinkAccountBody.qml +// +// Created by Clement on 7/18/16 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.4 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 as OriginalStyles + +import "../../controls-uit" +import "../../styles-uit" + +Item { + id: linkAccountBody + + clip: true + height: 300 + width: root.pane.width + property bool failAfterSignUp: false + function login() { + mainTextContainer.visible = false + toggleLoading(true) + loginDialog.login(usernameField.text, passwordField.text) + } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + + QtObject { + id: d + readonly property int minWidth: 1440 + readonly property int maxWidth: 3840 + readonly property int minHeight: 150 + readonly property int maxHeight: 660 + + function resize() { + var targetWidth = Math.max(titleWidth, form.contentWidth); + var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height + + 4 * hifi.dimensions.contentSpacing.y + form.height + + hifi.dimensions.contentSpacing.y + buttons.height; + + if (additionalInformation.visible) { + targetWidth = Math.max(targetWidth, additionalInformation.width); + targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height + } + + parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + parent.height = 420; + /*root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) + + (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y);*/ + } + } + + function toggleLoading(isLoading) { + linkAccountSpinner.visible = isLoading + form.visible = !isLoading + + if (loginDialog.isSteamRunning()) { + additionalInformation.visible = !isLoading + } + + leftButton.visible = !isLoading + buttons.visible = !isLoading + } + + BusyIndicator { + id: linkAccountSpinner + + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: false + running: true + + width: 144 + height: 144 + } + + ShortcutText { + id: mainTextContainer + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + + visible: false + + text: qsTr("Username or password incorrect.") + wrapMode: Text.WordWrap + color: hifi.colors.redAccent + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + + Column { + id: form + anchors { + top: mainTextContainer.bottom + left: parent.left + margins: 0 + topMargin: 0 // hifi.dimensions.contentSpacing.y + } + spacing: hifi.dimensions.contentSpacing.y / 2 + + TextField { + id: usernameField + anchors { + horizontalCenter: parent.horizontalCenter + } + width: 1080 + placeholderText: qsTr("Username or Email") + } + + TextField { + id: passwordField + anchors { + horizontalCenter: parent.horizontalCenter + } + width: 1080 + + placeholderText: qsTr("Password") + echoMode: TextInput.Password + } + } + + InfoItem { + id: additionalInformation + anchors { + top: form.bottom + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: loginDialog.isSteamRunning() + + text: qsTr("Your steam account informations will not be exposed to other users.") + wrapMode: Text.WordWrap + color: hifi.colors.baseGrayHighlight + lineHeight: 3 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + + // Override ScrollingWindow's keyboard that would be at very bottom of dialog. + Keyboard { + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttons.top + bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 + } + } + + Row { + id: leftButton + anchors { + left: parent.left + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Sign Up") + visible: !loginDialog.isSteamRunning() + + onClicked: { + bodyLoader.setSource("SignUpBody.qml") + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + } + + Row { + id: buttons + anchors { + right: parent.right + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + id: linkAccountButton + anchors.verticalCenter: parent.verticalCenter + + text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login") + color: hifi.buttons.blue + + onClicked: { + Qt.inputMethod.hide(); + linkAccountBody.login(); + } + } + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Cancel") + + onClicked: { + Qt.inputMethod.hide(); + root.tryDestroy(); + } + } + } + + Component.onCompleted: { + root.title = qsTr("Sign Into High Fidelity") + root.iconText = "<" + keyboardEnabled = HMD.active; + d.resize(); + + if (failAfterSignUp) { + mainTextContainer.text = "Account created successfully." + mainTextContainer.visible = true + } + + //usernameField.forceActiveFocus(); + } + + Connections { + target: loginDialog + onHandleLoginCompleted: { + console.log("Login Succeeded, linking steam account") + + if (loginDialog.isSteamRunning()) { + loginDialog.linkSteam() + } else { + bodyLoader.setSource("../WelcomeBody.qml", { "welcomeBack" : true }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + onHandleLoginFailed: { + console.log("Login Failed") + mainTextContainer.visible = true + toggleLoading(false) + } + onHandleLinkCompleted: { + console.log("Link Succeeded") + + bodyLoader.setSource("../WelcomeBody.qml", { "welcomeBack" : true }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + onHandleLinkFailed: { + console.log("Link Failed") + toggleLoading(false) + } + } + + Keys.onPressed: { + if (!visible) { + return + } + + switch (event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + linkAccountBody.login() + break + } + } +} diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml new file mode 100644 index 0000000000..3a44a8d741 --- /dev/null +++ b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml @@ -0,0 +1,297 @@ +// +// SignUpBody.qml +// +// Created by Stephen Birarda on 7 Dec 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.4 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 as OriginalStyles + +import "../../controls-uit" +import "../../styles-uit" + +Item { + id: signupBody + + clip: true + height: root.pane.height + width: root.pane.width + + function signup() { + mainTextContainer.visible = false + toggleLoading(true) + loginDialog.signup(emailField.text, usernameField.text, passwordField.text) + } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + + QtObject { + id: d + readonly property int minWidth: 960 + readonly property int maxWidth: 2560 + readonly property int minHeight: 240 + readonly property int maxHeight: 1480 + + function resize() { + var targetWidth = Math.max(titleWidth, form.contentWidth); + var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height + + 4 * hifi.dimensions.contentSpacing.y + form.height + + hifi.dimensions.contentSpacing.y + buttons.height; + + parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + //parent.height = 650; + parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)); + + } + } + + function toggleLoading(isLoading) { + linkAccountSpinner.visible = isLoading + form.visible = !isLoading + + leftButton.visible = !isLoading + buttons.visible = !isLoading + } + + BusyIndicator { + id: linkAccountSpinner + + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: false + running: true + + width: 48 + height: 48 + } + + ShortcutText { + id: mainTextContainer + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: false + + text: qsTr("There was an unknown error while creating your account.") + wrapMode: Text.WordWrap + color: hifi.colors.redAccent + horizontalAlignment: Text.AlignLeft + } + + Column { + id: form + anchors { + top: mainTextContainer.bottom + left: parent.left + margins: 0 + topMargin: 0; // 2 * hifi.dimensions.contentSpacing.y + } + spacing: hifi.dimensions.contentSpacing.y / 2 + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: emailField + anchors { + verticalCenter: parent.verticalCenter + } + width: 780 + + placeholderText: "Email" + } + } + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: usernameField + anchors { + verticalCenter: parent.verticalCenter + } + width: 780 + + placeholderText: "Username" + } + + ShortcutText { + anchors { + verticalCenter: parent.verticalCenter + } + + text: qsTr("No spaces / special chars.") + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + color: hifi.colors.blueAccent + } + } + + Row { + spacing: hifi.dimensions.contentSpacing.x + + TextField { + id: passwordField + anchors { + verticalCenter: parent.verticalCenter + } + width: 780 + + placeholderText: "Password" + echoMode: TextInput.Password + } + + ShortcutText { + anchors { + verticalCenter: parent.verticalCenter + } + + text: qsTr("At least 6 characters") + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + + color: hifi.colors.blueAccent + } + } + + } + + // Override ScrollingWindow's keyboard that would be at very bottom of dialog. + Keyboard { + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttons.top + bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 + } + } + + Row { + id: leftButton + anchors { + left: parent.left + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y// / 2 + } + + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Existing User") + + onClicked: { + bodyLoader.setSource("LinkAccountBody.qml") + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + } + + Row { + id: buttons + anchors { + right: parent.right + top: form.bottom + topMargin: hifi.dimensions.contentSpacing.y / 2 + } + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + + Button { + id: linkAccountButton + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Sign Up") + color: hifi.buttons.blue + + onClicked: signupBody.signup() + } + + Button { + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("Cancel") + + onClicked: root.destroy() + } + } + + Component.onCompleted: { + root.title = qsTr("Create an Account") + root.iconText = "<" + keyboardEnabled = HMD.active; + d.resize(); + + emailField.forceActiveFocus(); + } + + Connections { + target: loginDialog + onHandleSignupCompleted: { + console.log("Sign Up Succeeded"); + + // now that we have an account, login with that username and password + loginDialog.login(usernameField.text, passwordField.text) + } + onHandleSignupFailed: { + console.log("Sign Up Failed") + toggleLoading(false) + + mainTextContainer.text = errorString + mainTextContainer.visible = true + + d.resize(); + } + onHandleLoginCompleted: { + bodyLoader.setSource("../WelcomeBody.qml", { "welcomeBack": false }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + onHandleLoginFailed: { + // we failed to login, show the LoginDialog so the user will try again + bodyLoader.setSource("LinkAccountBody.qml", { "failAfterSignUp": true }) + bodyLoader.item.width = root.pane.width + bodyLoader.item.height = root.pane.height + } + } + + Keys.onPressed: { + if (!visible) { + return + } + + switch (event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true + signupBody.signup() + break + } + } +} diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 0e2f2a5282..4708bfdebe 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -174,7 +174,7 @@ Item { } } - CheckBoxQQC2 { + CheckBox { id: showPassword text: "Show password" } diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 9d55998b40..5eb99e0ece 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -11,7 +11,6 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 as OriginalStyles import "../controls-uit" import "../styles-uit" diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index 5c212578b8..bf05a36ce1 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -11,7 +11,6 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 as OriginalStyles import "../controls-uit" import "../styles-uit" diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index d2daf0fa1d..d73a574081 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtWebEngine 1.1 import QtWebChannel 1.0 diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 7c1ce704c3..bef6423e25 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -1,9 +1,4 @@ - import QtQuick 2.3 -import QtQuick.Controls 1.4 -import QtWebChannel 1.0 -import QtWebEngine 1.2 -import QtWebSockets 1.0 import "windows" as Windows import "controls" diff --git a/interface/resources/qml/StatText.qml b/interface/resources/qml/StatText.qml index 69963c1373..9fefbd28b8 100644 --- a/interface/resources/qml/StatText.qml +++ b/interface/resources/qml/StatText.qml @@ -1,5 +1,4 @@ import QtQuick 2.3 -import QtQuick.Controls 1.2 Text { color: "white"; diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index a12bd4b2c0..d961285a46 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -1,6 +1,5 @@ import Hifi 1.0 as Hifi import QtQuick 2.3 -import QtQuick.Controls 1.2 import '.' Item { @@ -257,11 +256,7 @@ Item { id: octreeCol spacing: 4; x: 4; y: 4; StatText { - text: "Render Engine: " + root.engineFrameTime.toFixed(1) + " ms" - } - StatText { - visible: root.expanded - text: root.renderEngineStats + text: "Engine: " + root.engineFrameTime.toFixed(1) + " ms" } StatText { text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms" diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 8dbcc8f4f8..141c1f25a7 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtWebChannel 1.0 import QtWebEngine 1.5 diff --git a/interface/resources/qml/Web3DOverlay.qml b/interface/resources/qml/Web3DOverlay.qml index a1fa2d2641..fdd5d8a7c6 100644 --- a/interface/resources/qml/Web3DOverlay.qml +++ b/interface/resources/qml/Web3DOverlay.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "controls" as Controls diff --git a/interface/resources/qml/controller/AnalogButton.qml b/interface/resources/qml/controller/AnalogButton.qml index 82beb818ab..6539bc793d 100644 --- a/interface/resources/qml/controller/AnalogButton.qml +++ b/interface/resources/qml/controller/AnalogButton.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 Item { id: root diff --git a/interface/resources/qml/controller/AnalogStick.qml b/interface/resources/qml/controller/AnalogStick.qml index c0d10bac59..d7b52a6319 100644 --- a/interface/resources/qml/controller/AnalogStick.qml +++ b/interface/resources/qml/controller/AnalogStick.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 Item { id: root diff --git a/interface/resources/qml/controller/Hydra.qml b/interface/resources/qml/controller/Hydra.qml index 19f3b4c193..e5c918cc77 100644 --- a/interface/resources/qml/controller/Hydra.qml +++ b/interface/resources/qml/controller/Hydra.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import "hydra" diff --git a/interface/resources/qml/controller/Standard.qml b/interface/resources/qml/controller/Standard.qml index 45e4febfa2..1c51a527cd 100644 --- a/interface/resources/qml/controller/Standard.qml +++ b/interface/resources/qml/controller/Standard.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import "xbox" diff --git a/interface/resources/qml/controller/ToggleButton.qml b/interface/resources/qml/controller/ToggleButton.qml index ee8bd380e2..567191bd25 100644 --- a/interface/resources/qml/controller/ToggleButton.qml +++ b/interface/resources/qml/controller/ToggleButton.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 Item { id: root diff --git a/interface/resources/qml/controller/Xbox.qml b/interface/resources/qml/controller/Xbox.qml index 4ff2959129..71f99c2081 100644 --- a/interface/resources/qml/controller/Xbox.qml +++ b/interface/resources/qml/controller/Xbox.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import "xbox" diff --git a/interface/resources/qml/controller/hydra/HydraButtons.qml b/interface/resources/qml/controller/hydra/HydraButtons.qml index aa8927f5b6..f579527a1f 100644 --- a/interface/resources/qml/controller/hydra/HydraButtons.qml +++ b/interface/resources/qml/controller/hydra/HydraButtons.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import ".." diff --git a/interface/resources/qml/controller/hydra/HydraStick.qml b/interface/resources/qml/controller/hydra/HydraStick.qml index d082a20b10..7b5ce41c76 100644 --- a/interface/resources/qml/controller/hydra/HydraStick.qml +++ b/interface/resources/qml/controller/hydra/HydraStick.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import ".." diff --git a/interface/resources/qml/controller/xbox/DPad.qml b/interface/resources/qml/controller/xbox/DPad.qml index 2cfb6412e7..20eda19648 100644 --- a/interface/resources/qml/controller/xbox/DPad.qml +++ b/interface/resources/qml/controller/xbox/DPad.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import ".." diff --git a/interface/resources/qml/controller/xbox/LeftAnalogStick.qml b/interface/resources/qml/controller/xbox/LeftAnalogStick.qml index 8e2de1eb36..430d6f39a4 100644 --- a/interface/resources/qml/controller/xbox/LeftAnalogStick.qml +++ b/interface/resources/qml/controller/xbox/LeftAnalogStick.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import ".." diff --git a/interface/resources/qml/controller/xbox/RightAnalogStick.qml b/interface/resources/qml/controller/xbox/RightAnalogStick.qml index 0cdfeda2cf..89e93aa8bc 100644 --- a/interface/resources/qml/controller/xbox/RightAnalogStick.qml +++ b/interface/resources/qml/controller/xbox/RightAnalogStick.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import ".." diff --git a/interface/resources/qml/controller/xbox/XboxButtons.qml b/interface/resources/qml/controller/xbox/XboxButtons.qml index e26a4a0b98..5c68fcff72 100644 --- a/interface/resources/qml/controller/xbox/XboxButtons.qml +++ b/interface/resources/qml/controller/xbox/XboxButtons.qml @@ -1,7 +1,4 @@ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 import ".." diff --git a/interface/resources/qml/controls-uit/+android/Button.qml b/interface/resources/qml/controls-uit/+android/Button.qml new file mode 100644 index 0000000000..2f05b35685 --- /dev/null +++ b/interface/resources/qml/controls-uit/+android/Button.qml @@ -0,0 +1,125 @@ +// +// Button.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 +import TabletScriptingInterface 1.0 + +import "../styles-uit" + +Original.Button { + id: root; + + property int color: 0 + property int colorScheme: hifi.colorSchemes.light + property string buttonGlyph: ""; + + width: hifi.dimensions.buttonWidth + height: hifi.dimensions.controlLineHeight + + HifiConstants { id: hifi } + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + + style: ButtonStyle { + + background: Rectangle { + radius: hifi.buttons.radius + + border.width: (control.color === hifi.buttons.none || + (control.color === hifi.buttons.noneBorderless && control.hovered) || + (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) || + (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0; + border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight : + (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white); + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + label: Item { + HiFiGlyphs { + id: buttonGlyph; + visible: root.buttonGlyph !== ""; + text: root.buttonGlyph === "" ? hifi.glyphs.question : root.buttonGlyph; + // Size + size: 34; + // Anchors + anchors.right: buttonText.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + // Style + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme]; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + RalewayBold { + id: buttonText; + anchors.centerIn: parent; + font.capitalization: Font.AllUppercase + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + size: hifi.fontSizes.buttonLabel + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + } + } + } +} diff --git a/interface/resources/qml/controls-uit/+android/ImageButton.qml b/interface/resources/qml/controls-uit/+android/ImageButton.qml new file mode 100644 index 0000000000..5ebf7cd3e9 --- /dev/null +++ b/interface/resources/qml/controls-uit/+android/ImageButton.qml @@ -0,0 +1,82 @@ +// +// ImageButton.qml +// interface/resources/qml/controls-uit +// +// Created by Gabriel Calero & Cristian Duarte on 12 Oct 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Layouts 1.3 +import "../styles-uit" as HifiStyles + +Item { + id: button + + property string text: "" + property string source : "" + property string hoverSource : "" + property real fontSize: 10 + property string fontColor: "#FFFFFF" + property string hoverFontColor: "#000000" + + signal clicked(); + + Rectangle { + color: "transparent" + anchors.fill: parent + Image { + id: image + anchors.fill: parent + source: button.source + } + + HifiStyles.FiraSansRegular { + id: buttonText + anchors.centerIn: parent + text: button.text + color: button.fontColor + font.pixelSize: button.fontSize + } + + MouseArea { + anchors.fill: parent + onClicked: button.clicked(); + onEntered: { + button.state = "hover state"; + } + onExited: { + button.state = "base state"; + } + } + + + } + states: [ + State { + name: "hover state" + PropertyChanges { + target: image + source: button.hoverSource + } + PropertyChanges { + target: buttonText + color: button.hoverFontColor + } + }, + State { + name: "base state" + PropertyChanges { + target: image + source: button.source + } + PropertyChanges { + target: buttonText + color: button.fontColor + } + } + ] +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/+android/Table.qml b/interface/resources/qml/controls-uit/+android/Table.qml new file mode 100644 index 0000000000..3c1d0fcd3c --- /dev/null +++ b/interface/resources/qml/controls-uit/+android/Table.qml @@ -0,0 +1,165 @@ +// +// Table.qml +// +// Created by David Rowe on 18 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 as QQC2 + +import "../styles-uit" + +TableView { + id: tableView + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool expandSelectedRow: false + property bool centerHeaderText: false + readonly property real headerSpacing: 3 //spacing between sort indicator and table header title + property var titlePaintedPos: [] // storing extra data position behind painted + // title text and sort indicatorin table's header + signal titlePaintedPosSignal(int column) //signal that extradata position gets changed + + model: ListModel { } + + Component.onCompleted: { + if (flickableItem !== null && flickableItem !== undefined) { + tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar + } + } + + QQC2.ScrollBar { + id: scrollbar + parent: tableView.flickableItem + policy: QQC2.ScrollBar.AsNeeded + orientation: Qt.Vertical + visible: size < 1.0 + topPadding: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 + anchors.top: tableView.top + anchors.left: tableView.right + anchors.bottom: tableView.bottom + + background: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { + fill: parent; + topMargin: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight : 0 + } + color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight + : hifi.colors.tableScrollBackgroundDark + } + } + + contentItem: Item { + implicitWidth: hifi.dimensions.scrollbarHandleWidth + Rectangle { + anchors.fill: parent + radius: (width - 4)/2 + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark + } + } + } + + headerVisible: false + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + + + RalewayRegular { + id: titleText + x: centerHeaderText ? (parent.width - paintedWidth - + ((sortIndicatorVisible && + sortIndicatorColumn === styleData.column) ? + (titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 : + hifi.dimensions.tablePadding + text: styleData.value + size: hifi.fontSizes.tableHeading + font.capitalization: Font.AllUppercase + color: hifi.colors.baseGrayHighlight + horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) + anchors.verticalCenter: parent.verticalCenter + } + + //actual image of sort indicator in glyph font only 20% of real font size + //i.e. if the charachter size set to 60 pixels, actual image is 12 pixels + HiFiGlyphs { + id: titleSort + text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn + color: hifi.colors.darkGray + opacity: 0.6; + size: hifi.fontSizes.tableHeadingIcon + anchors.verticalCenter: titleText.verticalCenter + anchors.left: titleText.right + anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing + visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + onXChanged: { + titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth + + paintedWidth / 5 + tableView.headerSpacing*2 + titlePaintedPosSignal(styleData.column) + } + } + + Rectangle { + width: 1 + anchors { + left: parent.left + top: parent.top + topMargin: 1 + bottom: parent.bottom + bottomMargin: 2 + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: styleData.column > 0 + } + + Rectangle { + height: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + } + } + + // Use rectangle to draw border with rounded corners. + frameVisible: false + Rectangle { + color: "#00000000" + anchors { fill: parent; margins: -2 } + border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + border.width: 2 + } + anchors.margins: 2 // Shrink TableView to lie within border. + + backgroundVisible: true + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + style: TableViewStyle { + // Needed in order for rows to keep displaying rows after end of table entries. + backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 + } + + rowDelegate: Rectangle { + height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight + color: styleData.selected + ? hifi.colors.primaryHighlight + : tableView.isLightColorScheme + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + } +} diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index 926e9c4fe5..0119d76ac2 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -8,21 +8,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.3 as Original import TabletScriptingInterface 1.0 import "../styles-uit" Original.Button { - id: root; + id: control; property int color: 0 property int colorScheme: hifi.colorSchemes.light property string buttonGlyph: ""; - width: 120 + width: hifi.dimensions.buttonWidth height: hifi.dimensions.controlLineHeight HifiConstants { id: hifi } @@ -38,88 +37,86 @@ Original.Button { Tablet.playSound(TabletEnums.ButtonHover); } } - + onClicked: { Tablet.playSound(TabletEnums.ButtonClick); } - style: ButtonStyle { + background: Rectangle { + radius: hifi.buttons.radius - background: Rectangle { - radius: hifi.buttons.radius + border.width: (control.color === hifi.buttons.none || + (control.color === hifi.buttons.noneBorderless && control.hovered) || + (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) || + (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0; + border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight : + (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white); - border.width: (control.color === hifi.buttons.none || - (control.color === hifi.buttons.noneBorderless && control.hovered) || - (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) || - (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0; - border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight : - (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white); - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] } else if (!control.hovered && control.focus) { hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } + } else { + hifi.buttons.colorStart[control.color] } } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] } else if (!control.hovered && control.focus) { hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } + } else { + hifi.buttons.colorFinish[control.color] } } } } - - label: Item { - HiFiGlyphs { - id: buttonGlyph; - visible: root.buttonGlyph !== ""; - text: root.buttonGlyph === "" ? hifi.glyphs.question : root.buttonGlyph; - // Size - size: 34; - // Anchors - anchors.right: buttonText.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - // Style - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme]; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - } - RalewayBold { - id: buttonText; - anchors.centerIn: parent; - font.capitalization: Font.AllUppercase - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - size: hifi.fontSizes.buttonLabel - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.text - } - } + } + + contentItem: Item { + HiFiGlyphs { + id: buttonGlyph; + visible: control.buttonGlyph !== ""; + text: control.buttonGlyph === "" ? hifi.glyphs.question : control.buttonGlyph; + // Size + size: 34; + // Anchors + anchors.right: buttonText.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + // Style + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme]; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + RalewayBold { + id: buttonText; + anchors.centerIn: parent; + font.capitalization: Font.AllUppercase + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + size: hifi.fontSizes.buttonLabel + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + } } } + diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index e60f646327..e13c9875e8 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.2 +import QtQuick.Controls 2.2 as Original import "../styles-uit" @@ -28,85 +27,89 @@ Original.CheckBox { property bool wrap: true; readonly property int checkSize: Math.max(boxSize - 8, 10) readonly property int checkRadius: 2 - activeFocusOnPress: true + focusPolicy: Qt.ClickFocus + hoverEnabled: true onClicked: { Tablet.playSound(TabletEnums.ButtonClick); } -// TODO: doesnt works for QQC1. check with QQC2 -// onHovered: { -// Tablet.playSound(TabletEnums.ButtonHover); -// } + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } - style: CheckBoxStyle { - indicator: Rectangle { - id: box + + indicator: Rectangle { + id: box + implicitWidth: boxSize + implicitHeight: boxSize + radius: boxRadius + y: parent.height / 2 - height / 2 + border.width: 1 + border.color: pressed || hovered + ? hifi.colors.checkboxCheckedBorder + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + visible: pressed || hovered + anchors.centerIn: parent + id: innerBox + width: checkSize - 4 + height: width + radius: checkRadius + color: hifi.colors.checkboxCheckedBorder + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: checkRadius + anchors.centerIn: parent + color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked + border.width: 2 + border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder + visible: checked && !pressed || !checked && pressed + } + + Rectangle { + id: disabledOverlay + visible: !enabled width: boxSize height: boxSize radius: boxRadius border.width: 1 - border.color: pressed || hovered - ? hifi.colors.checkboxCheckedBorder - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - - gradient: Gradient { - GradientStop { - position: 0.2 - color: pressed || hovered - ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - } - } - - Rectangle { - visible: pressed || hovered - anchors.centerIn: parent - id: innerBox - width: checkSize - 4 - height: width - radius: checkRadius - color: hifi.colors.checkboxCheckedBorder - } - - Rectangle { - id: check - width: checkSize - height: checkSize - radius: checkRadius - anchors.centerIn: parent - color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked - border.width: 2 - border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder - visible: checked && !pressed || !checked && pressed - } - - Rectangle { - id: disabledOverlay - visible: !enabled - width: boxSize - height: boxSize - radius: boxRadius - border.width: 1 - border.color: hifi.colors.baseGrayHighlight - color: hifi.colors.baseGrayHighlight - opacity: 0.5 - } - } - - label: Label { - text: control.text - color: control.color - x: 2 - wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap - elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight - enabled: checkBox.enabled + border.color: hifi.colors.baseGrayHighlight + color: hifi.colors.baseGrayHighlight + opacity: 0.5 } } + + contentItem: Label { + text: checkBox.text + color: checkBox.color + x: 2 + verticalAlignment: Text.AlignVCenter + wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap + elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight + enabled: checkBox.enabled + leftPadding: checkBox.indicator.width + checkBox.spacing + } } diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml index 040cd8e505..8a9686ff5e 100644 --- a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -109,9 +109,9 @@ CheckBox { contentItem: Text { id: root - FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; } font.pixelSize: hifi.fontSizes.inputLabel - font.family: ralewaySemiBold.name + font.family: "Raleway" + font.weight: Font.DemiBold text: checkBox.text color: checkBox.color x: 2 diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index d672fa6387..ab8a6c2344 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "../styles-uit" import "../controls-uit" as HifiControls @@ -24,6 +23,7 @@ FocusScope { property alias comboBox: comboBox readonly property alias currentText: comboBox.currentText; property alias currentIndex: comboBox.currentIndex; + property int currentHighLightedIndex: comboBox.currentIndex; property int dropdownHeight: 480 property int colorScheme: hifi.colorSchemes.light @@ -40,196 +40,134 @@ FocusScope { implicitHeight: comboBox.height; focus: true - Rectangle { - id: background - gradient: Gradient { - GradientStop { - position: 0.2 - color: popup.visible - ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) - : (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart) - } - GradientStop { - position: 1.0 - color: popup.visible - ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) - : (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish) - } - } - anchors.fill: parent - } - - SystemPalette { id: palette } - ComboBox { id: comboBox anchors.fill: parent - visible: false - height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. - } - - FiraSansSemiBold { - id: textField - anchors { - left: parent.left - leftMargin: hifi.dimensions.textPadding - right: dropIcon.left - verticalCenter: parent.verticalCenter - } - size: hifi.fontSizes.textFieldInput - text: comboBox.currentText - elide: Text.ElideRight - color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) - } - - Item { - id: dropIcon - anchors { right: parent.right; verticalCenter: parent.verticalCenter } - height: background.height - width: height - Rectangle { - width: 1 - height: parent.height - anchors.top: parent.top - anchors.left: parent.left - color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray - } - HiFiGlyphs { - anchors { - top: parent.top - topMargin: -11 - horizontalCenter: parent.horizontalCenter - } - size: hifi.dimensions.spinnerSize - text: hifi.glyphs.caratDn - color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText) - } - } - - MouseArea { - id: controlHover hoverEnabled: true - anchors.fill: parent - onClicked: toggleList(); - } + visible: true + height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. - function toggleList() { - if (popup.visible) { - hideList(); - } else { - showList(); - } - } - - function showList() { - var r; - if (isDesktop) { - r = desktop.mapFromItem(root, 0, 0, root.width, root.height); - } else { - r = mapFromItem(root, 0, 0, root.width, root.height); - } - var y = r.y + r.height; - var bottom = y + scrollView.height; - var height = isDesktop ? desktop.height : tabletRoot.height; - if (bottom > height) { - y -= bottom - height + 8; - } - scrollView.x = r.x; - scrollView.y = y; - popup.visible = true; - popup.forceActiveFocus(); - listView.currentIndex = root.currentIndex; - scrollView.hoverEnabled = true; - } - - function hideList() { - popup.visible = false; - scrollView.hoverEnabled = false; - root.accepted(); - } - - FocusScope { - id: popup - parent: isDesktop ? desktop : root - anchors.fill: parent - z: isDesktop ? desktop.zLevels.menu : 12 - visible: false - focus: true - - MouseArea { - anchors.fill: parent - onClicked: hideList(); - } - - function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; } - function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; } - function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); } - function selectSpecificItem(index) { root.currentIndex = index; hideList(); } + function previousItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count - 1) % comboBox.count; } + function nextItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count + 1) % comboBox.count; } + function selectCurrentItem() { root.currentIndex = root.currentHighLightedIndex; close(); /*hideList();*/ } + function selectSpecificItem(index) { root.currentIndex = index; close();/*hideList();*/ } Keys.onUpPressed: previousItem(); Keys.onDownPressed: nextItem(); Keys.onSpacePressed: selectCurrentItem(); Keys.onRightPressed: selectCurrentItem(); Keys.onReturnPressed: selectCurrentItem(); - Keys.onEscapePressed: hideList(); - ScrollView { - id: scrollView - height: root.dropdownHeight + background: Rectangle { + gradient: Gradient { + GradientStop { + position: 0.2 + color: comboBox.popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart) + } + GradientStop { + position: 1.0 + color: comboBox.popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish) + } + } + } + + indicator: Item { + id: dropIcon + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + height: root.height + width: height + Rectangle { + width: 1 + height: parent.height + anchors.top: parent.top + anchors.left: parent.left + color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray + } + HiFiGlyphs { + anchors { top: parent.top; topMargin: -11; horizontalCenter: parent.horizontalCenter } + size: hifi.dimensions.spinnerSize + text: hifi.glyphs.caratDn + color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText) + } + } + + contentItem: FiraSansSemiBold { + id: textField + anchors { + left: parent.left + leftMargin: hifi.dimensions.textPadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.textFieldInput + text: comboBox.currentText + elide: Text.ElideRight + color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) + } + + delegate: ItemDelegate { + id: itemDelegate + hoverEnabled: true width: root.width + 4 - property bool hoverEnabled: false; + height: popupText.implicitHeight * 1.4 + highlighted: root.currentHighLightedIndex == index - style: ScrollViewStyle { - decrementControl: Item { - visible: false - } - incrementControl: Item { - visible: false - } - scrollBarBackground: Rectangle{ - implicitWidth: 20 - color: hifi.colors.baseGray - } - - handle: - Rectangle { - implicitWidth: 16 - anchors.left: parent.left - anchors.leftMargin: 3 - anchors.top: parent.top - anchors.bottom: parent.bottom - radius: 6 - color: hifi.colors.lightGrayText + onHoveredChanged: { + if (hovered) { + root.currentHighLightedIndex = index } } - ListView { + background: Rectangle { + color: itemDelegate.highlighted ? hifi.colors.primaryHighlight + : (isLightColorScheme ? hifi.colors.dropDownPressedLight + : hifi.colors.dropDownPressedDark) + } + + contentItem: FiraSansSemiBold { + id: popupText + anchors.left: parent.left + anchors.leftMargin: hifi.dimensions.textPadding + anchors.verticalCenter: parent.verticalCenter + text: comboBox.model[index] ? comboBox.model[index] + : (comboBox.model.get && comboBox.model.get(index).text ? + comboBox.model.get(index).text : "") + size: hifi.fontSizes.textFieldInput + color: hifi.colors.baseGray + } + } + popup: Popup { + y: comboBox.height - 1 + width: comboBox.width + implicitHeight: listView.contentHeight > dropdownHeight ? dropdownHeight + : listView.contentHeight + padding: 0 + topPadding: 1 + + onClosed: { + root.accepted() + } + + contentItem: ListView { id: listView - height: textField.height * count * 1.4 - model: root.model - delegate: Rectangle { - width: root.width + 4 - height: popupText.implicitHeight * 1.4 - color: (listView.currentIndex === index) ? hifi.colors.primaryHighlight : - (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) - FiraSansSemiBold { - anchors.left: parent.left - anchors.leftMargin: hifi.dimensions.textPadding - anchors.verticalCenter: parent.verticalCenter - id: popupText - text: listView.model[index] ? listView.model[index] : (listView.model.get && listView.model.get(index).text ? listView.model.get(index).text : "") - size: hifi.fontSizes.textFieldInput - color: hifi.colors.baseGray - } - MouseArea { - id: popupHover - anchors.fill: parent; - hoverEnabled: scrollView.hoverEnabled; - onEntered: listView.currentIndex = index; - onClicked: popup.selectSpecificItem(index); - } + clip: true + model: comboBox.popup.visible ? comboBox.delegateModel : null + currentIndex: root.currentHighLightedIndex + delegate: comboBox.delegate + ScrollBar.vertical: HifiControls.ScrollBar { + id: scrollbar + parent: listView + policy: ScrollBar.AsNeeded + visible: size < 1.0 } } + + background: Rectangle { + color: hifi.colors.baseGray + } } } diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml new file mode 100644 index 0000000000..ecae790b22 --- /dev/null +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -0,0 +1,321 @@ +// +// FilterBar.qml +// +// Created by Zach Fox on 17 Feb 2018-03-12 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Item { + id: root; + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray + property bool error: false; + property alias textFieldHeight: textField.height; + property string placeholderText; + property alias dropdownHeight: dropdownContainer.height; + property alias text: textField.text; + property alias primaryFilterChoices: filterBarModel; + property int primaryFilter_index: -1; + property string primaryFilter_filterName: ""; + property string primaryFilter_displayName: ""; + signal accepted; + + onPrimaryFilter_indexChanged: { + if (primaryFilter_index === -1) { + primaryFilter_filterName = ""; + primaryFilter_displayName = ""; + } else { + primaryFilter_filterName = filterBarModel.get(primaryFilter_index).filterName; + primaryFilter_displayName = filterBarModel.get(primaryFilter_index).displayName; + } + } + + TextField { + id: textField; + + anchors.top: parent.top; + anchors.right: parent.right; + anchors.left: parent.left; + + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput; + + placeholderText: root.primaryFilter_index === -1 ? root.placeholderText : ""; + + TextMetrics { + id: primaryFilterTextMetrics; + font.family: "FiraSans Regular"; + font.pixelSize: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + text: root.primaryFilter_displayName; + } + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + root.accepted(); + root.forceActiveFocus(); + } + break; + case Qt.Key_Backspace: + if (textField.text === "") { + primaryFilter_index = -1; + } + break; + } + } + + onAccepted: { + root.forceActiveFocus(); + } + + onActiveFocusChanged: { + if (!activeFocus) { + dropdownContainer.visible = false; + } + } + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText + } + } + } + + background: Rectangle { + id: mainFilterBarRectangle; + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.textFieldLightBackground + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.faintGray50 + } + } else { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.baseGrayShadow + } + } + } + + border.color: textField.error ? hifi.colors.redHighlight : + (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + border.width: 1 + radius: 4 + + Item { + id: searchButtonContainer; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + width: 42; + + // Search icon + HiFiGlyphs { + id: searchIcon; + text: hifi.glyphs.search + color: textField.color + size: 40; + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: paintedWidth; + } + + // Carat + HiFiGlyphs { + text: hifi.glyphs.caratDn; + color: textField.color; + size: 40; + anchors.left: parent.left; + anchors.leftMargin: 15; + width: paintedWidth; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + dropdownContainer.visible = !dropdownContainer.visible; + } + } + } + + Rectangle { + z: 999; + id: primaryFilterContainer; + color: textField.activeFocus ? hifi.colors.faintGray : hifi.colors.white; + width: primaryFilterTextMetrics.tightBoundingRect.width + 14; + height: parent.height - 8; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: searchButtonContainer.right; + anchors.leftMargin: 4; + visible: primaryFilterText.text !== ""; + radius: height/2; + + FiraSansRegular { + id: primaryFilterText; + text: root.primaryFilter_displayName; + anchors.fill: parent; + color: textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + size: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + } + } + } + + // "Clear" button + HiFiGlyphs { + text: hifi.glyphs.error + color: textField.color + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: root.text !== "" || root.primaryFilter_index !== -1; + + MouseArea { + anchors.fill: parent; + onClicked: { + root.text = ""; + root.primaryFilter_index = -1; + dropdownContainer.visible = false; + textField.forceActiveFocus(); + } + } + } + } + + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20); + rightPadding: 44; + } + + Rectangle { + id: dropdownContainer; + visible: false; + height: 50 * filterBarModel.count; + width: parent.width; + anchors.top: textField.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.white; + + ListModel { + id: filterBarModel; + } + + ListView { + id: dropdownListView; + interactive: false; + anchors.fill: parent; + model: filterBarModel; + delegate: Rectangle { + id: dropDownButton; + color: hifi.colors.white; + width: parent.width; + height: 50; + + RalewaySemiBold { + id: dropDownButtonText; + text: model.displayName; + anchors.fill: parent; + anchors.leftMargin: 12; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + onEntered: { + dropDownButton.color = hifi.colors.blueHighlight; + } + onExited: { + dropDownButton.color = hifi.colors.white; + } + onClicked: { + textField.forceActiveFocus(); + root.primaryFilter_index = index; + dropdownContainer.visible = false; + } + } + } + } + } + + DropShadow { + anchors.fill: dropdownContainer; + horizontalOffset: 0; + verticalOffset: 4; + radius: 4.0; + samples: 9 + color: Qt.rgba(0, 0, 0, 0.25); + source: dropdownContainer; + visible: dropdownContainer.visible; + } + + function changeFilterByDisplayName(name) { + for (var i = 0; i < filterBarModel.count; i++) { + if (filterBarModel.get(i).displayName === name) { + root.primaryFilter_index = i; + return; + } + } + + console.log("Passed displayName not found in filterBarModel! primaryFilter unchanged."); + } +} diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index 024c131a09..9129486720 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -8,15 +8,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - -import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 as Original import TabletScriptingInterface 1.0 import "../styles-uit" Original.Button { + id: control property int color: 0 property int colorScheme: hifi.colorSchemes.light property string glyph: "" @@ -41,59 +40,52 @@ Original.Button { Tablet.playSound(TabletEnums.ButtonClick); } - style: ButtonStyle { + background: Rectangle { + radius: hifi.buttons.radius - background: Rectangle { - radius: hifi.buttons.radius - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] } else if (!control.hovered && control.focus) { hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } - } - } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } + } else { + hifi.buttons.colorStart[control.color] } } } - } - - label: HiFiGlyphs { - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors { - // Tweak horizontal alignment so that it looks right. - left: parent.left - leftMargin: -0.5 + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } } - text: control.glyph - size: control.size } } + + contentItem: HiFiGlyphs { + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.glyph + size: control.size + } } + diff --git a/interface/resources/qml/controls-uit/HorizontalRule.qml b/interface/resources/qml/controls-uit/HorizontalRule.qml index 425500f1d4..0609cc451d 100644 --- a/interface/resources/qml/controls-uit/HorizontalRule.qml +++ b/interface/resources/qml/controls-uit/HorizontalRule.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Rectangle { anchors.left: parent.left diff --git a/interface/resources/qml/controls-uit/ImageMessageBox.qml b/interface/resources/qml/controls-uit/ImageMessageBox.qml index 95c753aab4..74313f7ffe 100644 --- a/interface/resources/qml/controls-uit/ImageMessageBox.qml +++ b/interface/resources/qml/controls-uit/ImageMessageBox.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../styles-uit" Item { diff --git a/interface/resources/qml/controls-uit/Keyboard.qml b/interface/resources/qml/controls-uit/Keyboard.qml index 0c86754734..9d4fd33022 100644 --- a/interface/resources/qml/controls-uit/Keyboard.qml +++ b/interface/resources/qml/controls-uit/Keyboard.qml @@ -26,15 +26,17 @@ Rectangle { readonly property int keyboardRowHeight: 50 readonly property int keyboardWidth: 480 + readonly property int keyboardHeight: 200 readonly property int mirrorTextHeight: keyboardRowHeight property bool password: false property alias mirroredText: mirrorText.text property bool showMirrorText: true - readonly property int raisedHeight: 200 - height: enabled && raised ? raisedHeight + (showMirrorText ? keyboardRowHeight : 0) : 0 + readonly property int raisedHeight: keyboardHeight + (showMirrorText ? keyboardRowHeight : 0) + + height: enabled && raised ? raisedHeight : 0 visible: enabled && raised property bool shiftMode: false @@ -125,8 +127,7 @@ Rectangle { TextInput { id: mirrorText visible: showMirrorText - FontLoader { id: font; source: "qrc:/fonts/FiraSans-Regular.ttf"; } - font.family: font.name + font.family: "Fira Sans" font.pixelSize: 20 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter @@ -159,18 +160,16 @@ Rectangle { id: keyboardRect y: showMirrorText ? mirrorTextHeight : 0 width: keyboardWidth - height: raisedHeight + height: keyboardHeight color: "#252525" anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 0 - FontLoader { id: hiFiGlyphs; source: "qrc:/fonts/hifi-glyphs.ttf"; } - Column { id: columnAlpha width: keyboardWidth - height: raisedHeight + height: keyboardHeight visible: !numeric Row { @@ -250,7 +249,7 @@ Rectangle { Key { width: 43; glyph: ","; } Key { width: 43; glyph: "."; } Key { - fontFamily: hiFiGlyphs.name; + fontFamily: "hifi-glyphs"; fontPixelSize: 48; letterAnchors.topMargin: -4; verticalAlignment: Text.AlignVCenter; @@ -262,7 +261,7 @@ Rectangle { Column { id: columnNumeric width: keyboardWidth - height: raisedHeight + height: keyboardHeight visible: numeric Row { @@ -343,7 +342,7 @@ Rectangle { Key { width: 43; glyph: ","; } Key { width: 43; glyph: "."; } Key { - fontFamily: hiFiGlyphs.name; + fontFamily: "hifi-glyphs"; fontPixelSize: 48; letterAnchors.topMargin: -4; verticalAlignment: Text.AlignVCenter; diff --git a/interface/resources/qml/controls-uit/Label.qml b/interface/resources/qml/controls-uit/Label.qml index 1dc3aa0dd4..4c7051b495 100644 --- a/interface/resources/qml/controls-uit/Label.qml +++ b/interface/resources/qml/controls-uit/Label.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import "../styles-uit" diff --git a/interface/resources/qml/controls-uit/QueuedButton.qml b/interface/resources/qml/controls-uit/QueuedButton.qml index 36ffbe582f..6612d582df 100644 --- a/interface/resources/qml/controls-uit/QueuedButton.qml +++ b/interface/resources/qml/controls-uit/QueuedButton.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 import "../styles-uit" import "." as HifiControls diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml index a818be072b..ebfe1ff9a9 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -9,8 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 as Original import "../styles-uit" import "../controls-uit" as HifiControls @@ -21,6 +20,8 @@ Original.RadioButton { id: radioButton HifiConstants { id: hifi } + hoverEnabled: true + property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light @@ -33,50 +34,53 @@ Original.RadioButton { Tablet.playSound(TabletEnums.ButtonClick); } -// TODO: doesnt works for QQC1. check with QQC2 -// onHovered: { -// Tablet.playSound(TabletEnums.ButtonHover); -// } - - style: RadioButtonStyle { - indicator: Rectangle { - id: box - width: boxSize - height: boxSize - radius: 7 - gradient: Gradient { - GradientStop { - position: 0.2 - color: pressed || hovered - ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart) - : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish) - : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - } - } - - Rectangle { - id: check - width: checkSize - height: checkSize - radius: 7 - anchors.centerIn: parent - color: "#00B4EF" - border.width: 1 - border.color: "#36CDFF" - visible: checked && !pressed || !checked && pressed - } - } - - label: RalewaySemiBold { - text: control.text - size: hifi.fontSizes.inputLabel - color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText - x: radioButton.boxSize / 2 + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); } } + + indicator: Rectangle { + id: box + width: boxSize + height: boxSize + radius: 7 + x: radioButton.leftPadding + y: parent.height / 2 - height / 2 + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart) + : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish) + : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: 7 + anchors.centerIn: parent + color: "#00B4EF" + border.width: 1 + border.color: "#36CDFF" + visible: checked && !pressed || !checked && pressed + } + } + + contentItem: RalewaySemiBold { + text: radioButton.text + size: hifi.fontSizes.inputLabel + color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: radioButton.indicator.width + radioButton.spacing + } } diff --git a/interface/resources/qml/controls-uit/ScrollBar.qml b/interface/resources/qml/controls-uit/ScrollBar.qml new file mode 100644 index 0000000000..125e84e585 --- /dev/null +++ b/interface/resources/qml/controls-uit/ScrollBar.qml @@ -0,0 +1,41 @@ +// +// ScrollBar.qml +// +// Created by Vlad Stelmahovsky on 27 Nov 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../styles-uit" + +ScrollBar { + visible: size < 1.0 + + HifiConstants { id: hifi } + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + background: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { fill: parent; topMargin: 3; bottomMargin: 3 } + radius: hifi.dimensions.scrollbarHandleWidth/2 + color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight + : hifi.colors.tableScrollBackgroundDark + } + } + + contentItem: Item { + implicitWidth: hifi.dimensions.scrollbarHandleWidth + Rectangle { + anchors { fill: parent; topMargin: 1; bottomMargin: 1 } + radius: hifi.dimensions.scrollbarHandleWidth/2 + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark + } + } +} diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 89bae9bcde..3726d3f260 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "../styles-uit" import "../controls-uit" as HifiControls @@ -23,66 +22,65 @@ Slider { property string label: "" property real controlHeight: height + (sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0) + property alias minimumValue: slider.from + property alias maximumValue: slider.to + height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 - style: SliderStyle { + background: Rectangle { + x: slider.leftPadding + y: slider.topPadding + slider.availableHeight / 2 - height / 2 - groove: Rectangle { - implicitWidth: 50 - implicitHeight: hifi.dimensions.sliderGrooveHeight + implicitWidth: 50 + implicitHeight: hifi.dimensions.sliderGrooveHeight + width: slider.availableWidth + height: implicitHeight + radius: height / 2 + color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark + + Rectangle { + width: slider.visualPosition * parent.width + height: parent.height radius: height / 2 - color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.blueAccent } + GradientStop { position: 1.0; color: hifi.colors.primaryHighlight } + } + } + } - Rectangle { - width: parent.height - 2 - height: slider.width * (slider.value - slider.minimumValue) / (slider.maximumValue - slider.minimumValue) - 1 - radius: height / 2 - anchors { - top: parent.top - topMargin: width + 1 - left: parent.left - leftMargin: 1 - } - transformOrigin: Item.TopLeft - rotation: -90 - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.blueAccent } - GradientStop { position: 1.0; color: hifi.colors.primaryHighlight } - } + handle: Rectangle { + x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width) + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: hifi.dimensions.sliderHandleSize + implicitHeight: hifi.dimensions.sliderHandleSize + radius: height / 2 + border.width: 1 + border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark + gradient: Gradient { + GradientStop { + position: 0.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart ) + : (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart ) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish ) + : (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish ) } } - handle: Rectangle { - implicitWidth: hifi.dimensions.sliderHandleSize - implicitHeight: hifi.dimensions.sliderHandleSize + Rectangle { + height: parent.height - 2 + width: height radius: height / 2 + anchors.centerIn: parent + color: hifi.colors.transparent border.width: 1 - border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark - gradient: Gradient { - GradientStop { - position: 0.0 - color: pressed || hovered - ? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart ) - : (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart ) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish ) - : (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish ) - } - } - - Rectangle { - height: parent.height - 2 - width: height - radius: height / 2 - anchors.centerIn: parent - color: hifi.colors.transparent - border.width: 1 - border.color: hifi.colors.black - } + border.color: hifi.colors.black } } diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index f2a7e0efe8..83c30ce162 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "../styles-uit" import "../controls-uit" as HifiControls @@ -19,56 +18,108 @@ SpinBox { id: spinBox property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property string label: "" property string labelInside: "" property color colorLabelInside: hifi.colors.white property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) + property int decimals: 2; + property real factor: Math.pow(10, decimals) - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - font.family: firaSansSemiBold.name + property real minimumValue: 0.0 + property real maximumValue: 0.0 + + property real realValue: 0.0 + property real realFrom: 0.0 + property real realTo: 100.0 + property real realStepSize: 1.0 + + implicitHeight: height + implicitWidth: width + + padding: 0 + leftPadding: 0 + rightPadding: padding + (up.indicator ? up.indicator.width : 0) + topPadding: 0 + bottomPadding: 0 + + locale: Qt.locale("en_US") + + onValueModified: realValue = value/factor + onValueChanged: realValue = value/factor + + stepSize: realStepSize*factor + value: realValue*factor + to : realTo*factor + from : realFrom*factor + + font.family: "Fira Sans SemiBold" font.pixelSize: hifi.fontSizes.textFieldInput height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0 - style: SpinBoxStyle { - id: spinStyle - background: Rectangle { - color: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) - : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) - border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight - border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 - } + background: Rectangle { + color: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) + border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight + border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 + } - textColor: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) - : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) + validator: DoubleValidator { + bottom: Math.min(spinBox.from, spinBox.to)*spinBox.factor + top: Math.max(spinBox.from, spinBox.to)*spinBox.factor + } + + textFromValue: function(value, locale) { + return parseFloat(value*1.0/factor).toFixed(decimals); + } + + valueFromText: function(text, locale) { + return Number.fromLocaleString(locale, text); + } + + + contentItem: TextInput { + z: 2 + color: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - - horizontalAlignment: Qt.AlignLeft - padding.left: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding - padding.right: hifi.dimensions.spinnerSize - padding.top: 0 - - incrementControl: HiFiGlyphs { - id: incrementButton + text: spinBox.textFromValue(spinBox.value, spinBox.locale) + verticalAlignment: Qt.AlignVCenter + leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding + //rightPadding: hifi.dimensions.spinnerSize + width: spinBox.width - hifi.dimensions.spinnerSize + } + up.indicator: Item { + x: spinBox.width - implicitWidth - 5 + y: 1 + clip: true + implicitHeight: spinBox.implicitHeight/2 + implicitWidth: spinBox.implicitHeight/2 + HiFiGlyphs { + anchors.centerIn: parent text: hifi.glyphs.caratUp - x: 10 - y: 1 size: hifi.dimensions.spinnerSize - color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + color: spinBox.down.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } + } - decrementControl: HiFiGlyphs { - text: hifi.glyphs.caratDn - x: 10 - y: -1 - size: hifi.dimensions.spinnerSize - color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray - } + down.indicator: Item { + x: spinBox.width - implicitWidth - 5 + y: spinBox.implicitHeight/2 + clip: true + implicitHeight: spinBox.implicitHeight/2 + implicitWidth: spinBox.implicitHeight/2 + HiFiGlyphs { + anchors.centerIn: parent + text: hifi.glyphs.caratDn + size: hifi.dimensions.spinnerSize + color: spinBox.down.pressed || spinBox.down.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } } HifiControls.Label { @@ -92,26 +143,26 @@ SpinBox { visible: spinBox.labelInside != "" } - MouseArea { - anchors.fill: parent - propagateComposedEvents: true - onWheel: { - if(spinBox.activeFocus) - wheel.accepted = false - else - wheel.accepted = true - } - onPressed: { - mouse.accepted = false - } - onReleased: { - mouse.accepted = false - } - onClicked: { - mouse.accepted = false - } - onDoubleClicked: { - mouse.accepted = false - } - } +// MouseArea { +// anchors.fill: parent +// propagateComposedEvents: true +// onWheel: { +// if(spinBox.activeFocus) +// wheel.accepted = false +// else +// wheel.accepted = true +// } +// onPressed: { +// mouse.accepted = false +// } +// onReleased: { +// mouse.accepted = false +// } +// onClicked: { +// mouse.accepted = false +// } +// onDoubleClicked: { +// mouse.accepted = false +// } +// } } diff --git a/interface/resources/qml/controls-uit/Switch.qml b/interface/resources/qml/controls-uit/Switch.qml index d54f986717..bfe86b1420 100644 --- a/interface/resources/qml/controls-uit/Switch.qml +++ b/interface/resources/qml/controls-uit/Switch.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 as Original import "../styles-uit" @@ -33,44 +32,49 @@ Item { Original.Switch { id: originalSwitch; - activeFocusOnPress: true; + focusPolicy: Qt.ClickFocus anchors.top: rootSwitch.top; anchors.left: rootSwitch.left; anchors.leftMargin: rootSwitch.width/2 - rootSwitch.switchWidth/2; onCheckedChanged: rootSwitch.onCheckedChanged(); onClicked: rootSwitch.clicked(); + hoverEnabled: true - style: SwitchStyle { + topPadding: 3; + leftPadding: 3; + rightPadding: 3; + bottomPadding: 3; - padding { - top: 3; - left: 3; - right: 3; - bottom: 3; + onHoveredChanged: { + if (hovered) { + switchHandle.color = hifi.colors.blueHighlight; + } else { + switchHandle.color = hifi.colors.lightGray; + } + } + + background: Rectangle { + color: "#252525"; + implicitWidth: rootSwitch.switchWidth; + implicitHeight: rootSwitch.height; + radius: rootSwitch.switchRadius; + } + + indicator: Rectangle { + id: switchHandle; + implicitWidth: rootSwitch.height - originalSwitch.topPadding - originalSwitch.bottomPadding; + implicitHeight: implicitWidth; + radius: implicitWidth/2; + border.color: hifi.colors.lightGrayText; + color: hifi.colors.lightGray; + //x: originalSwitch.leftPadding + x: Math.max(0, Math.min(parent.width - width, originalSwitch.visualPosition * parent.width - (width / 2))) + y: parent.height / 2 - height / 2 + Behavior on x { + enabled: !originalSwitch.down + SmoothedAnimation { velocity: 200 } } - groove: Rectangle { - color: "#252525"; - implicitWidth: rootSwitch.switchWidth; - implicitHeight: rootSwitch.height; - radius: rootSwitch.switchRadius; - } - - handle: Rectangle { - id: switchHandle; - implicitWidth: rootSwitch.height - padding.top - padding.bottom; - implicitHeight: implicitWidth; - radius: implicitWidth/2; - border.color: hifi.colors.lightGrayText; - color: hifi.colors.lightGray; - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: parent.color = hifi.colors.blueHighlight; - onExited: parent.color = hifi.colors.lightGray; - } - } } } diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index 3c1d0fcd3c..ce4e1c376a 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import QtQuick.Controls 2.2 as QQC2 +import QtQuick.Controls 2.3 as QQC2 import "../styles-uit" diff --git a/interface/resources/qml/controls-uit/TabletHeader.qml b/interface/resources/qml/controls-uit/TabletHeader.qml index 17530f81ea..56203de286 100644 --- a/interface/resources/qml/controls-uit/TabletHeader.qml +++ b/interface/resources/qml/controls-uit/TabletHeader.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../styles-uit" diff --git a/interface/resources/qml/controls-uit/TextAction.qml b/interface/resources/qml/controls-uit/TextAction.qml index 1c66c3630a..1745a6c273 100644 --- a/interface/resources/qml/controls-uit/TextAction.qml +++ b/interface/resources/qml/controls-uit/TextAction.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "../styles-uit" import "../controls-uit" as HifiControls diff --git a/interface/resources/qml/controls-uit/TextEdit.qml b/interface/resources/qml/controls-uit/TextEdit.qml index 34eff7586a..a72a3b13d8 100644 --- a/interface/resources/qml/controls-uit/TextEdit.qml +++ b/interface/resources/qml/controls-uit/TextEdit.qml @@ -9,16 +9,14 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "../styles-uit" TextEdit { property real size: 32 - - FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; } - font.family: ralewaySemiBold.name + + font.family: "Raleway" + font.weight: Font.DemiBold font.pointSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index ee646b2575..3fcdf30d2d 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "../styles-uit" import "../controls-uit" as HifiControls @@ -29,14 +28,18 @@ TextField { property int roundedBorderRadius: 4 property bool error: false; property bool hasClearButton: false; + property alias textColor: textField.color property string leftPermanentGlyph: ""; property string centerPlaceholderGlyph: ""; placeholderText: textField.placeholderText - FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; } - FontLoader { id: hifiGlyphs; source: "qrc:/fonts/hifi-glyphs.ttf"; } - font.family: firaSansRegular.name + property bool rightAnchorSet: false; + anchors.onRightChanged: { + rightAnchorSet = true; + } + + font.family: "Fira Sans" font.pixelSize: hifi.fontSizes.textFieldInput height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered. property alias textFieldLabel: textFieldLabel @@ -46,42 +49,56 @@ TextField { // workaround for https://bugreports.qt.io/browse/QTBUG-49297 Keys.onPressed: { switch (event.key) { - case Qt.Key_Return: - case Qt.Key_Enter: - event.accepted = true; + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; - // emit accepted signal manually - if (acceptableInput) { - accepted(); - } + // emit accepted signal manually + if (acceptableInput) { + accepted(); + } } } - style: TextFieldStyle { - id: style; - textColor: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } + Text { + id: placeholder + x: textField.leftPadding + y: textField.topPadding + width: textField.width - (textField.leftPadding + textField.rightPadding) + height: textField.height - (textField.topPadding + textField.bottomPadding) + + text: textField.placeholderText + font: textField.font + color: textField.placeholderTextColor + verticalAlignment: textField.verticalAlignment + visible: !textField.length && !textField.preeditText && (!textField.activeFocus || textField.horizontalAlignment !== Qt.AlignHCenter) + elide: Text.ElideRight + } + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.black } else { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.lightGrayText - } + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText } } - background: Rectangle { - color: { + } + + background: Rectangle { + color: { if (isLightColorScheme) { if (textField.activeFocus) { hifi.colors.white @@ -102,22 +119,22 @@ TextField { } } } - border.color: textField.error ? hifi.colors.redHighlight : + border.color: textField.error ? hifi.colors.redHighlight : (textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color)) - border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 + border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0) - HiFiGlyphs { + HiFiGlyphs { text: textField.leftPermanentGlyph; - color: textColor; - size: hifi.fontSizes.textFieldSearchIcon; - anchors.left: parent.left; - anchors.verticalCenter: parent.verticalCenter; - anchors.leftMargin: hifi.dimensions.textPadding - 2; - visible: text; - } + color: textColor; + size: hifi.fontSizes.textFieldSearchIcon; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + anchors.leftMargin: hifi.dimensions.textPadding - 2; + visible: text; + } - HiFiGlyphs { + HiFiGlyphs { text: textField.centerPlaceholderGlyph; color: textColor; size: parent.height; @@ -127,44 +144,55 @@ TextField { } HiFiGlyphs { - text: hifi.glyphs.search - color: textColor - size: hifi.fontSizes.textFieldSearchIcon - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: hifi.dimensions.textPadding - 2 - visible: isSearchField - } + text: hifi.glyphs.search + color: textColor + size: hifi.fontSizes.textFieldSearchIcon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: hifi.dimensions.textPadding - 2 + visible: isSearchField + } - HiFiGlyphs { - text: hifi.glyphs.error - color: textColor - size: 40 - anchors.right: parent.right - anchors.rightMargin: hifi.dimensions.textPadding - 2 - anchors.verticalCenter: parent.verticalCenter - visible: hasClearButton && textField.text !== ""; + HiFiGlyphs { + text: hifi.glyphs.error + color: textColor + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: hasClearButton && textField.text !== ""; - MouseArea { - anchors.fill: parent; - onClicked: { - textField.text = ""; - } + MouseArea { + anchors.fill: parent; + onClicked: { + textField.text = ""; } } } - placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray - selectedTextColor: hifi.colors.black - selectionColor: hifi.colors.primaryHighlight - padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding - padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding } + property color placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + leftPadding: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding + rightPadding: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding + + HifiControls.Label { id: textFieldLabel text: textField.label colorScheme: textField.colorScheme anchors.left: parent.left + + Binding on anchors.right { + when: rightAnchorSet + value: textField.right + } + Binding on wrapMode { + when: rightAnchorSet + value: Text.WordWrap + } + anchors.bottom: parent.top anchors.bottomMargin: 3 visible: label != "" diff --git a/interface/resources/qml/controls-uit/WebGlyphButton.qml b/interface/resources/qml/controls-uit/WebGlyphButton.qml index 15524e4188..fd7cd001b2 100644 --- a/interface/resources/qml/controls-uit/WebGlyphButton.qml +++ b/interface/resources/qml/controls-uit/WebGlyphButton.qml @@ -9,8 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 as Original import "../styles-uit" @@ -26,23 +25,16 @@ Original.Button { readonly property color clickedColor: "#FFFFFF" readonly property color disabledColor: "#575757" - style: ButtonStyle { - background: Item {} + background: Item {} - - label: HiFiGlyphs { - color: control.enabled ? (control.pressed ? control.clickedColor : - (control.hovered ? control.hoverColor : control.normalColor)) : - control.disabledColor - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors { - // Tweak horizontal alignment so that it looks right. - left: parent.left - leftMargin: -0.5 - } - text: control.glyph - size: control.size - } + contentItem: HiFiGlyphs { + color: control.enabled ? (control.pressed ? control.clickedColor : + (control.hovered ? control.hoverColor : control.normalColor)) : + control.disabledColor + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.glyph + size: control.size } } + diff --git a/interface/resources/qml/controls/Button.qml b/interface/resources/qml/controls/Button.qml index c5a9f99343..6cbdec5644 100644 --- a/interface/resources/qml/controls/Button.qml +++ b/interface/resources/qml/controls/Button.qml @@ -1,27 +1,35 @@ import QtQuick 2.3 -import QtQuick.Controls 1.3 as Original -import QtQuick.Controls.Styles 1.3 +import QtQuick.Controls 2.2 as Original import "." import "../styles" +import "../controls-uit" Original.Button { - style: ButtonStyle { - HifiConstants { id: hifi } - padding { - top: 8 - left: 12 - right: 12 - bottom: 8 - } - background: Border { - anchors.fill: parent - } - label: Text { - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.text - color: control.enabled ? hifi.colors.text : hifi.colors.disabledText + id: control + + HifiConstants { id: hifi } + property Action action: null + + onActionChanged: { + if (action !== null && action.text !== "") { + control.text = action.text } } + + padding { + top: 8 + left: 12 + right: 12 + bottom: 8 + } + background: Border { + anchors.fill: parent + } + contentItem: Text { + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + color: control.enabled ? hifi.colors.text : hifi.colors.disabledText + } } diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index efc8519c1e..8e7db44b7d 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -122,9 +122,21 @@ Item { newViewRequestedCallback(request) } + // Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false + // as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns + // loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed + // when QT fixes this. + property bool safeLoading: false + property bool loadingLatched: false + property var loadingRequest: null onLoadingChanged: { - flick.onLoadingChanged(loadRequest) - loadingChangedCallback(loadRequest) + webViewCore.loadingRequest = loadRequest; + webViewCore.safeLoading = webViewCore.loading && !loadingLatched; + webViewCore.loadingLatched |= webViewCore.loading; + } + onSafeLoadingChanged: { + flick.onLoadingChanged(webViewCore.loadingRequest) + loadingChangedCallback(webViewCore.loadingRequest) } } @@ -133,7 +145,7 @@ Item { x: flick.width/2 - width/2 y: flick.height/2 - height/2 source: "../../icons/loader-snake-64-w.gif" - visible: webViewCore.loading && /^(http.*|)$/i.test(webViewCore.url.toString()) + visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString()) playing: visible z: 10000 } diff --git a/interface/resources/qml/controls/FontAwesome.qml b/interface/resources/qml/controls/FontAwesome.qml index 7eda46e17e..213a4b62f2 100644 --- a/interface/resources/qml/controls/FontAwesome.qml +++ b/interface/resources/qml/controls/FontAwesome.qml @@ -1,16 +1,13 @@ import QtQuick 2.3 -import QtQuick.Controls 1.3 -import QtQuick.Controls.Styles 1.3 Text { id: root - FontLoader { id: iconFont; source: "qrc:/fonts/fontawesome-webfont.ttf"; } property int size: 32 width: size height: size font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: iconFont.name + font.family: "FontAwesome" } diff --git a/interface/resources/qml/desktop/+android/Desktop.qml b/interface/resources/qml/desktop/+android/Desktop.qml new file mode 100644 index 0000000000..6a68f63d0a --- /dev/null +++ b/interface/resources/qml/desktop/+android/Desktop.qml @@ -0,0 +1,575 @@ +// +// Desktop.qml +// +// Created by Bradley Austin Davis on 15 Apr 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 + +import "../dialogs" +import "../js/Utils.js" as Utils + +// This is our primary 'desktop' object to which all VR dialogs and windows are childed. +FocusScope { + id: desktop + objectName: "desktop" + anchors.fill: parent + + readonly property int invalid_position: -9999; + property rect recommendedRect: Qt.rect(0,0,0,0); + property var expectedChildren; + property bool repositionLocked: true + property bool hmdHandMouseActive: false + + onRepositionLockedChanged: { + if (!repositionLocked) { + d.handleSizeChanged(); + } + + } + + onHeightChanged: d.handleSizeChanged(); + + onWidthChanged: d.handleSizeChanged(); + + // Controls and windows can trigger this signal to ensure the desktop becomes visible + // when they're opened. + signal showDesktop(); + + // This is for JS/QML communication, which is unused in the Desktop, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + + // Allows QML/JS to find the desktop through the parent chain + property bool desktopRoot: true + + // The VR version of the primary menu + property var rootMenu: Menu { + id: rootMenuId + objectName: "rootMenu" + + property var exclusionGroups: ({}); + property Component exclusiveGroupMaker: Component { + ExclusiveGroup { + } + } + + function addExclusionGroup(qmlAction, exclusionGroup) { + + var exclusionGroupId = exclusionGroup.toString(); + if(!exclusionGroups[exclusionGroupId]) { + exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId); + } + + qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId] + } + } + + // FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD + // because shaders are 4.2, and do not include #version declarations. + property bool gradientsSupported: Qt.platform.os != "osx" && !~GL.vendor.indexOf("ATI") + + readonly property alias zLevels: zLevels + QtObject { + id: zLevels; + readonly property real normal: 1 // make windows always appear higher than QML overlays and other non-window controls. + readonly property real top: 2000 + readonly property real modal: 4000 + readonly property real menu: 8000 + } + + QtObject { + id: d + + function handleSizeChanged() { + if (desktop.repositionLocked) { + return; + } + var oldRecommendedRect = recommendedRect; + var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); + var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, + newRecommendedRectJS.width, + newRecommendedRectJS.height); + + var oldChildren = expectedChildren; + var newChildren = d.getRepositionChildren(); + if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1) + && (oldRecommendedRect != newRecommendedRect + || oldChildren != newChildren) + ) { + expectedChildren = newChildren; + d.repositionAll(); + } + recommendedRect = newRecommendedRect; + } + + function findChild(item, name) { + for (var i = 0; i < item.children.length; ++i) { + if (item.children[i].objectName === name) { + return item.children[i]; + } + } + return null; + } + + function findParentMatching(item, predicate) { + while (item) { + if (predicate(item)) { + break; + } + item = item.parent; + } + return item; + } + + function findMatchingChildren(item, predicate) { + var results = []; + for (var i in item.children) { + var child = item.children[i]; + if (predicate(child)) { + results.push(child); + } + } + return results; + } + + function isTopLevelWindow(item) { + return item.topLevelWindow; + } + + function isAlwaysOnTopWindow(window) { + return window.alwaysOnTop; + } + + function isModalWindow(window) { + return window.modality !== Qt.NonModal; + } + + function getTopLevelWindows(predicate) { + return findMatchingChildren(desktop, function(child) { + return (isTopLevelWindow(child) && (!predicate || predicate(child))); + }); + } + + function getDesktopWindow(item) { + return findParentMatching(item, isTopLevelWindow) + } + + function fixupZOrder(windows, basis, topWindow) { + windows.sort(function(a, b){ return a.z - b.z; }); + + if ((topWindow.z >= basis) && (windows[windows.length - 1] === topWindow)) { + return; + } + + var lastZ = -1; + var lastTargetZ = basis - 1; + for (var i = 0; i < windows.length; ++i) { + var window = windows[i]; + if (!window.visible) { + continue + } + + if (topWindow && (topWindow === window)) { + continue + } + + if (window.z > lastZ) { + lastZ = window.z; + ++lastTargetZ; + } + if (DebugQML) { + console.log("Assigning z order " + lastTargetZ + " to " + window) + } + + window.z = lastTargetZ; + } + if (topWindow) { + ++lastTargetZ; + if (DebugQML) { + console.log("Assigning z order " + lastTargetZ + " to " + topWindow) + } + topWindow.z = lastTargetZ; + } + + return lastTargetZ; + } + + function raiseWindow(targetWindow) { + var predicate; + var zBasis; + if (isModalWindow(targetWindow)) { + predicate = isModalWindow; + zBasis = zLevels.modal + } else if (isAlwaysOnTopWindow(targetWindow)) { + predicate = function(window) { + return (isAlwaysOnTopWindow(window) && !isModalWindow(window)); + } + zBasis = zLevels.top + } else { + predicate = function(window) { + return (!isAlwaysOnTopWindow(window) && !isModalWindow(window)); + } + zBasis = zLevels.normal + } + + var windows = getTopLevelWindows(predicate); + fixupZOrder(windows, zBasis, targetWindow); + } + + Component.onCompleted: { + //offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged); + focusHack.start(); + } + + function onWindowFocusChanged() { + //console.log("Focus item is " + offscreenWindow.activeFocusItem); + + // FIXME this needs more testing before it can go into production + // and I already cant produce any way to have a modal dialog lose focus + // to a non-modal one. + /* + var focusedWindow = getDesktopWindow(offscreenWindow.activeFocusItem); + + if (isModalWindow(focusedWindow)) { + return; + } + + // new focused window is not modal... check if there are any modal windows + var windows = getTopLevelWindows(isModalWindow); + if (0 === windows.length) { + return; + } + + // There are modal windows present, force focus back to the top-most modal window + windows.sort(function(a, b){ return a.z - b.z; }); + windows[windows.length - 1].focus = true; + */ + +// var focusedItem = offscreenWindow.activeFocusItem ; +// if (DebugQML && focusedItem) { +// var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height); +// focusDebugger.x = rect.x; +// focusDebugger.y = rect.y; +// focusDebugger.width = rect.width +// focusDebugger.height = rect.height +// } + } + + function getRepositionChildren(predicate) { + return findMatchingChildren(desktop, function(child) { + return (child.shouldReposition === true && (!predicate || predicate(child))); + }); + } + + function repositionAll() { + if (desktop.repositionLocked) { + return; + } + + var oldRecommendedRect = recommendedRect; + var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; + var newRecommendedRect = Controller.getRecommendedHUDRect(); + var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; + var windows = d.getTopLevelWindows(); + for (var i = 0; i < windows.length; ++i) { + var targetWindow = windows[i]; + if (targetWindow.visible) { + repositionWindow(targetWindow, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); + } + } + + // also reposition the other children that aren't top level windows but want to be repositioned + var otherChildren = d.getRepositionChildren(); + for (var i = 0; i < otherChildren.length; ++i) { + var child = otherChildren[i]; + repositionWindow(child, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); + } + + } + } + + property bool pinned: false + property var hiddenChildren: [] + + function togglePinned() { + pinned = !pinned + } + + function isPointOnWindow(point) { + for (var i = 0; i < desktop.visibleChildren.length; i++) { + var child = desktop.visibleChildren[i]; + if (child.hasOwnProperty("modality")) { + var mappedPoint = mapToItem(child, point.x, point.y); + if (child.hasOwnProperty("frame")) { + var outLine = child.frame.children[2]; + var framePoint = outLine.mapFromGlobal(point.x, point.y); + if (outLine.contains(framePoint)) { + return true; + } + } + + if (child.contains(mappedPoint)) { + return true; + } + } + } + return false; + } + + function setPinned(newPinned) { + pinned = newPinned + } + + property real unpinnedAlpha: 1.0; + + Behavior on unpinnedAlpha { + NumberAnimation { + easing.type: Easing.Linear; + duration: 300 + } + } + + state: "NORMAL" + states: [ + State { + name: "NORMAL" + PropertyChanges { target: desktop; unpinnedAlpha: 1.0 } + }, + State { + name: "PINNED" + PropertyChanges { target: desktop; unpinnedAlpha: 0.0 } + } + ] + + transitions: [ + Transition { + NumberAnimation { properties: "unpinnedAlpha"; duration: 300 } + } + ] + + onPinnedChanged: { + if (pinned) { + d.raiseWindow(desktop); + desktop.focus = true; + desktop.forceActiveFocus(); + + // recalculate our non-pinned children + hiddenChildren = d.findMatchingChildren(desktop, function(child){ + return !d.isTopLevelWindow(child) && child.visible && !child.pinned; + }); + + hiddenChildren.forEach(function(child){ + child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha }); + }); + } + state = pinned ? "PINNED" : "NORMAL" + } + + onShowDesktop: pinned = false + + function raise(item) { + var targetWindow = d.getDesktopWindow(item); + if (!targetWindow) { + console.warn("Could not find top level window for " + item); + return; + } + + // Fix up the Z-order (takes into account if this is a modal window) + d.raiseWindow(targetWindow); + var setFocus = true; + if (!d.isModalWindow(targetWindow)) { + var modalWindows = d.getTopLevelWindows(d.isModalWindow); + if (modalWindows.length) { + setFocus = false; + } + } + + if (setFocus) { + targetWindow.focus = true; + } + + showDesktop(); + } + + function ensureTitleBarVisible(targetWindow) { + // Reposition window to ensure that title bar is vertically inside window. + if (targetWindow.frame && targetWindow.frame.decoration) { + var topMargin = -targetWindow.frame.decoration.anchors.topMargin; // Frame's topMargin is a negative value. + targetWindow.y = Math.max(targetWindow.y, topMargin); + } + } + + function centerOnVisible(item) { + var targetWindow = d.getDesktopWindow(item); + if (!targetWindow) { + console.warn("Could not find top level window for " + item); + return; + } + + if (typeof Controller === "undefined") { + console.warn("Controller not yet available... can't center"); + return; + } + + var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); + var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, + newRecommendedRectJS.width, + newRecommendedRectJS.height); + var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; + var newX = newRecommendedRect.x + ((newRecommendedRect.width - targetWindow.width) / 2); + var newY = newRecommendedRect.y + ((newRecommendedRect.height - targetWindow.height) / 2); + targetWindow.x = newX; + targetWindow.y = newY; + + ensureTitleBarVisible(targetWindow); + + // If we've noticed that our recommended desktop rect has changed, record that change here. + if (recommendedRect != newRecommendedRect) { + recommendedRect = newRecommendedRect; + } + } + + function repositionOnVisible(item) { + var targetWindow = d.getDesktopWindow(item); + if (!targetWindow) { + console.warn("Could not find top level window for " + item); + return; + } + + if (typeof Controller === "undefined") { + console.warn("Controller not yet available... can't reposition targetWindow:" + targetWindow); + return; + } + + var oldRecommendedRect = recommendedRect; + var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; + var newRecommendedRect = Controller.getRecommendedHUDRect(); + var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; + repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); + } + + function repositionWindow(targetWindow, forceReposition, + oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions) { + + if (desktop.width === 0 || desktop.height === 0) { + return; + } + + if (!targetWindow) { + console.warn("Could not find top level window for " + item); + return; + } + + var recommended = Controller.getRecommendedHUDRect(); + var maxX = recommended.x + recommended.width; + var maxY = recommended.y + recommended.height; + var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); + + // if we asked to force reposition, or if the window is completely outside of the recommended rectangle, reposition it + if (forceReposition || (targetWindow.x > maxX || (targetWindow.x + targetWindow.width) < recommended.x) || + (targetWindow.y > maxY || (targetWindow.y + targetWindow.height) < recommended.y)) { + newPosition.x = -1 + newPosition.y = -1 + } + + if (newPosition.x === -1 && newPosition.y === -1) { + var originRelativeX = (targetWindow.x - oldRecommendedRect.x); + var originRelativeY = (targetWindow.y - oldRecommendedRect.y); + if (isNaN(originRelativeX)) { + originRelativeX = 0; + } + if (isNaN(originRelativeY)) { + originRelativeY = 0; + } + var fractionX = Utils.clamp(originRelativeX / oldRecommendedDimmensions.x, 0, 1); + var fractionY = Utils.clamp(originRelativeY / oldRecommendedDimmensions.y, 0, 1); + var newX = (fractionX * newRecommendedDimmensions.x) + newRecommendedRect.x; + var newY = (fractionY * newRecommendedDimmensions.y) + newRecommendedRect.y; + newPosition = Qt.vector2d(newX, newY); + } + targetWindow.x = newPosition.x; + targetWindow.y = newPosition.y; + + ensureTitleBarVisible(targetWindow); + } + + Component { id: messageDialogBuilder; MessageDialog { } } + function messageBox(properties) { + return messageDialogBuilder.createObject(desktop, properties); + } + + Component { id: inputDialogBuilder; QueryDialog { } } + function inputDialog(properties) { + return inputDialogBuilder.createObject(desktop, properties); + } + + Component { id: customInputDialogBuilder; CustomQueryDialog { } } + function customInputDialog(properties) { + return customInputDialogBuilder.createObject(desktop, properties); + } + + Component { id: fileDialogBuilder; FileDialog { } } + function fileDialog(properties) { + return fileDialogBuilder.createObject(desktop, properties); + } + + Component { id: assetDialogBuilder; AssetDialog { } } + function assetDialog(properties) { + return assetDialogBuilder.createObject(desktop, properties); + } + + function unfocusWindows() { + // First find the active focus item, and unfocus it, all the way + // up the parent chain to the window + var currentFocus = offscreenWindow.activeFocusItem; + var targetWindow = d.getDesktopWindow(currentFocus); + while (currentFocus) { + if (currentFocus === targetWindow) { + break; + } + currentFocus.focus = false; + currentFocus = currentFocus.parent; + } + + // Unfocus all windows + var windows = d.getTopLevelWindows(); + for (var i = 0; i < windows.length; ++i) { + windows[i].focus = false; + } + + // For the desktop to have active focus + desktop.focus = true; + desktop.forceActiveFocus(); + } + + function openBrowserWindow(request, profile) { + var component = Qt.createComponent("../Browser.qml"); + var newWindow = component.createObject(desktop); + newWindow.webView.profile = profile; + request.openIn(newWindow.webView); + } + + FocusHack { id: focusHack; } + + Rectangle { + id: focusDebugger; + objectName: "focusDebugger" + z: 9999; visible: false; color: "red" + ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 } + } + + Action { + text: "Toggle Focus Debugger" + shortcut: "Ctrl+Shift+F" + enabled: DebugQML + onTriggered: focusDebugger.visible = !focusDebugger.visible + } + +} diff --git a/interface/resources/qml/desktop/+android/FocusHack.qml b/interface/resources/qml/desktop/+android/FocusHack.qml new file mode 100644 index 0000000000..38253fdec1 --- /dev/null +++ b/interface/resources/qml/desktop/+android/FocusHack.qml @@ -0,0 +1,26 @@ +// +// FocusHack.qml +// +// Created by Bradley Austin Davis on 21 Jan 2015 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +FocusScope { + id: root + objectName: "FocusHack" + + TextInput { + id: textInput; + focus: true + width: 10; height: 10 + onActiveFocusChanged: root.destroy() + } + + function start() { + } +} diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index e7c68b2a47..5f924834d9 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -8,8 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 as QQC2 import "../dialogs" import "../js/Utils.js" as Utils @@ -323,6 +324,18 @@ FocusScope { return false; } + function hideDesktopWindows() { + for (var index = 0; index < desktop.visibleChildren.length; index++) { + var child = desktop.visibleChildren[index]; + if (child.topLevelWindow && child.hasOwnProperty("modality")) { + var TOOLBAR_NAME = "com.highfidelity.interface.toolbar.system" + if (child.objectName !== TOOLBAR_NAME) { + child.setShown(false); + } + } + } + } + function setPinned(newPinned) { pinned = newPinned } @@ -565,7 +578,7 @@ FocusScope { ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 } } - Action { + QQC2.Action { text: "Toggle Focus Debugger" shortcut: "Ctrl+Shift+F" enabled: DebugQML diff --git a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml b/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml new file mode 100644 index 0000000000..b1b6de4644 --- /dev/null +++ b/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml @@ -0,0 +1,338 @@ +// +// CustomQueryDialog.qml +// +// Created by Zander Otavka on 7/14/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7; +import QtQuick.Dialogs 1.2 as OriginalDialogs; +import QtQuick.Controls 1.4; + +import "../controls-uit"; +import "../styles-uit"; +import "../windows"; + +ModalWindow { + id: root; + HifiConstants { id: hifi; } + implicitWidth: 640; + implicitHeight: 320; + visible: true; + keyboardOverride: true // Disable ModalWindow's keyboard. + + signal selected(var result); + signal canceled(); + + property int icon: hifi.icons.none; + property string iconText: ""; + property int iconSize: 35; + onIconChanged: updateIcon(); + + property var textInput; + property var comboBox; + property var checkBox; + onTextInputChanged: { + if (textInput && textInput.text !== undefined) { + textField.text = textInput.text; + } + } + onComboBoxChanged: { + if (comboBox && comboBox.index !== undefined) { + comboBoxField.currentIndex = comboBox.index; + } + } + onCheckBoxChanged: { + if (checkBox && checkBox.checked !== undefined) { + checkBoxField.checked = checkBox.checked; + } + } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + onKeyboardRaisedChanged: d.resize(); + + property var warning: ""; + property var result; + + property var implicitCheckState: null; + + property int titleWidth: 0; + onTitleWidthChanged: d.resize(); + + function updateIcon() { + if (!root) { + return; + } + iconText = hifi.glyphForIcon(root.icon); + } + + function updateCheckbox() { + if (checkBox.disableForItems) { + var currentItemInDisableList = false; + for (var i in checkBox.disableForItems) { + if (comboBoxField.currentIndex === checkBox.disableForItems[i]) { + currentItemInDisableList = true; + break; + } + } + + if (currentItemInDisableList) { + checkBoxField.enabled = false; + if (checkBox.checkStateOnDisable !== null && checkBox.checkStateOnDisable !== undefined) { + root.implicitCheckState = checkBoxField.checked; + checkBoxField.checked = checkBox.checkStateOnDisable; + } + root.warning = checkBox.warningOnDisable; + } else { + checkBoxField.enabled = true; + if (root.implicitCheckState !== null) { + checkBoxField.checked = root.implicitCheckState; + root.implicitCheckState = null; + } + root.warning = ""; + } + } + } + + Item { + clip: true; + width: pane.width; + height: pane.height; + anchors.margins: 0; + + QtObject { + id: d; + readonly property int minWidth: 480 + readonly property int maxWdith: 1280 + readonly property int minHeight: 120 + readonly property int maxHeight: 720 + + function resize() { + var targetWidth = Math.max(titleWidth, pane.width); + var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) + + (extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) + + (buttons.height + 3 * hifi.dimensions.contentSpacing.y) + + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + hifi.dimensions.contentSpacing.y) : 0); + + root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); + root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? + d.maxHeight : targetHeight); + } + } + + Item { + anchors { + top: parent.top; + bottom: extraInputs.visible ? extraInputs.top : buttons.top; + left: parent.left; + right: parent.right; + margins: 0; + } + + // FIXME make a text field type that can be bound to a history for autocompletion + TextField { + id: textField; + label: root.textInput.label; + focus: root.textInput ? true : false; + visible: root.textInput ? true : false; + anchors { + left: parent.left; + right: parent.right; + bottom: keyboard.top; + bottomMargin: hifi.dimensions.contentSpacing.y; + } + } + + Keyboard { + id: keyboard + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + bottomMargin: raised ? hifi.dimensions.contentSpacing.y : 0 + } + } + } + + Item { + id: extraInputs; + visible: Boolean(root.checkBox || root.comboBox); + anchors { + left: parent.left; + right: parent.right; + bottom: buttons.top; + bottomMargin: hifi.dimensions.contentSpacing.y; + } + height: comboBoxField.controlHeight; + onHeightChanged: d.resize(); + onWidthChanged: d.resize(); + + CheckBox { + id: checkBoxField; + text: root.checkBox.label; + focus: Boolean(root.checkBox); + visible: Boolean(root.checkBox); + anchors { + left: parent.left; + bottom: parent.bottom; + leftMargin: 6; // Magic number to align with warning icon + bottomMargin: 6; + } + } + + ComboBox { + id: comboBoxField; + label: root.comboBox.label; + focus: Boolean(root.comboBox); + visible: Boolean(root.comboBox); + Binding on x { + when: comboBoxField.visible + value: !checkBoxField.visible ? buttons.x : acceptButton.x + } + + Binding on width { + when: comboBoxField.visible + value: !checkBoxField.visible ? buttons.width : buttons.width - acceptButton.x + } + anchors { + right: parent.right; + bottom: parent.bottom; + } + model: root.comboBox ? root.comboBox.items : []; + onAccepted: { + updateCheckbox(); + focus = true; + } + } + } + + Row { + id: buttons; + focus: true; + spacing: hifi.dimensions.contentSpacing.x; + layoutDirection: Qt.RightToLeft; + onHeightChanged: d.resize(); + onWidthChanged: { + d.resize(); + resizeWarningText(); + } + + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + bottomMargin: hifi.dimensions.contentSpacing.y; + } + + function resizeWarningText() { + var rowWidth = buttons.width; + var buttonsWidth = acceptButton.width + cancelButton.width + hifi.dimensions.contentSpacing.x * 2; + var warningIconWidth = warningIcon.width + hifi.dimensions.contentSpacing.x; + warningText.width = rowWidth - buttonsWidth - warningIconWidth; + } + + Button { + id: cancelButton; + action: cancelAction; + } + + Button { + id: acceptButton; + action: acceptAction; + } + + Text { + id: warningText; + visible: Boolean(root.warning); + text: root.warning; + wrapMode: Text.WordWrap; + font.italic: true; + maximumLineCount: 2; + } + + HiFiGlyphs { + id: warningIcon; + visible: Boolean(root.warning); + text: hifi.glyphs.alert; + size: hifi.dimensions.controlLineHeight; + width: 20 // Line up with checkbox. + } + } + + Action { + id: cancelAction; + text: qsTr("Cancel"); + shortcut: "Esc"; + onTriggered: { + root.result = null; + root.canceled(); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; + } + } + + Action { + id: acceptAction; + text: qsTr("Add"); + shortcut: "Return" + onTriggered: { + var result = {}; + if (textInput) { + result.textInput = textField.text; + } + if (comboBox) { + result.comboBox = comboBoxField.currentIndex; + result.comboBoxText = comboBoxField.currentText; + } + if (checkBox) { + result.checkBox = checkBoxField.enabled ? checkBoxField.checked : null; + } + root.result = JSON.stringify(result); + root.selected(root.result); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; + } + } + } + + Keys.onPressed: { + if (!visible) { + return; + } + + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + cancelAction.trigger(); + event.accepted = true; + break; + + case Qt.Key_Return: + case Qt.Key_Enter: + acceptAction.trigger(); + event.accepted = true; + break; + } + } + + Component.onCompleted: { + keyboardEnabled = HMD.active; + updateIcon(); + updateCheckbox(); + d.resize(); + textField.forceActiveFocus(); + } +} diff --git a/interface/resources/qml/dialogs/+android/FileDialog.qml b/interface/resources/qml/dialogs/+android/FileDialog.qml new file mode 100644 index 0000000000..548ab453a7 --- /dev/null +++ b/interface/resources/qml/dialogs/+android/FileDialog.qml @@ -0,0 +1,840 @@ +// +// FileDialog.qml +// +// Created by Bradley Austin Davis on 14 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import Qt.labs.folderlistmodel 2.1 +import Qt.labs.settings 1.0 +import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Controls 1.4 + +import ".." +import "../controls-uit" +import "../styles-uit" +import "../windows" + +import "fileDialog" + +//FIXME implement shortcuts for favorite location +ModalWindow { + id: root + resizable: true + implicitWidth: 480 + implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0) + + minSize: Qt.vector2d(360, 240) + draggable: true + + HifiConstants { id: hifi } + + property var filesModel: ListModel { } + + Settings { + category: "FileDialog" + property alias width: root.width + property alias height: root.height + property alias x: root.x + property alias y: root.y + } + + + // Set from OffscreenUi::getOpenFile() + property alias caption: root.title; + // Set from OffscreenUi::getOpenFile() + property alias dir: fileTableModel.folder; + // Set from OffscreenUi::getOpenFile() + property alias filter: selectionType.filtersString; + // Set from OffscreenUi::getOpenFile() + property int options; // <-- FIXME unused + + property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : "" + property int iconSize: 40 + + property bool selectDirectory: false; + property bool showHidden: false; + // FIXME implement + property bool multiSelect: false; + property bool saveDialog: false; + property var helper: fileDialogHelper + property alias model: fileTableView.model + property var drives: helper.drives() + + property int titleWidth: 0 + + signal selectedFile(var file); + signal canceled(); + signal selected(int button); + function click(button) { + clickedButton = button; + selected(button); + destroy(); + } + + property int clickedButton: OriginalDialogs.StandardButton.NoButton; + + Component.onCompleted: { + console.log("Helper " + helper + " drives " + drives); + + fileDialogItem.keyboardEnabled = HMD.active; + + // HACK: The following lines force the model to initialize properly such that the go-up button + // works properly from the initial screen. + var initialFolder = folderListModel.folder; + fileTableModel.folder = helper.pathToUrl(drives[0]); + fileTableModel.folder = initialFolder; + + iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; + + // Clear selection when click on external frame. + frameClicked.connect(function() { d.clearSelection(); }); + + if (selectDirectory) { + currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder)); + d.currentSelectionIsFolder = true; + d.currentSelectionUrl = initialFolder; + } + + helper.contentsChanged.connect(function() { + if (folderListModel) { + // Make folderListModel refresh. + var save = folderListModel.folder; + folderListModel.folder = ""; + folderListModel.folder = save; + } + }); + + focusTimer.start(); + } + + Timer { + id: focusTimer + interval: 10 + running: false + repeat: false + onTriggered: { + fileTableView.contentItem.forceActiveFocus(); + } + } + + Item { + id: fileDialogItem + clip: true + width: pane.width + height: pane.height + anchors.margins: 0 + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + MouseArea { + // Clear selection when click on internal unused area. + anchors.fill: parent + drag.target: root + onClicked: { + d.clearSelection(); + // Defocus text field so that the keyboard gets hidden. + // Clicking also breaks keyboard navigation apart from backtabbing to cancel + frame.forceActiveFocus(); + } + } + + Row { + id: navControls + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: parent.left + } + spacing: hifi.dimensions.contentSpacing.x + + GlyphButton { + id: upButton + glyph: hifi.glyphs.levelUp + width: height + size: 30 + enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" + onClicked: d.navigateUp(); + Keys.onReturnPressed: { d.navigateUp(); } + KeyNavigation.tab: homeButton + KeyNavigation.backtab: upButton + KeyNavigation.left: upButton + KeyNavigation.right: homeButton + } + + GlyphButton { + id: homeButton + property var destination: helper.home(); + glyph: hifi.glyphs.home + size: 28 + width: height + enabled: d.homeDestination ? true : false + onClicked: d.navigateHome(); + Keys.onReturnPressed: { d.navigateHome(); } + KeyNavigation.tab: fileTableView.contentItem + KeyNavigation.backtab: upButton + KeyNavigation.left: upButton + } + } + + ComboBox { + id: pathSelector + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: navControls.right + leftMargin: hifi.dimensions.contentSpacing.x + right: parent.right + } + + property var lastValidFolder: helper.urlToPath(fileTableModel.folder) + + function calculatePathChoices(folder) { + var folders = folder.split("/"), + choices = [], + i, length; + + if (folders[folders.length - 1] === "") { + folders.pop(); + } + + choices.push(folders[0]); + + for (i = 1, length = folders.length; i < length; i++) { + choices.push(choices[i - 1] + "/" + folders[i]); + } + + if (folders[0] === "") { + // Special handling for OSX root dir. + choices[0] = "/"; + } + + choices.reverse(); + + if (drives && drives.length > 1) { + choices.push("This PC"); + } + + if (choices.length > 0) { + pathSelector.model = choices; + } + } + + onLastValidFolderChanged: { + var folder = d.capitalizeDrive(lastValidFolder); + calculatePathChoices(folder); + } + + onCurrentTextChanged: { + var folder = currentText; + + if (/^[a-zA-z]:$/.test(folder)) { + folder = "file:///" + folder + "/"; + } else if (folder === "This PC") { + folder = "file:///"; + } else { + folder = helper.pathToUrl(folder); + } + + if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) { + if (root.selectDirectory) { + currentSelection.text = currentText !== "This PC" ? currentText : ""; + d.currentSelectionUrl = helper.pathToUrl(currentText); + } + fileTableModel.folder = folder; + } + } + + KeyNavigation.up: fileTableView.contentItem + KeyNavigation.down: fileTableView.contentItem + KeyNavigation.tab: fileTableView.contentItem + KeyNavigation.backtab: fileTableView.contentItem + KeyNavigation.left: fileTableView.contentItem + KeyNavigation.right: fileTableView.contentItem + } + + QtObject { + id: d + property var currentSelectionUrl; + readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl); + property bool currentSelectionIsFolder; + property var backStack: [] + property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); } + property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); } + property var homeDestination: helper.home(); + + function capitalizeDrive(path) { + // Consistently capitalize drive letter for Windows. + if (/[a-zA-Z]:/.test(path)) { + return path.charAt(0).toUpperCase() + path.slice(1); + } + return path; + } + + function update() { + var row = fileTableView.currentRow; + + if (row === -1) { + if (!root.selectDirectory) { + currentSelection.text = ""; + currentSelectionIsFolder = false; + } + return; + } + + currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); + currentSelectionIsFolder = fileTableView.model !== filesModel ? + fileTableView.model.isFolder(row) : + fileTableModel.isFolder(row); + if (root.selectDirectory || !currentSelectionIsFolder) { + currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); + } else { + currentSelection.text = ""; + } + } + + function navigateUp() { + if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") { + fileTableModel.folder = fileTableModel.parentFolder; + return true; + } + } + + function navigateHome() { + fileTableModel.folder = homeDestination; + return true; + } + + function clearSelection() { + fileTableView.selection.clear(); + fileTableView.currentRow = -1; + update(); + } + } + + FolderListModel { + id: folderListModel + nameFilters: selectionType.currentFilter + showDirsFirst: true + showDotAndDotDot: false + showFiles: !root.selectDirectory + Component.onCompleted: { + showFiles = !root.selectDirectory + } + + onFolderChanged: { + fileTableModel.update(); // Update once the data from the folder change is available. + } + + function getItem(index, field) { + return get(index, field); + } + } + + ListModel { + // Emulates FolderListModel but contains drive data. + id: driveListModel + + property int count: 1 + + Component.onCompleted: initialize(); + + function initialize() { + var drive, + i; + + count = drives.length; + + for (i = 0; i < count; i++) { + drive = drives[i].slice(0, -1); // Remove trailing "/". + append({ + fileName: drive, + fileModified: new Date(0), + fileSize: 0, + filePath: drive + "/", + fileIsDir: true, + fileNameSort: drive.toLowerCase() + }); + } + } + + function getItem(index, field) { + return get(index)[field]; + } + } + + Component { + id: filesModelBuilder + ListModel { } + } + + QtObject { + id: fileTableModel + + // FolderListModel has a couple of problems: + // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757 + // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901 + // + // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with + // drive information when viewing at the computer level. + + property var folder + property int sortOrder: Qt.AscendingOrder + property int sortColumn: 0 + property var model: folderListModel + property string parentFolder: calculateParentFolder(); + + readonly property string rootFolder: "file:///" + + function calculateParentFolder() { + if (model === folderListModel) { + if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) { + return rootFolder; + } else { + return folderListModel.parentFolder; + } + } else { + return ""; + } + } + + onFolderChanged: { + if (folder === rootFolder) { + model = driveListModel; + helper.monitorDirectory(""); + update(); + } else { + var needsUpdate = model === driveListModel && folder === folderListModel.folder; + + model = folderListModel; + folderListModel.folder = folder; + helper.monitorDirectory(helper.urlToPath(folder)); + + if (needsUpdate) { + update(); + } + } + } + + function isFolder(row) { + if (row === -1) { + return false; + } + return filesModel.get(row).fileIsDir; + } + + function get(row) { + return filesModel.get(row) + } + + function update() { + var dataFields = ["fileName", "fileModified", "fileSize"], + sortFields = ["fileNameSort", "fileModified", "fileSize"], + dataField = dataFields[sortColumn], + sortField = sortFields[sortColumn], + sortValue, + fileName, + fileIsDir, + comparisonFunction, + lower, + middle, + upper, + rows = 0, + i; + + var newFilesModel = filesModelBuilder.createObject(root); + + comparisonFunction = sortOrder === Qt.AscendingOrder + ? function(a, b) { return a < b; } + : function(a, b) { return a > b; } + + for (i = 0; i < model.count; i++) { + fileName = model.getItem(i, "fileName"); + fileIsDir = model.getItem(i, "fileIsDir"); + + sortValue = model.getItem(i, dataField); + if (dataField === "fileName") { + // Directories first by prefixing a "*". + // Case-insensitive. + sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase(); + } + + lower = 0; + upper = rows; + while (lower < upper) { + middle = Math.floor((lower + upper) / 2); + var lessThan; + if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) { + lessThan = true; + upper = middle; + } else { + lessThan = false; + lower = middle + 1; + } + } + + newFilesModel.insert(lower, { + fileName: fileName, + fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), + fileSize: model.getItem(i, "fileSize"), + filePath: model.getItem(i, "filePath"), + fileIsDir: fileIsDir, + fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase() + }); + + rows++; + } + filesModel = newFilesModel; + + d.clearSelection(); + } + } + + Table { + id: fileTableView + colorScheme: hifi.colorSchemes.light + anchors { + top: navControls.bottom + topMargin: hifi.dimensions.contentSpacing.y + left: parent.left + right: parent.right + bottom: currentSelection.top + bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height + } + headerVisible: !selectDirectory + onDoubleClicked: navigateToRow(row); + Keys.onReturnPressed: navigateToCurrentRow(); + Keys.onEnterPressed: navigateToCurrentRow(); + + sortIndicatorColumn: 0 + sortIndicatorOrder: Qt.AscendingOrder + sortIndicatorVisible: true + + model: filesModel + + function updateSort() { + fileTableModel.sortOrder = sortIndicatorOrder; + fileTableModel.sortColumn = sortIndicatorColumn; + fileTableModel.update(); + } + + onSortIndicatorColumnChanged: { updateSort(); } + + onSortIndicatorOrderChanged: { updateSort(); } + + itemDelegate: Item { + clip: true + + FiraSansSemiBold { + text: getText(); + elide: styleData.elideMode + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.tableText + color: hifi.colors.baseGrayHighlight + font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) + ? "Fira Sans SemiBold" : "Fira Sans" + + function getText() { + if (styleData.row === -1) { + return styleData.value; + } + + switch (styleData.column) { + case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value; + case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value); + default: return styleData.value; + } + } + function formatSize(size) { + var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; + var suffixIndex = 0 + while ((size / 1024.0) > 1.1) { + size /= 1024.0; + ++suffixIndex; + } + + size = Math.round(size*1000)/1000; + size = size.toLocaleString() + + return size + " " + suffixes[suffixIndex]; + } + } + } + + TableViewColumn { + id: fileNameColumn + role: "fileName" + title: "Name" + width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width + movable: false + resizable: true + } + TableViewColumn { + id: fileModifiedColumn + role: "fileModified" + title: "Date" + width: 0.3 * fileTableView.width + movable: false + resizable: true + visible: !selectDirectory + } + TableViewColumn { + role: "fileSize" + title: "Size" + width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width + movable: false + resizable: true + visible: !selectDirectory + } + + function navigateToRow(row) { + currentRow = row; + navigateToCurrentRow(); + } + + function navigateToCurrentRow() { + var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel + var row = fileTableView.currentRow + var isFolder = currentModel.isFolder(row); + var file = currentModel.get(row).filePath; + if (isFolder) { + currentModel.folder = helper.pathToUrl(file); + } else { + okAction.trigger(); + } + } + + property string prefix: "" + + function addToPrefix(event) { + if (!event.text || event.text === "") { + return false; + } + var newPrefix = prefix + event.text.toLowerCase(); + var matchedIndex = -1; + for (var i = 0; i < model.count; ++i) { + var name = model !== filesModel ? model.get(i).fileName.toLowerCase() : + filesModel.get(i).fileName.toLowerCase(); + if (0 === name.indexOf(newPrefix)) { + matchedIndex = i; + break; + } + } + + if (matchedIndex !== -1) { + fileTableView.selection.clear(); + fileTableView.selection.select(matchedIndex); + fileTableView.currentRow = matchedIndex; + fileTableView.prefix = newPrefix; + } + prefixClearTimer.restart(); + return true; + } + + Timer { + id: prefixClearTimer + interval: 1000 + repeat: false + running: false + onTriggered: fileTableView.prefix = ""; + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + case Qt.Key_Tab: + case Qt.Key_Backtab: + event.accepted = false; + break; + case Qt.Key_Escape: + event.accepted = true; + root.click(OriginalDialogs.StandardButton.Cancel); + break; + default: + if (addToPrefix(event)) { + event.accepted = true + } else { + event.accepted = false; + } + break; + } + } + + KeyNavigation.tab: root.saveDialog ? currentSelection : openButton + } + + TextField { + id: currentSelection + label: selectDirectory ? "Directory:" : "File name:" + anchors { + left: parent.left + right: selectionType.visible ? selectionType.left: parent.right + rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 + bottom: keyboard.top + bottomMargin: hifi.dimensions.contentSpacing.y + } + readOnly: !root.saveDialog + activeFocusOnTab: !readOnly + onActiveFocusChanged: if (activeFocus) { selectAll(); } + onAccepted: okAction.trigger(); + KeyNavigation.up: fileTableView.contentItem + KeyNavigation.down: openButton + KeyNavigation.tab: openButton + KeyNavigation.backtab: fileTableView.contentItem + } + + FileTypeSelection { + id: selectionType + anchors { + top: currentSelection.top + left: buttonRow.left + right: parent.right + } + visible: !selectDirectory && filtersCount > 1 + } + + Keyboard { + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttonRow.top + bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0 + } + } + + Row { + id: buttonRow + anchors { + right: parent.right + bottom: parent.bottom + } + spacing: hifi.dimensions.contentSpacing.y + + Button { + id: openButton + color: hifi.buttons.blue + action: okAction + Keys.onReturnPressed: okAction.trigger() + KeyNavigation.right: cancelButton + KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem + KeyNavigation.tab: cancelButton + } + + Button { + id: cancelButton + action: cancelAction + Keys.onReturnPressed: { cancelAction.trigger() } + KeyNavigation.left: openButton + KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem + KeyNavigation.backtab: openButton + } + } + + Action { + id: okAction + text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open" + enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false + onTriggered: { + if (!root.selectDirectory && !d.currentSelectionIsFolder + || root.selectDirectory && fileTableView.currentRow === -1) { + okActionTimer.start(); + } else { + fileTableView.navigateToCurrentRow(); + } + } + } + + Timer { + id: okActionTimer + interval: 50 + running: false + repeat: false + onTriggered: { + if (!root.saveDialog) { + selectedFile(d.currentSelectionUrl); + root.destroy() + return; + } + + // Handle the ambiguity between different cases + // * typed name (with or without extension) + // * full path vs relative vs filename only + var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter); + + if (!selection) { + desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" }) + return; + } + + if (helper.urlIsDir(selection)) { + root.dir = selection; + currentSelection.text = ""; + return; + } + + // Check if the file is a valid target + if (!helper.urlIsWritable(selection)) { + desktop.messageBox({ + icon: OriginalDialogs.StandardIcon.Warning, + text: "Unable to write to location " + selection + }) + return; + } + + if (helper.urlExists(selection)) { + var messageBox = desktop.messageBox({ + icon: OriginalDialogs.StandardIcon.Question, + buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No, + text: "Do you wish to overwrite " + selection + "?", + }); + var result = messageBox.exec(); + if (OriginalDialogs.StandardButton.Yes !== result) { + return; + } + } + + console.log("Selecting " + selection) + selectedFile(selection); + root.destroy(); + } + } + + Action { + id: cancelAction + text: "Cancel" + onTriggered: { canceled(); root.shown = false; } + } + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + event.accepted = d.navigateUp(); + break; + + case Qt.Key_Home: + event.accepted = d.navigateHome(); + break; + + case Qt.Key_Escape: + event.accepted = true; + root.click(OriginalDialogs.StandardButton.Cancel); + break; + } + } +} diff --git a/interface/resources/qml/dialogs/+android/QueryDialog.qml b/interface/resources/qml/dialogs/+android/QueryDialog.qml new file mode 100644 index 0000000000..aec6d8a286 --- /dev/null +++ b/interface/resources/qml/dialogs/+android/QueryDialog.qml @@ -0,0 +1,231 @@ +// +// QueryDialog.qml +// +// Created by Bradley Austin Davis on 22 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 + +import "../controls-uit" +import "../styles-uit" +import "../windows" + +ModalWindow { + id: root + HifiConstants { id: hifi } + implicitWidth: 640 + implicitHeight: 320 + visible: true + keyboardOverride: true // Disable ModalWindow's keyboard. + + signal selected(var result); + signal canceled(); + + property int icon: hifi.icons.none + property string iconText: "" + property int iconSize: 35 + onIconChanged: updateIcon(); + + property var items; + property string label + property var result; + property alias current: textResult.text + + // For text boxes + property alias placeholderText: textResult.placeholderText + + // For combo boxes + property bool editable: true; + + property int titleWidth: 0 + onTitleWidthChanged: d.resize(); + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + + function updateIcon() { + if (!root) { + return; + } + iconText = hifi.glyphForIcon(root.icon); + } + + Item { + id: modalWindowItem + clip: true + width: pane.width + height: pane.height + anchors.margins: 0 + + QtObject { + id: d + readonly property int minWidth: 480 + readonly property int maxWdith: 1280 + readonly property int minHeight: 120 + readonly property int maxHeight: 720 + + function resize() { + var targetWidth = Math.max(titleWidth, pane.width) + var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height + root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); + root.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0) + } + } + + Item { + anchors { + top: parent.top + bottom: keyboard.top; + left: parent.left; + right: parent.right; + margins: 0 + bottomMargin: 2 * hifi.dimensions.contentSpacing.y + } + + // FIXME make a text field type that can be bound to a history for autocompletion + TextField { + id: textResult + label: root.label + visible: items ? false : true + anchors { + left: parent.left; + right: parent.right; + bottom: parent.bottom + } + KeyNavigation.down: acceptButton + KeyNavigation.tab: acceptButton + } + + ComboBox { + id: comboBox + label: root.label + visible: items ? true : false + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + model: items ? items : [] + KeyNavigation.down: acceptButton + KeyNavigation.tab: acceptButton + } + } + + property alias keyboardOverride: root.keyboardOverride + property alias keyboardRaised: root.keyboardRaised + property alias punctuationMode: root.punctuationMode + Keyboard { + id: keyboard + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttons.top + bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0 + } + } + + Flow { + id: buttons + spacing: hifi.dimensions.contentSpacing.x + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + layoutDirection: Qt.RightToLeft + anchors { + bottom: parent.bottom + right: parent.right + margins: 0 + bottomMargin: hifi.dimensions.contentSpacing.y + } + Button { + id: cancelButton + action: cancelAction + KeyNavigation.left: acceptButton + KeyNavigation.up: items ? comboBox : textResult + KeyNavigation.backtab: acceptButton + } + Button { + id: acceptButton + action: acceptAction + KeyNavigation.right: cancelButton + KeyNavigation.up: items ? comboBox : textResult + KeyNavigation.tab: cancelButton + KeyNavigation.backtab: items ? comboBox : textResult + } + } + + Action { + id: cancelAction + text: qsTr("Cancel"); + shortcut: "Esc" + onTriggered: { + root.canceled(); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; + } + } + + Action { + id: acceptAction + text: qsTr("OK"); + shortcut: "Return" + onTriggered: { + root.result = items ? comboBox.currentText : textResult.text + root.selected(root.result); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; + } + } + } + + Keys.onPressed: { + if (!visible) { + return + } + + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + cancelAction.trigger() + event.accepted = true; + break; + + case Qt.Key_Return: + case Qt.Key_Enter: + if (acceptButton.focus) { + acceptAction.trigger() + } else if (cancelButton.focus) { + cancelAction.trigger() + } else if (comboBox.focus || comboBox.popup.focus) { + comboBox.showList() + } + event.accepted = true; + break; + } + } + + Component.onCompleted: { + keyboardEnabled = HMD.active; + updateIcon(); + d.resize(); + if (items) { + comboBox.forceActiveFocus() + } else { + textResult.forceActiveFocus() + } + } +} diff --git a/interface/resources/qml/dialogs/AssetDialog.qml b/interface/resources/qml/dialogs/AssetDialog.qml index 8d19d38efb..e8d28e9b37 100644 --- a/interface/resources/qml/dialogs/AssetDialog.qml +++ b/interface/resources/qml/dialogs/AssetDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import Qt.labs.settings 1.0 import "../styles-uit" diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 4d6fe74bca..008ed5b860 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5; -import QtQuick.Controls 1.4; +import QtQuick 2.7; import QtQuick.Dialogs 1.2 as OriginalDialogs; +import QtQuick.Controls 2.3 import "../controls-uit"; import "../styles-uit"; @@ -122,12 +122,6 @@ ModalWindow { root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight); - if (checkBoxField.visible && comboBoxField.visible) { - checkBoxField.width = extraInputs.width / 2; - comboBoxField.width = extraInputs.width / 2; - } else if (!checkBoxField.visible && comboBoxField.visible) { - comboBoxField.width = extraInputs.width; - } } } @@ -198,6 +192,15 @@ ModalWindow { label: root.comboBox.label; focus: Boolean(root.comboBox); visible: Boolean(root.comboBox); + Binding on x { + when: comboBoxField.visible + value: !checkBoxField.visible ? buttons.x : acceptButton.x + } + + Binding on width { + when: comboBoxField.visible + value: !checkBoxField.visible ? buttons.width : buttons.width - acceptButton.x + } anchors { right: parent.right; bottom: parent.bottom; @@ -266,18 +269,22 @@ ModalWindow { Action { id: cancelAction; text: qsTr("Cancel"); - shortcut: Qt.Key_Escape; + shortcut: "Esc"; onTriggered: { root.result = null; root.canceled(); - root.destroy(); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; } } Action { id: acceptAction; text: qsTr("Add"); - shortcut: Qt.Key_Return; + shortcut: "Return" onTriggered: { var result = {}; if (textInput) { @@ -292,7 +299,11 @@ ModalWindow { } root.result = JSON.stringify(result); root.selected(root.result); - root.destroy(); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; } } } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index c078ace264..154d66378b 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -8,12 +8,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import Qt.labs.folderlistmodel 2.1 import Qt.labs.settings 1.0 -import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Controls 1.4 as QQC1 +import QtQuick.Controls 2.3 import ".." import "../controls-uit" @@ -532,9 +532,6 @@ ModalWindow { itemDelegate: Item { clip: true - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; } - FiraSansSemiBold { text: getText(); elide: styleData.elideMode @@ -548,7 +545,7 @@ ModalWindow { size: hifi.fontSizes.tableText color: hifi.colors.baseGrayHighlight font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) - ? firaSansSemiBold.name : firaSansRegular.name + ? "Fira Sans SemiBold" : "Fira Sans" function getText() { if (styleData.row === -1) { @@ -577,7 +574,7 @@ ModalWindow { } } - TableViewColumn { + QQC1.TableViewColumn { id: fileNameColumn role: "fileName" title: "Name" @@ -585,7 +582,7 @@ ModalWindow { movable: false resizable: true } - TableViewColumn { + QQC1.TableViewColumn { id: fileModifiedColumn role: "fileModified" title: "Date" @@ -594,7 +591,7 @@ ModalWindow { resizable: true visible: !selectDirectory } - TableViewColumn { + QQC1.TableViewColumn { role: "fileSize" title: "Size" width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 351df6dc8a..b5ac6cab72 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index e16f3aa12d..0a97ab9241 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../controls-uit" as HifiControls import "../styles-uit" @@ -27,7 +26,7 @@ ScrollingWindow { property var showCategories: [] minSize: Qt.vector2d(400, 500) - HifiConstants { id: hifi } + //HifiConstants { id: hifi } function saveAll() { for (var i = 0; i < sections.length; ++i) { @@ -97,19 +96,19 @@ ScrollingWindow { anchors { top: parent.top right: parent.right - rightMargin: hifi.dimensions.contentMargin.x + rightMargin: 10//hifi.dimensions.contentMargin.x } - spacing: hifi.dimensions.contentSpacing.x + spacing: 1//hifi.dimensions.contentSpacing.x HifiControls.Button { text: "Save changes" - color: hifi.buttons.blue + color: 1//hifi.buttons.blue onClicked: root.saveAll() } HifiControls.Button { text: "Cancel" - color: hifi.buttons.white + color: 2//hifi.buttons.white onClicked: root.restoreAll() } } diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index b5de5362f2..41ee30e6d5 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick 2.7 +import QtQuick.Controls 2.3 import "../controls-uit" import "../styles-uit" @@ -165,21 +164,30 @@ ModalWindow { Action { id: cancelAction - text: qsTr("Cancel") - shortcut: Qt.Key_Escape + text: qsTr("Cancel"); + shortcut: "Esc" onTriggered: { root.canceled(); - root.destroy(); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; } } + Action { id: acceptAction - text: qsTr("OK") - shortcut: Qt.Key_Return + text: qsTr("OK"); + shortcut: "Return" onTriggered: { root.result = items ? comboBox.currentText : textResult.text root.selected(root.result); - root.destroy(); + // FIXME we are leaking memory to avoid a crash + // root.destroy(); + + root.disableFade = true + visible = false; } } } diff --git a/interface/resources/qml/dialogs/TabletAssetDialog.qml b/interface/resources/qml/dialogs/TabletAssetDialog.qml index 016deec094..897378e40c 100644 --- a/interface/resources/qml/dialogs/TabletAssetDialog.qml +++ b/interface/resources/qml/dialogs/TabletAssetDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../styles-uit" import "../windows" diff --git a/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml b/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml index 6d2ff36550..544824135e 100644 --- a/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml +++ b/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml @@ -10,7 +10,6 @@ import Hifi 1.0 import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs Item { diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml index 7965006b8b..623388e9b3 100644 --- a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Controls 2.3 import "../controls-uit" import "../styles-uit" @@ -140,12 +140,6 @@ TabletModalWindow { root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); modalWindowItem.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight); - if (checkBoxField.visible && comboBoxField.visible) { - checkBoxField.width = extraInputs.width / 2; - comboBoxField.width = extraInputs.width / 2; - } else if (!checkBoxField.visible && comboBoxField.visible) { - comboBoxField.width = extraInputs.width; - } } } @@ -223,6 +217,15 @@ TabletModalWindow { label: root.comboBox.label; focus: Boolean(root.comboBox); visible: Boolean(root.comboBox); + Binding on x { + when: comboBoxField.visible + value: !checkBoxField.visible ? buttons.x : acceptButton.x + } + + Binding on width { + when: comboBoxField.visible + value: !checkBoxField.visible ? buttons.width : buttons.width - acceptButton.x + } anchors { right: parent.right; bottom: parent.bottom; @@ -294,7 +297,7 @@ TabletModalWindow { Action { id: cancelAction; text: qsTr("Cancel"); - shortcut: Qt.Key_Escape; + shortcut: "Esc"; onTriggered: { root.result = null; root.canceled(); @@ -305,7 +308,7 @@ TabletModalWindow { Action { id: acceptAction; text: qsTr("Add"); - shortcut: Qt.Key_Return; + shortcut: "Return"; onTriggered: { var result = {}; if (textInput) { diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 776f47d19d..db15337913 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -8,12 +8,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import Qt.labs.folderlistmodel 2.1 import Qt.labs.settings 1.0 -import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Controls 1.4 as QQC1 +import QtQuick.Controls 2.3 import ".." import "../controls-uit" @@ -496,9 +496,6 @@ TabletModalWindow { itemDelegate: Item { clip: true - //FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - //FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; } - FiraSansSemiBold { text: getText(); elide: styleData.elideMode @@ -512,7 +509,7 @@ TabletModalWindow { size: hifi.fontSizes.tableText color: hifi.colors.baseGrayHighlight //font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) - //? firaSansSemiBold.name : firaSansRegular.name + //? "Fira Sans SemiBold" : "Fira Sans" function getText() { if (styleData.row === -1) { @@ -541,7 +538,7 @@ TabletModalWindow { } } - TableViewColumn { + QQC1.TableViewColumn { id: fileNameColumn role: "fileName" title: "Name" @@ -549,7 +546,7 @@ TabletModalWindow { movable: false resizable: true } - TableViewColumn { + QQC1.TableViewColumn { id: fileMofifiedColumn role: "fileModified" title: "Date" @@ -558,7 +555,7 @@ TabletModalWindow { resizable: true visible: !selectDirectory } - TableViewColumn { + QQC1.TableViewColumn { role: "fileSize" title: "Size" width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 269788a808..c85b2b2ba0 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -10,7 +10,6 @@ import Hifi 1.0 import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../controls-uit" import "../styles-uit" diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml index f8876b1ec8..fabe0dd247 100644 --- a/interface/resources/qml/dialogs/TabletMessageBox.qml +++ b/interface/resources/qml/dialogs/TabletMessageBox.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" diff --git a/interface/resources/qml/dialogs/TabletQueryDialog.qml b/interface/resources/qml/dialogs/TabletQueryDialog.qml index e21677c12c..5746a3d67c 100644 --- a/interface/resources/qml/dialogs/TabletQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletQueryDialog.qml @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Controls 2.3 import "../controls-uit" import "../styles-uit" @@ -65,34 +65,33 @@ TabletModalWindow { id: modalWindowItem width: parent.width - 12 height: 240 - anchors { - verticalCenter: parent.verticalCenter - horizontalCenter: parent.horizontalCenter - } + anchors.horizontalCenter: parent.horizontalCenter QtObject { id: d - readonly property int minWidth: 470 - readonly property int maxWidth: 470 + readonly property int minWidth: modalWindowItem.width + readonly property int maxWidth: modalWindowItem.width readonly property int minHeight: 120 readonly property int maxHeight: 720 function resize() { - var targetWidth = Math.max(titleWidth, 470) + var targetWidth = Math.max(titleWidth, modalWindowItem.width) var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height modalWindowItem.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); - modalWindowItem.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0) + modalWindowItem.frameMarginTop + modalWindowItem.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + modalWindowItem.frameMarginTop + modalWindowItem.y = (root.height - (modalWindowItem.height + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0))) / 2 } } Item { anchors { top: parent.top - bottom: keyboard.top; + bottom: buttons.top; left: parent.left; right: parent.right; margins: 0 bottomMargin: 2 * hifi.dimensions.contentSpacing.y + topMargin: modalWindowItem.frameMarginTop } // FIXME make a text field type that can be bound to a history for autocompletion @@ -106,6 +105,7 @@ TabletModalWindow { right: parent.right; bottom: parent.bottom leftMargin: 5 + rightMargin: 5 } } @@ -124,22 +124,6 @@ TabletModalWindow { } } - property alias keyboardOverride: root.keyboardOverride - property alias keyboardRaised: root.keyboardRaised - property alias punctuationMode: root.punctuationMode - - Keyboard { - id: keyboard - raised: keyboardEnabled && keyboardRaised - numeric: punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: buttons.top - bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0 - } - } - Flow { id: buttons focus: true @@ -150,6 +134,7 @@ TabletModalWindow { bottom: parent.bottom right: parent.right margins: 0 + rightMargin: hifi.dimensions.borderRadius bottomMargin: hifi.dimensions.contentSpacing.y } Button { action: cancelAction } @@ -159,7 +144,7 @@ TabletModalWindow { Action { id: cancelAction text: qsTr("Cancel") - shortcut: Qt.Key_Escape + shortcut: "Esc" onTriggered: { root.canceled(); root.destroy(); @@ -168,7 +153,7 @@ TabletModalWindow { Action { id: acceptAction text: qsTr("OK") - shortcut: Qt.Key_Return + shortcut: "Return" onTriggered: { root.result = items ? comboBox.currentText : textResult.text root.selected(root.result); @@ -177,7 +162,17 @@ TabletModalWindow { } } - Keys.onPressed: { + Keyboard { + id: keyboard + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + top: modalWindowItem.bottom + } + } + Keys.onPressed: { if (!visible) { return } diff --git a/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml new file mode 100644 index 0000000000..54bdb0a888 --- /dev/null +++ b/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml @@ -0,0 +1,533 @@ +// +// AssetDialogContent.qml +// +// Created by David Rowe on 19 Apr 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.5 + +import "../../controls-uit" +import "../../styles-uit" + +import "../fileDialog" + +Item { + // Set from OffscreenUi::assetDialog() + property alias dir: assetTableModel.folder + property alias filter: selectionType.filtersString // FIXME: Currently only supports simple filters, "*.xxx". + property int options // Not used. + + property bool selectDirectory: false + + // Not implemented. + //property bool saveDialog: false; + //property bool multiSelect: false; + + property bool singleClickNavigate: false + + HifiConstants { id: hifi } + + Component.onCompleted: { + homeButton.destination = dir; + + if (selectDirectory) { + d.currentSelectionIsFolder = true; + d.currentSelectionPath = assetTableModel.folder; + } + + assetTableView.forceActiveFocus(); + } + + Item { + id: assetDialogItem + anchors.fill: parent + clip: true + + MouseArea { + // Clear selection when click on internal unused area. + anchors.fill: parent + drag.target: root + onClicked: { + d.clearSelection(); + frame.forceActiveFocus(); + assetTableView.forceActiveFocus(); + } + } + + Row { + id: navControls + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: parent.left + } + spacing: hifi.dimensions.contentSpacing.x + + GlyphButton { + id: upButton + glyph: hifi.glyphs.levelUp + width: height + size: 30 + enabled: assetTableModel.parentFolder !== "" + onClicked: d.navigateUp(); + } + + GlyphButton { + id: homeButton + property string destination: "" + glyph: hifi.glyphs.home + size: 28 + width: height + enabled: destination !== "" + //onClicked: d.navigateHome(); + onClicked: assetTableModel.folder = destination; + } + } + + ComboBox { + id: pathSelector + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: navControls.right + leftMargin: hifi.dimensions.contentSpacing.x + right: parent.right + } + z: 10 + + property string lastValidFolder: assetTableModel.folder + + function calculatePathChoices(folder) { + var folders = folder.split("/"), + choices = [], + i, length; + + if (folders[folders.length - 1] === "") { + folders.pop(); + } + + choices.push(folders[0]); + + for (i = 1, length = folders.length; i < length; i++) { + choices.push(choices[i - 1] + "/" + folders[i]); + } + + if (folders[0] === "") { + choices[0] = "/"; + } + + choices.reverse(); + + if (choices.length > 0) { + pathSelector.model = choices; + } + } + + onLastValidFolderChanged: { + var folder = lastValidFolder; + calculatePathChoices(folder); + } + + onCurrentTextChanged: { + var folder = currentText; + + if (folder !== "/") { + folder += "/"; + } + + if (folder !== assetTableModel.folder) { + if (root.selectDirectory) { + currentSelection.text = currentText; + d.currentSelectionPath = currentText; + } + assetTableModel.folder = folder; + assetTableView.forceActiveFocus(); + } + } + } + + QtObject { + id: d + + property string currentSelectionPath + property bool currentSelectionIsFolder + property var tableViewConnection: Connections { target: assetTableView; onCurrentRowChanged: d.update(); } + + function update() { + var row = assetTableView.currentRow; + + if (row === -1) { + if (!root.selectDirectory) { + currentSelection.text = ""; + currentSelectionIsFolder = false; + } + return; + } + + var rowInfo = assetTableModel.get(row); + currentSelectionPath = rowInfo.filePath; + currentSelectionIsFolder = rowInfo.fileIsDir; + if (root.selectDirectory || !currentSelectionIsFolder) { + currentSelection.text = currentSelectionPath; + } else { + currentSelection.text = ""; + } + } + + function navigateUp() { + if (assetTableModel.parentFolder !== "") { + assetTableModel.folder = assetTableModel.parentFolder; + return true; + } + return false; + } + + function navigateHome() { + assetTableModel.folder = homeButton.destination; + return true; + } + + function clearSelection() { + assetTableView.selection.clear(); + assetTableView.currentRow = -1; + update(); + } + } + + ListModel { + id: assetTableModel + + property string folder + property string parentFolder: "" + readonly property string rootFolder: "/" + + onFolderChanged: { + parentFolder = calculateParentFolder(); + update(); + } + + function calculateParentFolder() { + if (folder !== "/") { + return folder.slice(0, folder.slice(0, -1).lastIndexOf("/") + 1); + } + return ""; + } + + function isFolder(row) { + if (row === -1) { + return false; + } + return get(row).fileIsDir; + } + + function onGetAllMappings(error, map) { + var mappings, + fileTypeFilter, + index, + path, + fileName, + fileType, + fileIsDir, + isValid, + subDirectory, + subDirectories = [], + fileNameSort, + rows = 0, + lower, + middle, + upper, + i, + length; + + clear(); + + if (error === "") { + mappings = Object.keys(map); + fileTypeFilter = filter.replace("*", "").toLowerCase(); + + for (i = 0, length = mappings.length; i < length; i++) { + index = mappings[i].lastIndexOf("/"); + + path = mappings[i].slice(0, mappings[i].lastIndexOf("/") + 1); + fileName = mappings[i].slice(path.length); + fileType = fileName.slice(fileName.lastIndexOf(".")); + fileIsDir = false; + isValid = false; + + if (fileType.toLowerCase() === fileTypeFilter) { + if (path === folder) { + isValid = !selectDirectory; + } else if (path.length > folder.length) { + subDirectory = path.slice(folder.length); + index = subDirectory.indexOf("/"); + if (index === subDirectory.lastIndexOf("/")) { + fileName = subDirectory.slice(0, index); + if (subDirectories.indexOf(fileName) === -1) { + fileIsDir = true; + isValid = true; + subDirectories.push(fileName); + } + } + } + } + + if (isValid) { + fileNameSort = (fileIsDir ? "*" : "") + fileName.toLowerCase(); + + lower = 0; + upper = rows; + while (lower < upper) { + middle = Math.floor((lower + upper) / 2); + var lessThan; + if (fileNameSort < get(middle)["fileNameSort"]) { + lessThan = true; + upper = middle; + } else { + lessThan = false; + lower = middle + 1; + } + } + + insert(lower, { + fileName: fileName, + filePath: path + (fileIsDir ? "" : fileName), + fileIsDir: fileIsDir, + fileNameSort: fileNameSort + }); + + rows++; + } + } + + } else { + console.log("Error getting mappings from Asset Server"); + } + } + + function update() { + d.clearSelection(); + clear(); + Assets.getAllMappings(onGetAllMappings); + } + } + + Table { + id: assetTableView + colorScheme: hifi.colorSchemes.light + anchors { + top: navControls.bottom + topMargin: hifi.dimensions.contentSpacing.y + left: parent.left + right: parent.right + bottom: currentSelection.top + bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height + } + + model: assetTableModel + + focus: true + + onClicked: { + if (singleClickNavigate) { + navigateToRow(row); + } + } + + onDoubleClicked: navigateToRow(row); + Keys.onReturnPressed: navigateToCurrentRow(); + Keys.onEnterPressed: navigateToCurrentRow(); + + itemDelegate: Item { + clip: true + + FiraSansSemiBold { + text: styleData.value + elide: styleData.elideMode + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.tableText + color: hifi.colors.baseGrayHighlight + font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir) + ? "Fira Sans SemiBold" : "Fira Sans" + } + } + + TableViewColumn { + id: fileNameColumn + role: "fileName" + title: "Name" + width: assetTableView.width + movable: false + resizable: false + } + + function navigateToRow(row) { + currentRow = row; + navigateToCurrentRow(); + } + + function navigateToCurrentRow() { + if (model.isFolder(currentRow)) { + model.folder = model.get(currentRow).filePath; + } else { + okAction.trigger(); + } + } + + Timer { + id: prefixClearTimer + interval: 1000 + repeat: false + running: false + onTriggered: assetTableView.prefix = ""; + } + + property string prefix: "" + + function addToPrefix(event) { + if (!event.text || event.text === "") { + return false; + } + var newPrefix = prefix + event.text.toLowerCase(); + var matchedIndex = -1; + for (var i = 0; i < model.count; ++i) { + var name = model.get(i).fileName.toLowerCase(); + if (0 === name.indexOf(newPrefix)) { + matchedIndex = i; + break; + } + } + + if (matchedIndex !== -1) { + assetTableView.selection.clear(); + assetTableView.selection.select(matchedIndex); + assetTableView.currentRow = matchedIndex; + assetTableView.prefix = newPrefix; + } + prefixClearTimer.restart(); + return true; + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + case Qt.Key_Tab: + case Qt.Key_Backtab: + event.accepted = false; + break; + + default: + if (addToPrefix(event)) { + event.accepted = true + } else { + event.accepted = false; + } + break; + } + } + } + + TextField { + id: currentSelection + label: selectDirectory ? "Directory:" : "File name:" + anchors { + left: parent.left + right: selectionType.visible ? selectionType.left: parent.right + rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 + bottom: buttonRow.top + bottomMargin: hifi.dimensions.contentSpacing.y + } + readOnly: true + activeFocusOnTab: !readOnly + onActiveFocusChanged: if (activeFocus) { selectAll(); } + onAccepted: okAction.trigger(); + } + + FileTypeSelection { + id: selectionType + anchors { + top: currentSelection.top + left: buttonRow.left + right: parent.right + } + visible: !selectDirectory && filtersCount > 1 + KeyNavigation.left: assetTableView + KeyNavigation.right: openButton + } + + Action { + id: okAction + text: currentSelection.text && root.selectDirectory && assetTableView.currentRow === -1 ? "Choose" : "Open" + enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false + onTriggered: { + if (!root.selectDirectory && !d.currentSelectionIsFolder + || root.selectDirectory && assetTableView.currentRow === -1) { + selectedAsset(d.currentSelectionPath); + root.destroy(); + } else { + assetTableView.navigateToCurrentRow(); + } + } + } + + Action { + id: cancelAction + text: "Cancel" + onTriggered: { + canceled(); + root.destroy(); + } + } + + Row { + id: buttonRow + anchors { + right: parent.right + bottom: parent.bottom + } + spacing: hifi.dimensions.contentSpacing.y + + Button { + id: openButton + color: hifi.buttons.blue + action: okAction + Keys.onReturnPressed: okAction.trigger() + KeyNavigation.up: selectionType + KeyNavigation.left: selectionType + KeyNavigation.right: cancelButton + } + + Button { + id: cancelButton + action: cancelAction + KeyNavigation.up: selectionType + KeyNavigation.left: openButton + KeyNavigation.right: assetTableView.contentItem + Keys.onReturnPressed: { canceled(); root.enabled = false } + } + } + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + event.accepted = d.navigateUp(); + break; + + case Qt.Key_Home: + event.accepted = d.navigateHome(); + break; + + } + } +} diff --git a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml index dabc66c502..d49e1e11be 100644 --- a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml +++ b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml @@ -8,8 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.3 +import QtQuick.Controls 1.5 as QQC1 import "../../controls-uit" import "../../styles-uit" @@ -345,9 +346,6 @@ Item { itemDelegate: Item { clip: true - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; } - FiraSansSemiBold { text: styleData.value elide: styleData.elideMode @@ -361,11 +359,11 @@ Item { size: hifi.fontSizes.tableText color: hifi.colors.baseGrayHighlight font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir) - ? firaSansSemiBold.name : firaSansRegular.name + ? "Fira Sans SemiBold" : "Fira Sans" } } - TableViewColumn { + QQC1.TableViewColumn { id: fileNameColumn role: "fileName" title: "Name" diff --git a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml index bdb42aba61..8411980db7 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 import "../../controls-uit" diff --git a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml index 860cbcb5a8..3b3efaf520 100644 --- a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml @@ -10,8 +10,6 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "../../controls-uit" as HiFiControls import "../../styles-uit" diff --git a/interface/resources/qml/dialogs/preferences/Preference.qml b/interface/resources/qml/dialogs/preferences/Preference.qml index 1d72197382..6956147950 100644 --- a/interface/resources/qml/dialogs/preferences/Preference.qml +++ b/interface/resources/qml/dialogs/preferences/Preference.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 Item { id: root diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android/AvatarOption.qml new file mode 100644 index 0000000000..85d7e52eb2 --- /dev/null +++ b/interface/resources/qml/hifi/+android/AvatarOption.qml @@ -0,0 +1,117 @@ +// +// AvatarOption.qml +// interface/resources/qml/hifi/android +// +// Created by Cristian Duarte & Gabriel Calero on 12 Oct 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick.Layouts 1.3 +import QtQuick 2.5 +import "../controls-uit" as HifiControlsUit + +ColumnLayout { + id: itemRoot + + property string type: ""; + + property string thumbnailUrl: ""; + property string avatarUrl: ""; + property string avatarName: ""; + property bool avatarSelected: false; + + property string methodName: ""; + property string actionText: ""; + + spacing: 4 * 3 + signal sendToParentQml(var message); + + Image { + id: itemImage + Layout.preferredWidth: 250 * 3 + Layout.preferredHeight: 140 * 3 + source: thumbnailUrl + asynchronous: true + fillMode: Image.PreserveAspectFit + + MouseArea { + id: itemArea + anchors.fill: parent + hoverEnabled: true + enabled: true + onClicked: { + if (type=="avatar") { + if (!avatarSelected) sendToParentQml({ method: "selectAvatar", params: { avatarUrl: avatarUrl } }); + } else { + sendToParentQml({ method: methodName, params: { } }); + } + } + } + + } + + Text { + id: itemName + text: avatarName + color: "#FFFFFF" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.horizontalCenter: itemImage.horizontalCenter + font.pointSize: 5*3 + wrapMode: Text.WordWrap + width: parent + MouseArea { + id: itemNameArea + anchors.fill: parent + hoverEnabled: true + enabled: true + onClicked: { + if (type=="avatar") { + if (!avatarSelected) sendToParentQml({ method: "selectAvatar", params: { avatarUrl: avatarUrl } }); + } else { + sendToParentQml({ method: methodName, params: { } }); + } + } + } + } + + HifiControlsUit.ImageButton { + width: 140*3 + height: 35*3 + text: type=="extra" ? actionText: "CHOOSE" + source: "../../../../icons/button.svg" + hoverSource: "../../../../icons/button-a.svg" + fontSize: 18*3 + fontColor: "#2CD8FF" + hoverFontColor: "#FFFFFF" + anchors { + horizontalCenter: itemName.horizontalCenter + } + visible: !avatarSelected + onClicked: { + if (type=="avatar") { + if (!avatarSelected) sendToParentQml({ method: "selectAvatar", params: { avatarUrl: avatarUrl } }); + } else { + sendToParentQml({ method: methodName, params: { } }); + } + } + } + + Image { + id: tickImage + width: 35 * 3 + height: 35 * 3 + source: "../../../icons/tick.svg" + anchors { + horizontalCenter: itemName.horizontalCenter + } + visible: avatarSelected + } + + Component.onCompleted:{ + sendToParentQml.connect(sendToScript); + } +} diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android/HifiConstants.qml index ee6d92ed38..4c161da259 100644 --- a/interface/resources/qml/hifi/+android/HifiConstants.qml +++ b/interface/resources/qml/hifi/+android/HifiConstants.qml @@ -10,6 +10,7 @@ // import QtQuick 2.4 +import QtQuick.Window 2.2 Item { @@ -20,26 +21,27 @@ Item { Item { id: dimen - readonly property real windowLessWidth: 126 - readonly property real windowLessHeight: 64 + readonly property bool atLeast1440p: Screen.width >= 2560 && Screen.height >= 1440 + readonly property real windowLessWidth: atLeast1440p ? 378 : 284 + readonly property real windowLessHeight: atLeast1440p ? 192 : 144 readonly property real windowZ: 100 - readonly property real headerHeight: 276 + readonly property real headerHeight: atLeast1440p ? 276 : 207 - readonly property real headerIconPosX: 90 - readonly property real headerIconPosY: 108 - readonly property real headerIconWidth: 111 - readonly property real headerIconHeight: 111 - readonly property real headerIconTitleDistance: 151 + readonly property real headerIconPosX: atLeast1440p ? 90 : 67 + readonly property real headerIconPosY: atLeast1440p ? 108 : 81 + readonly property real headerIconWidth: atLeast1440p ? 111 : 83 + readonly property real headerIconHeight: atLeast1440p ? 111 : 83 + readonly property real headerIconTitleDistance: atLeast1440p ? 151 : 113 - readonly property real headerHideWidth: 150 - readonly property real headerHideHeight: 150 - readonly property real headerHideRightMargin: 110 - readonly property real headerHideTopMargin: 90 - readonly property real headerHideIconWidth: 70 - readonly property real headerHideIconHeight: 45 - readonly property real headerHideTextTopMargin: 36 + readonly property real headerHideWidth: atLeast1440p ? 150 : 112 + readonly property real headerHideHeight: atLeast1440p ? 150 : 112 + readonly property real headerHideRightMargin: atLeast1440p ? 110 : 82 + readonly property real headerHideTopMargin: atLeast1440p ? 90 : 67 + readonly property real headerHideIconWidth: atLeast1440p ? 70 : 52 + readonly property real headerHideIconHeight: atLeast1440p ? 45 : 33 + readonly property real headerHideTextTopMargin: atLeast1440p ? 36 : 27 readonly property real botomHudWidth: 366 readonly property real botomHudHeight: 180 diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android/StatsBar.qml new file mode 100644 index 0000000000..aee438b44f --- /dev/null +++ b/interface/resources/qml/hifi/+android/StatsBar.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: bar + x:300 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + bar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + // put on bottom + x = 300; + y = 0; + width = 300; + height = 300; + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android/avatarSelection.qml b/interface/resources/qml/hifi/+android/avatarSelection.qml new file mode 100644 index 0000000000..afa5634575 --- /dev/null +++ b/interface/resources/qml/hifi/+android/avatarSelection.qml @@ -0,0 +1,179 @@ +// +// avatarSelection.qml +// interface/resources/qml/android +// +// Created by Gabriel Calero & Cristian Duarte on 21 Sep 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Layouts 1.3 +import Hifi 1.0 + +import "../../styles" +import "." +import ".." +import ".." as QmlHifi +import "../../styles-uit" as HifiStyles + + +Item { + + id: top + + HifiConstants { id: android } + width: parent ? parent.width - android.dimen.windowLessWidth : 0 + height: parent ? parent.height - android.dimen.windowLessHeight : 0 + z: android.dimen.windowZ + anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } + + signal sendToScript(var message); + + property bool shown: true + + onShownChanged: { + top.visible = shown; + } + + + HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifiStyleConstants } + + property int cardWidth: 250 *3; + property int cardHeight: 240 *3; + property int gap: 14 *3; + + property var avatarsArray: []; + property var extraOptionsArray: []; + + function hide() { + shown = false; + sendToScript ({ method: "hide" }); + } + + Rectangle { + + width: parent ? parent.width : 0 + height: parent ? parent.height : 0 + + MouseArea { + anchors.fill: parent + } + + gradient: Gradient { + GradientStop { position: 0.0; color: android.color.gradientTop } + GradientStop { position: 1.0; color: android.color.gradientBottom } + } + + QmlHifi.WindowHeader { + id: header + iconSource: "../../../../icons/avatar-i.svg" + titleText: "AVATAR" + } + + ListModel { id: avatars } + + ListView { + id: scroll + height: 250*3 + property int stackedCardShadowHeight: 10*3; + spacing: gap; + clip: true; + anchors { + left: parent.left + right: parent.right + top: header.bottom + topMargin: gap + leftMargin: gap + rightMargin: gap + } + model: avatars; + orientation: ListView.Horizontal; + delegate: QmlHifi.AvatarOption { + type: model.type; + thumbnailUrl: model.thumbnailUrl; + avatarUrl: model.avatarUrl; + avatarName: model.avatarName; + avatarSelected: model.avatarSelected; + methodName: model.methodName; + actionText: model.actionText; + } + highlightMoveDuration: -1; + highlightMoveVelocity: -1; + } + + } + + function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + } + function replaceAll(str, find, replace) { + return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); + } + + function refreshSelected(selectedAvatarUrl) { + // URL as ID? + avatarsArray.forEach(function (avatarData) { + avatarData.avatarSelected = (selectedAvatarUrl == avatarData.avatarUrl); + console.log('[avatarSelection] avatar : ', avatarData.avatarName, ' is selected? ' , avatarData.avatarSelected); + }); + } + + function addAvatar(name, thumbnailUrl, avatarUrl) { + avatarsArray.push({ + type: "avatar", + thumbnailUrl: thumbnailUrl, + avatarUrl: avatarUrl, + avatarName: name, + avatarSelected: false, + methodName: "", + actionText: "" + }); + } + + function showAvatars() { + avatars.clear(); + avatarsArray.forEach(function (avatarData) { + avatars.append(avatarData); + console.log('[avatarSelection] adding avatar to model: ', JSON.stringify(avatarData)); + }); + extraOptionsArray.forEach(function (extraData) { + avatars.append(extraData); + console.log('[avatarSelection] adding extra option to model: ', JSON.stringify(extraData)); + }); + } + + function addExtraOption(showName, thumbnailUrl, methodNameWhenClicked, actionText) { + extraOptionsArray.push({ + type: "extra", + thumbnailUrl: thumbnailUrl, + avatarUrl: "", + avatarName: showName, + avatarSelected: false, + methodName: methodNameWhenClicked, + actionText: actionText + }); + } + + function fromScript(message) { + //console.log("[CHAT] fromScript " + JSON.stringify(message)); + switch (message.type) { + case "addAvatar": + addAvatar(message.name, message.thumbnailUrl, message.avatarUrl); + break; + case "addExtraOption": + //(showName, thumbnailUrl, methodNameWhenClicked, actionText) + addExtraOption(message.showName, message.thumbnailUrl, message.methodNameWhenClicked, message.actionText); + break; + case "refreshSelected": + refreshSelected(message.selectedAvatarUrl); + break; + case "showAvatars": + showAvatars(); + break; + default: + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/bottombar.qml b/interface/resources/qml/hifi/+android/bottombar.qml index 2a34b7fe19..66117d0389 100644 --- a/interface/resources/qml/hifi/+android/bottombar.qml +++ b/interface/resources/qml/hifi/+android/bottombar.qml @@ -25,6 +25,7 @@ import "." Item { id: bar x:0 + height: 255 property bool shown: true @@ -41,11 +42,14 @@ Item { Styles.HifiConstants { id: hifi } HifiConstants { id: android } + MouseArea { + anchors.fill: parent + } - Rectangle { + Rectangle { id: background anchors.fill : parent - color: "#FF000000" + color: "#FF000000" border.color: "#FFFFFF" anchors.bottomMargin: -1 anchors.leftMargin: -1 @@ -101,13 +105,25 @@ Item { } } } - } + } + + function relocateAndResize(newWindowWidth, newWindowHeight) { + width = newWindowWidth; + y = newWindowHeight - height; + } + + function onWindowGeometryChanged(rect) { + relocateAndResize(rect.width, rect.height); + } Component.onCompleted: { // put on bottom - width = Window.innerWidth; - height = 255; - y = Window.innerHeight - height; + relocateAndResize(Window.innerWidth, Window.innerHeight); + Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this + } + + Component.onDestruction: { + Window.geometryChanged.disconnect(onWindowGeometryChanged); } function addButton(properties) { diff --git a/interface/resources/qml/hifi/+android/button.qml b/interface/resources/qml/hifi/+android/button.qml index ec7af2ab92..3e9ce39351 100644 --- a/interface/resources/qml/hifi/+android/button.qml +++ b/interface/resources/qml/hifi/+android/button.qml @@ -40,6 +40,8 @@ Item { property bool isActive: false signal clicked() + signal entered() + signal exited() onIsActiveChanged: { if (button.isEntered) { @@ -116,16 +118,18 @@ Item { tabletRoot.playButtonClickSound(); }*/ } - onEntered: { + onPressed: { button.isEntered = true; + button.entered(); if (button.isActive) { button.state = "hover active state"; } else { button.state = "hover state"; } } - onExited: { + onReleased: { button.isEntered = false; + button.exited() if (button.isActive) { button.state = "active state"; } else { diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 8ce455c2c1..fe71314ece 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -25,8 +25,8 @@ Item { } Component.onCompleted: { - width = 330; - height = 330; + width = 300 + 30; // That 30 is extra regardless the qty of items shown + height = 300 + 30; x=Window.innerWidth - width; } @@ -60,10 +60,10 @@ Item { function fromScript(message) { switch (message.type) { case "allButtonsShown": - modesbar.height = flowMain.children.length * 100 + 10; + modesbar.height = flowMain.children.length * 300 + 30; // That 30 is extra regardless the qty of items shown break; case "inactiveButtonsHidden": - modesbar.height = 100 + 10; + modesbar.height = 300 + 30; break; default: break; diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 19a559b66c..526ea6aad0 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -24,6 +24,7 @@ Windows.ScrollingWindow { objectName: "AssetServer" title: "Asset Browser" resizable: true + opacity: parent.opacity destroyOnHidden: true implicitWidth: 384; implicitHeight: 640 minSize: Qt.vector2d(200, 300) @@ -38,6 +39,7 @@ Windows.ScrollingWindow { property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; property var selectedItemCount: treeView.selection.selectedIndexes.length; + property int updatesCount: 0; // this is used for notifying model-dependent bindings about model updates Settings { category: "Overlay.AssetServer" @@ -50,6 +52,9 @@ Windows.ScrollingWindow { ApplicationInterface.uploadRequest.connect(uploadClicked); assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError); assetMappingsModel.autoRefreshEnabled = true; + assetMappingsModel.updated.connect(function() { + ++updatesCount; + }); reload(); } @@ -57,7 +62,7 @@ Windows.ScrollingWindow { Component.onDestruction: { assetMappingsModel.autoRefreshEnabled = false; } - + function letterbox(headerGlyph, headerText, message) { letterboxMessage.headerGlyph = headerGlyph; letterboxMessage.headerText = headerText; @@ -144,7 +149,7 @@ Windows.ScrollingWindow { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; - + if (selectedItemCount > 1) { return false; } @@ -153,8 +158,8 @@ Windows.ScrollingWindow { return total | new RegExp(current).test(path); }, false); } - - function canRename() { + + function canRename() { if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { @@ -198,7 +203,7 @@ Windows.ScrollingWindow { var SHAPE_TYPE_STATIC_MESH = 3; var SHAPE_TYPE_BOX = 4; var SHAPE_TYPE_SPHERE = 5; - + var SHAPE_TYPES = []; SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; @@ -206,7 +211,7 @@ Windows.ScrollingWindow { SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND; var DYNAMIC_DEFAULT = false; var prompt = desktop.customInputDialog({ @@ -348,14 +353,14 @@ Windows.ScrollingWindow { } function deleteFile(index) { var paths = []; - + if (!index) { for (var i = 0; i < selectedItemCount; ++i) { index = treeView.selection.selectedIndexes[i]; paths[i] = assetProxyModel.data(index, 0x100); } } - + if (!paths) { return; } @@ -364,13 +369,13 @@ Windows.ScrollingWindow { var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } - + var object = desktop.messageBox({ icon: hifi.icons.question, buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, @@ -475,11 +480,11 @@ Windows.ScrollingWindow { }); } } - + Item { width: pane.contentWidth height: pane.height - + // The letterbox used for popup messages LetterboxMessage { id: letterboxMessage; @@ -541,7 +546,7 @@ Windows.ScrollingWindow { anchors.margins: hifi.dimensions.contentMargin.x + 2 // Extra for border anchors.left: parent.left anchors.right: parent.right - + treeModel: assetProxyModel selectionMode: SelectionMode.ExtendedSelection headerVisible: true @@ -561,9 +566,13 @@ Windows.ScrollingWindow { id: bakedColumn title: "Use Baked?" role: "baked" - width: 100 + width: 170 } - + + onSortIndicatorOrderChanged: { + Assets.sortProxyModel(sortIndicatorColumn, sortIndicatorOrder); + } + itemDelegate: Loader { id: itemDelegateLoader @@ -599,7 +608,7 @@ Windows.ScrollingWindow { } sourceComponent: getComponent() - + Component { id: labelComponent FiraSansSemiBold { @@ -608,15 +617,15 @@ Windows.ScrollingWindow { color: colorScheme == hifi.colorSchemes.light ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - + horizontalAlignment: styleData.column === 1 ? TextInput.AlignHCenter : TextInput.AlignLeft - + elide: Text.ElideMiddle MouseArea { id: mouseArea anchors.fill: parent - + acceptedButtons: Qt.NoButton hoverEnabled: true @@ -638,7 +647,7 @@ Windows.ScrollingWindow { color: colorScheme == hifi.colorSchemes.light ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - + elide: Text.ElideRight horizontalAlignment: TextInput.AlignHCenter @@ -660,8 +669,7 @@ Windows.ScrollingWindow { text: styleData.value - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - font.family: firaSansSemiBold.name + font.family: "Fira Sans SemiBold" font.pixelSize: hifi.fontSizes.textFieldInput height: hifi.dimensions.tableRowHeight @@ -726,7 +734,7 @@ Windows.ScrollingWindow { size: hifi.fontSizes.tableText color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText } - + Timer { id: showTimer interval: 1000 @@ -745,7 +753,7 @@ Windows.ScrollingWindow { treeLabelToolTip.visible = false; } }// End_OF( treeLabelToolTip ) - + MouseArea { propagateComposedEvents: true anchors.fill: parent @@ -803,7 +811,7 @@ Windows.ScrollingWindow { anchors.left: treeView.left anchors.right: treeView.right anchors.bottom: uploadSection.top - + RalewayRegular { anchors.verticalCenter: parent.verticalCenter @@ -847,13 +855,18 @@ Windows.ScrollingWindow { checked = Qt.binding(isChecked); } + + function getStatus() { + // kind of hack for ensuring getStatus() will be re-evaluated on updatesCount changes + return updatesCount, assetProxyModel.data(treeView.selection.currentIndex, 0x105); + } function isEnabled() { if (!treeView.selection.hasSelection) { return false; } - var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); + var status = getStatus(); if (status === "--") { return false; } @@ -871,18 +884,18 @@ Windows.ScrollingWindow { } } - return true; + return true; } function isChecked() { if (!treeView.selection.hasSelection) { return false; } - var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); + var status = getStatus(); return isEnabled() && status !== "Not Baked"; } } - + Item { anchors.verticalCenter: parent.verticalCenter width: infoGlyph.size; @@ -906,7 +919,7 @@ Windows.ScrollingWindow { "What is baking?", "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } - } + } }// End_OF( infoRow ) HifiControls.ContentSection { diff --git a/interface/resources/qml/hifi/ComboDialog.qml b/interface/resources/qml/hifi/ComboDialog.qml index 0b1a3b1a5c..e5dc8a9c1a 100644 --- a/interface/resources/qml/hifi/ComboDialog.qml +++ b/interface/resources/qml/hifi/ComboDialog.qml @@ -10,7 +10,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../styles-uit" import "../controls-uit" @@ -25,8 +24,6 @@ Item { property int dialogHeight; property int comboOptionTextSize: 16; property int comboBodyTextSize: 16; - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } - FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; } visible: false; id: combo; anchors.fill: parent; diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 8c732aac40..24111ad935 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,12 +1,14 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import QtWebEngine 1.5; import Qt.labs.settings 1.0 +import QtQuick.Controls 2.3 + import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" +import "../controls-uit" OriginalDesktop.Desktop { id: desktop diff --git a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml index 9230bbe962..9e9dcc75b2 100644 --- a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml +++ b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml @@ -10,7 +10,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../styles-uit" Item { @@ -24,8 +23,6 @@ Item { property real headerTextMargin: -5 property real headerGlyphMargin: -15 property bool isDesktop: false - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } - FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; } visible: false id: letterbox anchors.fill: parent @@ -78,7 +75,8 @@ Item { // Text Size font.pixelSize: headerTextPixelSize // Style - font.family: ralewaySemiBold.name + font.family: "Raleway" + font.weight: Font.DemiBold color: hifi.colors.darkGray horizontalAlignment: Text.AlignHLeft verticalAlignment: Text.AlignVCenter @@ -101,7 +99,7 @@ Item { horizontalAlignment: Text.AlignHLeft // Style font.pixelSize: popupTextPixelSize - font.family: ralewayRegular.name + font.family: "Raleway" color: hifi.colors.darkGray wrapMode: Text.WordWrap textFormat: Text.StyledText diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index dcd0d906db..d88bf1b147 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -10,7 +10,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../styles-uit" Item { @@ -23,8 +22,6 @@ Item { property real popupTextPixelSize: 16 property real headerTextMargin: -5 property real headerGlyphMargin: -15 - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } - FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; } visible: false id: letterbox anchors.fill: parent @@ -82,7 +79,8 @@ Item { // Text Size font.pixelSize: headerTextPixelSize // Style - font.family: ralewaySemiBold.name + font.family: "Raleway" + font.weight: Font.DemiBold color: hifi.colors.darkGray horizontalAlignment: Text.AlignHLeft verticalAlignment: Text.AlignVCenter @@ -127,7 +125,7 @@ Item { horizontalAlignment: Text.AlignHLeft // Style font.pixelSize: popupTextPixelSize - font.family: ralewayRegular.name + font.family: "Raleway" color: hifi.colors.darkGray wrapMode: Text.WordWrap textFormat: Text.StyledText diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 4c9c746488..c97a802f10 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -177,8 +177,7 @@ Item { anchors.right: parent.right anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin // Style - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - font.family: firaSansSemiBold.name + font.family: "Fira Sans SemiBold" font.pixelSize: displayNameTextPixelSize selectionColor: hifi.colors.blueAccent selectedTextColor: "black" diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 699173aaeb..d779b4ba42 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -908,7 +908,6 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter; } - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } Text { id: connectionHelpText; // Anchors @@ -923,7 +922,7 @@ Rectangle { horizontalAlignment: Text.AlignHLeft // Style font.pixelSize: 18; - font.family: ralewayRegular.name + font.family: "Raleway" color: hifi.colors.darkGray wrapMode: Text.WordWrap textFormat: Text.StyledText; diff --git a/interface/resources/qml/hifi/SpectatorCamera.qml b/interface/resources/qml/hifi/SpectatorCamera.qml index 3a8559ab1e..4bf80e410b 100644 --- a/interface/resources/qml/hifi/SpectatorCamera.qml +++ b/interface/resources/qml/hifi/SpectatorCamera.qml @@ -12,8 +12,7 @@ // import Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import "../styles-uit" import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index eab1e40af0..ba50b7f238 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -112,6 +112,7 @@ Rectangle { // mute is in its own row RowLayout { + spacing: (margins.sizeCheckBox - 10.5) * 3; AudioControls.CheckBox { id: muteMic text: qsTr("Mute microphone"); @@ -123,6 +124,19 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } } + + AudioControls.CheckBox { + id: stereoMic + spacing: muteMic.spacing; + text: qsTr("Enable stereo input"); + checked: AudioScriptingInterface.isStereoInput(); + onClicked: { + var success = AudioScriptingInterface.setStereoInput(checked); + if (!success) { + checked = !checked; + } + } + } } RowLayout { @@ -204,6 +218,8 @@ Rectangle { text: devicename onPressed: { if (!checked) { + stereoMic.checked = false; + AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1); } } diff --git a/interface/resources/qml/hifi/audio/InputPeak.qml b/interface/resources/qml/hifi/audio/InputPeak.qml index be58c9536b..00f7e63528 100644 --- a/interface/resources/qml/hifi/audio/InputPeak.qml +++ b/interface/resources/qml/hifi/audio/InputPeak.qml @@ -10,8 +10,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 Rectangle { diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index e798b35e29..fee37ca1c1 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -10,8 +10,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 6d7eb80974..2b9599a3cc 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -9,9 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import "../../styles-uit" @@ -57,31 +56,31 @@ RowLayout { HifiConstants { id: hifi; } Button { - style: ButtonStyle { - background: Rectangle { - implicitWidth: 20; - implicitHeight: 20; - radius: hifi.buttons.radius; - gradient: Gradient { - GradientStop { - position: 0.2; - color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; - } - GradientStop { - position: 1.0; - color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; - } + id: control + background: Rectangle { + implicitWidth: 20; + implicitHeight: 20; + radius: hifi.buttons.radius; + gradient: Gradient { + GradientStop { + position: 0.2; + color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; + } + GradientStop { + position: 1.0; + color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; } } - label: HiFiGlyphs { - // absolutely position due to asymmetry in glyph - x: isPlaying ? 0 : 1; - y: 1; - size: 14; - color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white"; - text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; - } } + contentItem: HiFiGlyphs { + // absolutely position due to asymmetry in glyph +// x: isPlaying ? 0 : 1; +// y: 1; + size: 14; + color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white"; + text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; + } + onClicked: isPlaying ? stopSound() : playSound(); } diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 372fb3c774..c0cf45d292 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -30,25 +30,31 @@ Rectangle { property string activeView: "initialize"; property bool ownershipStatusReceived: false; property bool balanceReceived: false; + property bool availableUpdatesReceived: false; + property string baseItemName: ""; property string itemName; property string itemId; property string itemHref; property string itemAuthor; + property int itemEdition: -1; + property string certificateId; property double balanceAfterPurchase; property bool alreadyOwned: false; property int itemPrice: -1; property bool isCertified; property string itemType; - property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"]; - property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"]; - property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"]; - property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"] - property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; + property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"]; + property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"]; + property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"]; + property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!", "REZZED!"] + property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar, hifi.glyphs.wand]; property bool shouldBuyWithControlledFailure: false; property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property string referrer; property bool isInstalled; + property bool isUpdating; + property string baseAppURL; // Style color: hifi.colors.white; Connections { @@ -90,6 +96,7 @@ Rectangle { root.activeView = "checkoutFailure"; UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message); } else { + root.certificateId = result.data.certificate_id; root.itemHref = result.data.download_url; if (result.data.categories.indexOf("Wearables") > -1) { root.itemType = "wearable"; @@ -103,8 +110,8 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get balance", result.data.message); } else { - root.balanceReceived = true; root.balanceAfterPurchase = result.data.balance - root.itemPrice; + root.balanceReceived = true; root.refreshBuyUI(); } } @@ -113,13 +120,13 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get Already Owned status", result.data.message); } else { - root.ownershipStatusReceived = true; if (result.data.marketplace_item_id === root.itemId) { root.alreadyOwned = result.data.already_owned; } else { console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!"); root.alreadyOwned = false; } + root.ownershipStatusReceived = true; root.refreshBuyUI(); } } @@ -129,26 +136,67 @@ Rectangle { root.isInstalled = true; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + for (var i = 0; i < result.data.updates.length; i++) { + // If the ItemID of the item we're looking at matches EITHER the ID of a "base" item + // OR the ID of an "updated" item, we're updating. + if (root.itemId === result.data.updates[i].item_id || + root.itemId === result.data.updates[i].updated_item_id) { + if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) { + continue; + } + root.isUpdating = true; + root.baseItemName = result.data.updates[i].base_item_title; + // This CertID is the one corresponding to the base item CertID that the user already owns + root.certificateId = result.data.updates[i].certificate_id; + if (root.itemType === "app") { + root.baseAppURL = result.data.updates[i].item_download_url; + } + break; + } + } + root.availableUpdatesReceived = true; + refreshBuyUI(); + } + } + + onUpdateItemResult: { + if (result.status !== 'success') { + failureErrorText.text = result.message; + root.activeView = "checkoutFailure"; + } else { + root.itemHref = result.data.download_url; + if (result.data.categories.indexOf("Wearables") > -1) { + root.itemType = "wearable"; + } + root.activeView = "checkoutSuccess"; + } + } } onItemIdChanged: { root.ownershipStatusReceived = false; Commerce.alreadyOwned(root.itemId); + root.availableUpdatesReceived = false; + Commerce.getAvailableUpdates(root.itemId); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } onItemHrefChanged: { if (root.itemHref.indexOf(".fst") > -1) { root.itemType = "avatar"; - } else if (root.itemHref.indexOf('.json.gz') > -1) { + } else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) { root.itemType = "contentSet"; } else if (root.itemHref.indexOf('.app.json') > -1) { root.itemType = "app"; } else if (root.itemHref.indexOf('.json') > -1) { root.itemType = "entity"; // "wearable" type handled later } else { - console.log("WARNING - Item type is UNKNOWN!"); - root.itemType = "entity"; + root.itemType = "unknown"; } } @@ -162,6 +210,7 @@ Rectangle { } onItemPriceChanged: { + root.balanceReceived = false; Commerce.balance(); } @@ -241,6 +290,7 @@ Rectangle { Component.onCompleted: { ownershipStatusReceived = false; balanceReceived = false; + availableUpdatesReceived = false; Commerce.getWalletStatus(); } } @@ -317,7 +367,7 @@ Rectangle { Rectangle { id: loading; z: 997; - visible: !root.ownershipStatusReceived || !root.balanceReceived; + visible: !root.ownershipStatusReceived || !root.balanceReceived || !root.availableUpdatesReceived; anchors.fill: parent; color: hifi.colors.white; @@ -413,6 +463,7 @@ Rectangle { // "HFC" balance label HiFiGlyphs { id: itemPriceTextLabel; + visible: !(root.isUpdating && root.itemEdition > 0); text: hifi.glyphs.hfc; // Size size: 30; @@ -428,9 +479,9 @@ Rectangle { } FiraSansSemiBold { id: itemPriceText; - text: (root.itemPrice === -1) ? "--" : root.itemPrice; + text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); // Text size - size: 26; + size: (root.isUpdating && root.itemEdition > 0) ? 20 : 26; // Anchors anchors.top: parent.top; anchors.right: parent.right; @@ -530,9 +581,13 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: "VIEW THIS ITEM IN MY PURCHASES"; + text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN MY PURCHASES"; onClicked: { - sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); + if (root.isUpdating) { + sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName}); + } else { + sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); + } } } @@ -540,7 +595,7 @@ Rectangle { HifiControlsUit.Button { id: buyButton; visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible) - enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified); + enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified) || root.isUpdating; color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : @@ -549,10 +604,19 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? - (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item"); + text: (root.isUpdating && root.itemEdition > 0) ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? + ((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { - if (root.isCertified) { + if (root.isUpdating && root.itemEdition > 0) { + // If we're updating an app, the existing app needs to be uninstalled. + // This call will fail/return `false` if the app isn't installed, but that's OK. + if (root.itemType === "app") { + Commerce.uninstallApp(root.baseAppURL); + } + buyButton.enabled = false; + loading.visible = true; + Commerce.updateItem(root.certificateId); + } else if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { lightboxPopup.titleText = "Purchase Content Set"; @@ -709,7 +773,7 @@ Rectangle { lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" + + lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "', '" + root.certificateId + "');" + "root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" + "UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');"; lightboxPopup.visible = true; @@ -976,7 +1040,7 @@ Rectangle { buyButton.color = hifi.buttons.red; root.shouldBuyWithControlledFailure = true; } else { - buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); + buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); buyButton.color = hifi.buttons.blue; root.shouldBuyWithControlledFailure = false; } @@ -1002,12 +1066,13 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updateCheckoutQML': - itemId = message.params.itemId; - itemName = message.params.itemName; + root.itemId = message.params.itemId; + root.itemName = message.params.itemName.trim(); root.itemPrice = message.params.itemPrice; - itemHref = message.params.itemHref; - referrer = message.params.referrer; - itemAuthor = message.params.itemAuthor; + root.itemHref = message.params.itemHref; + root.referrer = message.params.referrer; + root.itemAuthor = message.params.itemAuthor; + root.itemEdition = message.params.itemEdition || -1; refreshBuyUI(); break; default: @@ -1016,35 +1081,70 @@ Rectangle { } signal sendToScript(var message); + function canBuyAgain() { + return (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "unknown"); + } + + function handleContentSets() { + if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " + + "domain's server settings before you can replace this domain's content with " + root.itemName + ""; + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + } + } + + function handleBuyAgainLogic() { + // If you can buy this item again... + if (canBuyAgain()) { + // If you can't afford another copy of the item... + if (root.balanceAfterPurchase < 0) { + // If you already own the item... + if (root.alreadyOwned) { + buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; + // Else if you don't already own the item... + } else { + buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; + } + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + // If you CAN afford another copy of the item... + } else { + handleContentSets(); + } + } + } + function refreshBuyUI() { if (root.isCertified) { - if (root.ownershipStatusReceived && root.balanceReceived) { - if (root.balanceAfterPurchase < 0) { - if (root.alreadyOwned) { - buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; - viewInMyPurchasesButton.visible = true; + if (root.ownershipStatusReceived && root.balanceReceived && root.availableUpdatesReceived) { + buyText.text = ""; + + // If the user IS on the checkout page for the updated version of an owned item... + if (root.isUpdating) { + // If the user HAS already selected a specific edition to update... + if (root.itemEdition > 0) { + buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it."; + buyTextContainer.color = "#FFFFFF"; + buyTextContainer.border.color = "#FFFFFF"; + // Else if the user HAS NOT selected a specific edition to update... } else { - buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; - } - buyTextContainer.color = "#FFC3CD"; - buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 54; + viewInMyPurchasesButton.visible = true; + + handleBuyAgainLogic(); + } + // If the user IS NOT on the checkout page for the updated verison of an owned item... + // (i.e. they are checking out an item "normally") } else { if (root.alreadyOwned) { viewInMyPurchasesButton.visible = true; - } else { - buyText.text = ""; - } - - if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { - buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " + - "domain's server settings before you can replace this domain's content with " + root.itemName + ""; - buyTextContainer.color = "#FFC3CD"; - buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 54; } + + handleBuyAgainLogic(); } } else { buyText.text = ""; @@ -1061,13 +1161,15 @@ Rectangle { function authSuccessStep() { if (!root.debugCheckoutSuccess) { root.activeView = "checkoutMain"; + root.ownershipStatusReceived = false; + Commerce.alreadyOwned(root.itemId); + root.availableUpdatesReceived = false; + Commerce.getAvailableUpdates(root.itemId); + root.balanceReceived = false; + Commerce.balance(); } else { root.activeView = "checkoutSuccess"; } - root.balanceReceived = false; - root.ownershipStatusReceived = false; - Commerce.alreadyOwned(root.itemId); - Commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index eeda3afc71..89ac9b96e6 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -14,7 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 8eb03c1254..1b77dcd3e9 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -14,7 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -28,6 +27,7 @@ Item { property string referrerURL: (Account.metaverseServerURL + "/marketplace?"); readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin; property alias usernameDropdownVisible: usernameDropdown.visible; + property bool messagesWaiting: false; height: mainContainer.height + additionalDropdownHeight; @@ -38,6 +38,7 @@ Item { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 5) { + Commerce.getAvailableUpdates(); Commerce.getSecurityImage(); } else if (walletStatus > 5) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); @@ -58,6 +59,14 @@ Item { securityImage.source = "image://security/securityImage"; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + root.messagesWaiting = result.data.updates.length > 0; + } + } } Component.onCompleted: { @@ -134,17 +143,28 @@ Item { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToParent({method: 'header_goToPurchases'}); + sendToParent({ method: 'header_goToPurchases', hasUpdates: root.messagesWaiting }); } onEntered: myPurchasesText.color = hifi.colors.blueHighlight; onExited: myPurchasesText.color = hifi.colors.blueAccent; } } - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } + Rectangle { + id: messagesWaitingLight; + visible: root.messagesWaiting; + anchors.right: myPurchasesLink.left; + anchors.rightMargin: -2; + anchors.verticalCenter: parent.verticalCenter; + height: 10; + width: height; + radius: height/2; + color: "red"; + } + TextMetrics { id: textMetrics; - font.family: ralewayRegular.name + font.family: "Raleway" text: usernameText.text; } diff --git a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml index 9944838e03..5f874d3f04 100644 --- a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml +++ b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml @@ -14,7 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index a622349d00..d24344b40a 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -51,7 +50,24 @@ Rectangle { onCertificateInfoResult: { if (result.status !== 'success') { - console.log("Failed to get certificate info", result.message); + console.log("Failed to get certificate info", result.data.message); + // We should still tell the user that static cert verification failed + if (root.certificateStatus !== 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate Unavailable"; + popText.text = ""; + showInMarketplaceButton.visible = false; + root.certInfoReplaceMode = 0; + root.itemName = ""; + root.itemEdition = ""; + root.itemOwner = ""; + root.dateOfPurchase = ""; + root.itemCost = ""; + errorText.text = "Information about this certificate is currently unavailable. Please try again later."; + } } else { root.marketplaceUrl = result.data.marketplace_item_url; root.isMyCert = result.isMyCert ? result.isMyCert : false; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index fb8e509cde..2aac269d56 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -48,11 +48,14 @@ Item { property bool hasPermissionToRezThis; property bool permissionExplanationCardVisible; property bool isInstalled; + property string upgradeUrl; + property string upgradeTitle; + property bool isShowingMyItems; property string originalStatusText; property string originalStatusColor; - height: 110; + height: (root.upgradeUrl === "" || root.isShowingMyItems) ? 110 : 150; width: parent.width; Connections { @@ -137,6 +140,14 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: root.height - 10; + // START "incorrect indentation to prevent insane diffs" + Item { + id: itemContainer; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + height: 100; + Image { id: itemPreviewImage; source: root.itemPreviewImageUrl; @@ -357,7 +368,7 @@ Item { Item { id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged || root.numberSold > -1; anchors.left: itemName.left; anchors.top: certificateContainer.bottom; anchors.topMargin: 8; @@ -376,7 +387,7 @@ Item { "PENDING..." } else if (root.purchaseStatus === "invalidated") { "INVALIDATED" - } else if (root.numberSold !== -1) { + } else if (root.numberSold > -1) { ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) } else { "" @@ -553,7 +564,7 @@ Item { onClicked: { Tablet.playSound(TabletEnums.ButtonClick); if (root.itemType === "contentSet") { - sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref}); + sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId}); } else if (root.itemType === "avatar") { sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref}); } else if (root.itemType === "app") { @@ -634,6 +645,48 @@ Item { } } } + } + // END "incorrect indentation to prevent insane diffs" + + Rectangle { + id: upgradeAvailableContainer; + visible: root.upgradeUrl !== "" && !root.isShowingMyItems; + anchors.top: itemContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: "#B5EAFF"; + + RalewayRegular { + id: updateAvailableText; + text: "UPDATE AVAILABLE"; + size: 13; + anchors.left: parent.left; + anchors.leftMargin: 12; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + color: hifi.colors.black; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: updateNowText; + text: "Update this item now"; + size: 13; + anchors.left: updateAvailableText.right; + anchors.leftMargin: 16; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + color: hifi.colors.black; + verticalAlignment: Text.AlignVCenter; + + onLinkActivated: { + sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl}); + } + } + } } DropShadow { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index c505baebf4..9f4fc376d4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -37,6 +36,8 @@ Rectangle { property bool isDebuggingFirstUseTutorial: false; property int pendingItemCount: 0; property string installedApps; + property bool keyboardRaised: false; + property int numUpdatesAvailable: 0; // Style color: hifi.colors.white; Connections { @@ -64,6 +65,7 @@ Rectangle { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); + Commerce.getAvailableUpdates(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -119,6 +121,15 @@ Rectangle { root.pendingInventoryReply = false; } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length }); + root.numUpdatesAvailable = result.data.updates.length; + } + } } Timer { @@ -273,6 +284,7 @@ Rectangle { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); + Commerce.getAvailableUpdates(); break; } } @@ -296,6 +308,7 @@ Rectangle { // FILTER BAR START // Item { + z: 997; id: filterBarContainer; // Size height: 40; @@ -321,28 +334,61 @@ Rectangle { size: 22; } - HifiControlsUit.TextField { + HifiControlsUit.FilterBar { id: filterBar; property string previousText: ""; + property string previousPrimaryFilter: ""; colorScheme: hifi.colorSchemes.faintGray; - hasClearButton: true; - hasRoundedBorder: true; + anchors.top: parent.top; + anchors.right: parent.right; anchors.left: myText.right; anchors.leftMargin: 16; - height: 39; - anchors.verticalCenter: parent.verticalCenter; - anchors.right: parent.right; + textFieldHeight: 39; + height: textFieldHeight + dropdownHeight; placeholderText: "filter items"; + Component.onCompleted: { + var choices = [ + { + "displayName": "App", + "filterName": "app" + }, + { + "displayName": "Avatar", + "filterName": "avatar" + }, + { + "displayName": "Content Set", + "filterName": "contentSet" + }, + { + "displayName": "Entity", + "filterName": "entity" + }, + { + "displayName": "Wearable", + "filterName": "wearable" + }, + { + "displayName": "Updatable", + "filterName": "updatable" + } + ] + filterBar.primaryFilterChoices.clear(); + filterBar.primaryFilterChoices.append(choices); + } + + onPrimaryFilter_displayNameChanged: { + buildFilteredPurchasesModel(); + purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName; + } + onTextChanged: { buildFilteredPurchasesModel(); purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) filterBar.previousText = filterBar.text; } - - onAccepted: { - focus = false; - } } } // @@ -350,6 +396,7 @@ Rectangle { // HifiControlsUit.Separator { + z: 996; id: separator; colorScheme: 2; anchors.left: parent.left; @@ -377,12 +424,11 @@ Rectangle { clip: true; model: filteredPurchasesModel; snapMode: ListView.SnapToItem; - highlightRangeMode: ListView.StrictlyEnforceRange; // Anchors anchors.top: separator.bottom; anchors.topMargin: 12; anchors.left: parent.left; - anchors.bottom: parent.bottom; + anchors.bottom: updatesAvailableBanner.visible ? updatesAvailableBanner.top : parent.bottom; width: parent.width; delegate: PurchasedItem { itemName: title; @@ -398,21 +444,10 @@ Rectangle { displayedItemCount: model.displayedItemCount; permissionExplanationCardVisible: model.permissionExplanationCardVisible; isInstalled: model.isInstalled; - itemType: { - if (model.root_file_url.indexOf(".fst") > -1) { - "avatar"; - } else if (model.categories.indexOf("Wearables") > -1) { - "wearable"; - } else if (model.root_file_url.endsWith('.json.gz')) { - "contentSet"; - } else if (model.root_file_url.endsWith('.app.json')) { - "app"; - } else if (model.root_file_url.endsWith('.json')) { - "entity"; - } else { - "unknown"; - } - } + upgradeUrl: model.upgrade_url; + upgradeTitle: model.upgrade_title; + itemType: model.itemType; + isShowingMyItems: root.isShowingMyItems; anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -450,7 +485,7 @@ Rectangle { lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;"; + lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "', '" + msg.certID + "'); root.visible = false;"; lightboxPopup.visible = true; } else if (msg.method === "showChangeAvatarLightbox") { lightboxPopup.titleText = "Change Avatar"; @@ -485,15 +520,80 @@ Rectangle { filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true); } } + } else if (msg.method === "updateItemClicked") { + sendToScript(msg); } } } } } + Rectangle { + id: updatesAvailableBanner; + visible: root.numUpdatesAvailable > 0 && !root.isShowingMyItems; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 75; + color: "#B5EAFF"; + + Rectangle { + id: updatesAvailableGlyph; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 16; + // Size + width: 10; + height: width; + radius: width/2; + // Style + color: "red"; + } + + RalewaySemiBold { + text: "You have " + root.numUpdatesAvailable + " item updates available."; + // Text size + size: 18; + // Anchors + anchors.left: updatesAvailableGlyph.right; + anchors.leftMargin: 12; + height: parent.height; + width: paintedWidth; + // Style + color: hifi.colors.black; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + + HifiControlsUit.Button { + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 12; + width: 100; + height: 40; + text: "SHOW ME"; + onClicked: { + filterBar.text = ""; + filterBar.changeFilterByDisplayName("Updatable"); + } + } + } + Item { id: noItemsAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && + root.purchasesReceived && + root.isShowingMyItems && + filterBar.text === "" && + filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -539,7 +639,11 @@ Rectangle { Item { id: noPurchasesAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && + root.purchasesReceived && + !root.isShowingMyItems && + filterBar.text === "" && + filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -589,7 +693,7 @@ Rectangle { HifiControlsUit.Keyboard { id: keyboard; - raised: HMD.mounted && filterBar.focus; + raised: HMD.mounted && parent.keyboardRaised; numeric: parent.punctuationMode; anchors { bottom: parent.bottom; @@ -613,6 +717,7 @@ Rectangle { console.log("Refreshing Purchases..."); root.pendingInventoryReply = true; Commerce.inventory(); + Commerce.getAvailableUpdates(); } } } @@ -660,8 +765,13 @@ Rectangle { var sameItemCount = 0; tempPurchasesModel.clear(); + for (var i = 0; i < purchasesModel.count; i++) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { + if (!purchasesModel.get(i).valid) { + continue; + } + if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { tempPurchasesModel.insert(0, purchasesModel.get(i)); } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || @@ -671,6 +781,35 @@ Rectangle { } } + // primaryFilter filtering and adding of itemType property to model + var currentItemType, currentRootFileUrl, currentCategories; + for (var i = 0; i < tempPurchasesModel.count; i++) { + currentRootFileUrl = tempPurchasesModel.get(i).root_file_url; + currentCategories = tempPurchasesModel.get(i).categories; + + if (currentRootFileUrl.indexOf(".fst") > -1) { + currentItemType = "avatar"; + } else if (currentCategories.indexOf("Wearables") > -1) { + currentItemType = "wearable"; + } else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) { + currentItemType = "contentSet"; + } else if (currentRootFileUrl.endsWith('.app.json')) { + currentItemType = "app"; + } else if (currentRootFileUrl.endsWith('.json')) { + currentItemType = "entity"; + } else { + currentItemType = "unknown"; + } + if (filterBar.primaryFilter_displayName !== "" && + ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") || + (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { + tempPurchasesModel.remove(i); + i--; + } else { + tempPurchasesModel.setProperty(i, 'itemType', currentItemType); + } + } + for (var i = 0; i < tempPurchasesModel.count; i++) { if (!filteredPurchasesModel.get(i)) { sameItemCount = -1; @@ -682,12 +821,17 @@ Rectangle { } } - if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) { + if (sameItemCount !== tempPurchasesModel.count || + filterBar.text !== filterBar.previousText || + filterBar.primaryFilter !== filterBar.previousPrimaryFilter) { filteredPurchasesModel.clear(); var currentId; for (var i = 0; i < tempPurchasesModel.count; i++) { currentId = tempPurchasesModel.get(i).id; - + + if (!purchasesModel.get(i).valid) { + continue; + } filteredPurchasesModel.append(tempPurchasesModel.get(i)); filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); @@ -736,7 +880,7 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updatePurchases': - referrerURL = message.referrerURL; + referrerURL = message.referrerURL || ""; titleBarContainer.referrerURL = message.referrerURL; filterBar.text = message.filterText ? message.filterText : ""; break; diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index f6875bb06f..eadf1ca8a2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml index 91d2ab9f7f..8451c90836 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -24,6 +23,18 @@ Item { HifiConstants { id: hifi; } id: root; + + // This will cause a bug -- if you bring up passphrase selection in HUD mode while + // in HMD while having HMD preview enabled, then move, then finish passphrase selection, + // HMD preview will stay off. + // TODO: Fix this unlikely bug + onVisibleChanged: { + if (visible) { + sendSignalToWallet({method: 'disableHmdPreview'}); + } else { + sendSignalToWallet({method: 'maybeEnableHmdPreview'}); + } + } // Username Text RalewayRegular { diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index f1692acb3d..2ac9015c8c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -68,7 +67,7 @@ Item { propagateComposedEvents: false; hoverEnabled: true; } - + // This will cause a bug -- if you bring up passphrase selection in HUD mode while // in HMD while having HMD preview enabled, then move, then finish passphrase selection, // HMD preview will stay off. diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 50bea2a3cf..e052b78876 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -61,9 +60,6 @@ Item { if (root.shouldImmediatelyFocus) { focusFirstTextField(); } - sendMessageToLightbox({method: 'disableHmdPreview'}); - } else { - sendMessageToLightbox({method: 'maybeEnableHmdPreview'}); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 224b743c06..216d621bf8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -14,7 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index 86a4220b74..09b0096638 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -44,6 +43,17 @@ Item { } } + // This will cause a bug -- if you bring up security image selection in HUD mode while + // in HMD while having HMD preview enabled, then move, then finish passphrase selection, + // HMD preview will stay off. + // TODO: Fix this unlikely bug + onVisibleChanged: { + if (visible) { + sendSignalToWallet({method: 'disableHmdPreview'}); + } else { + sendSignalToWallet({method: 'maybeEnableHmdPreview'}); + } + } // Security Image Item { diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index 56b78c5865..599c6a1851 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -13,7 +13,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -25,18 +24,6 @@ Item { id: root; property alias currentIndex: securityImageGrid.currentIndex; - - // This will cause a bug -- if you bring up security image selection in HUD mode while - // in HMD while having HMD preview enabled, then move, then finish passphrase selection, - // HMD preview will stay off. - // TODO: Fix this unlikely bug - onVisibleChanged: { - if (visible) { - sendSignalToWallet({method: 'disableHmdPreview'}); - } else { - sendSignalToWallet({method: 'maybeEnableHmdPreview'}); - } - } SecurityImageModel { id: gridModel; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 952390a7a4..0edf2865c8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -14,7 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -174,11 +173,12 @@ Rectangle { WalletChoice { id: walletChoice; proceedFunction: function (isReset) { - console.log(isReset ? "Reset wallet." : "Trying again with new wallet."); + console.log("WalletChoice", isReset ? "Reset wallet." : "Trying again with new wallet."); Commerce.setSoftReset(); if (isReset) { walletResetSetup(); } else { + Commerce.clearWallet(); var msg = { referrer: walletChoice.referrer } followReferrer(msg); } @@ -236,6 +236,8 @@ Rectangle { } else { sendToScript(msg); } + } else { + sendToScript(msg); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 27660b5e9e..7a14ee060f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -39,6 +39,7 @@ Item { root.noMoreHistoryData = false; root.historyRequestPending = true; Commerce.history(root.currentHistoryPage); + Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); } @@ -133,6 +134,14 @@ Item { refreshTimer.start(); } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length }); + } + } } Connections { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index bad592067c..44b7ad682c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -14,7 +14,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls @@ -76,6 +75,12 @@ Item { var currentStepNumber = root.activeView.substring(5); UserActivityLogger.commerceWalletSetupProgress(timestamp, root.setupAttemptID, Math.round((timestamp - root.startingTimestamp)/1000), currentStepNumber, root.setupStepNames[currentStepNumber - 1]); + + if (root.activeView === "step_2" || root.activeView === "step_3") { + sendSignalToWallet({method: 'disableHmdPreview'}); + } else { + sendSignalToWallet({method: 'maybeEnableHmdPreview'}); + } } // @@ -441,7 +446,7 @@ Item { } Item { id: choosePassphraseContainer; - visible: root.hasShownSecurityImageTip && root.activeView === "step_3"; + visible: root.activeView === "step_3"; // Anchors anchors.top: titleBarContainer.bottom; anchors.topMargin: 30; @@ -451,10 +456,7 @@ Item { onVisibleChanged: { if (visible) { - sendSignalToWallet({method: 'disableHmdPreview'}); Commerce.getWalletAuthenticatedStatus(); - } else { - sendSignalToWallet({method: 'maybeEnableHmdPreview'}); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index 07c85a7f6a..c5989fff20 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -398,6 +398,7 @@ Item { // Item { id: filterBarContainer; + visible: !connectionInstructions.visible; // Size height: 40; // Anchors @@ -495,6 +496,78 @@ Item { } } } + + // "Make a Connection" instructions + Rectangle { + id: connectionInstructions; + visible: connectionsModel.count === 0 && !connectionsLoading.visible; + anchors.fill: parent; + color: "white"; + + RalewayRegular { + id: makeAConnectionText; + // Properties + text: "Make a Connection"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.right: parent.right; + // Text Size + size: 24; + // Text Positioning + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter; + // Style + color: hifi.colors.darkGray; + } + + Image { + id: connectionImage; + source: "qrc:/icons/connection.svg"; + width: 150; + height: 150; + mipmap: true; + // Anchors + anchors.top: makeAConnectionText.bottom; + anchors.topMargin: 15; + anchors.horizontalCenter: parent.horizontalCenter; + } + + FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } + Text { + id: connectionHelpText; + // Anchors + anchors.top: connectionImage.bottom; + anchors.topMargin: 15; + anchors.left: parent.left + anchors.leftMargin: 40; + anchors.right: parent.right + anchors.rightMargin: 40; + // Text alignment + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHLeft + // Style + font.pixelSize: 18; + font.family: ralewayRegular.name + color: hifi.colors.darkGray + wrapMode: Text.Wrap + textFormat: Text.StyledText; + property string instructions: + "When you meet someone you want to remember later, you can connect with a handshake:

" + property string hmdMountedInstructions: + "1. Put your hand out onto their hand and squeeze your controller's grip button on its side.
" + + "2. Once the other person puts their hand onto yours, you'll see your connection form.
" + + "3. After about 3 seconds, you're connected!" + property string hmdNotMountedInstructions: + "1. Press and hold the 'x' key to extend your arm.
" + + "2. Once the other person puts their hand onto yours, you'll see your connection form.
" + + "3. After about 3 seconds, you're connected!"; + // Text + text: + HMD.mounted ? instructions + hmdMountedInstructions : instructions + hmdNotMountedInstructions + } + } } } } @@ -924,14 +997,13 @@ Item { anchors.right: parent.right; anchors.rightMargin: 20; height: 95; - - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } + TextArea { id: optionalMessage; property int maximumLength: 72; property string previousText: text; placeholderText: "Optional Public Message (" + maximumLength + " character limit)"; - font.family: firaSansSemiBold.name; + font.family: "Fira Sans SemiBold"; font.pixelSize: 20; // Anchors anchors.fill: parent; diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 76484cf8c7..006a4b7158 100644 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import Qt.labs.settings 1.0 import "../../styles-uit" diff --git a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml index 5de0864df5..b4357a0077 100644 --- a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml @@ -1,5 +1,5 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 1.5 import QtQuick.XmlListModel 2.0 import QtQuick.Controls.Styles 1.4 @@ -104,7 +104,7 @@ ModalWindow { id: acceptAction text: qsTr("OK") enabled: root.result ? true : false - shortcut: Qt.Key_Return + shortcut: "Return" onTriggered: { root.selected(root.result); root.destroy(); @@ -114,7 +114,7 @@ ModalWindow { Action { id: cancelAction text: qsTr("Cancel") - shortcut: Qt.Key_Escape + shortcut: "Esc" onTriggered: { root.canceled(); root.destroy(); diff --git a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml deleted file mode 100644 index 1388889248..0000000000 --- a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick 2.5 -import Qt.labs.settings 1.0 - -import "../../dialogs" - -PreferencesDialog { - id: root - objectName: "GraphicsPreferencesDialog" - title: "Graphics Settings" - showCategories: ["Graphics Quality"] - property var settings: Settings { - category: root.objectName - property alias x: root.x - property alias y: root.y - property alias width: root.width - property alias height: root.height - } -} - diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index c427052904..55b4e98bf5 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../windows" import "content" diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 00273171df..9e3ebcbab0 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -25,6 +25,7 @@ ScrollingWindow { resizable: true destroyOnHidden: false implicitWidth: 424 + opacity: parent.opacity implicitHeight: isHMD ? 695 : 728 minSize: Qt.vector2d(424, 300) diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 0f70b44477..6bf8f8a5d5 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -40,6 +40,7 @@ Rectangle { property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; property var selectedItemCount: treeView.selection.selectedIndexes.length; + property int updatesCount: 0; // this is used for notifying model-dependent bindings about model updates Settings { category: "Overlay.AssetServer" @@ -51,6 +52,9 @@ Rectangle { ApplicationInterface.uploadRequest.connect(uploadClicked); assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError); assetMappingsModel.autoRefreshEnabled = true; + assetMappingsModel.updated.connect(function() { + ++updatesCount; + }); reload(); } @@ -58,7 +62,7 @@ Rectangle { Component.onDestruction: { assetMappingsModel.autoRefreshEnabled = false; } - + function letterbox(headerGlyph, headerText, message) { letterboxMessage.headerGlyph = headerGlyph; letterboxMessage.headerText = headerText; @@ -66,7 +70,7 @@ Rectangle { letterboxMessage.visible = true; letterboxMessage.popupRadius = 0; } - + function errorMessageBox(message) { return tabletRoot.messageBox({ icon: hifi.icons.warning, @@ -145,7 +149,7 @@ Rectangle { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; - + if (selectedItemCount > 1) { return false; } @@ -154,8 +158,8 @@ Rectangle { return total | new RegExp(current).test(path); }, false); } - - function canRename() { + + function canRename() { if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { @@ -199,7 +203,7 @@ Rectangle { var SHAPE_TYPE_STATIC_MESH = 3; var SHAPE_TYPE_BOX = 4; var SHAPE_TYPE_SPHERE = 5; - + var SHAPE_TYPES = []; SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; @@ -207,7 +211,7 @@ Rectangle { SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND; var DYNAMIC_DEFAULT = false; var prompt = tabletRoot.customInputDialog({ @@ -349,14 +353,14 @@ Rectangle { } function deleteFile(index) { var paths = []; - + if (!index) { for (var i = 0; i < selectedItemCount; ++i) { index = treeView.selection.selectedIndexes[i]; paths[i] = assetProxyModel.data(index, 0x100); } } - + if (!paths) { return; } @@ -365,7 +369,7 @@ Rectangle { var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { @@ -476,7 +480,7 @@ Rectangle { }); } } - + // The letterbox used for popup messages LetterboxMessage { id: letterboxMessage; @@ -540,7 +544,7 @@ Rectangle { anchors.margins: hifi.dimensions.contentMargin.x + 2 // Extra for border anchors.left: parent.left anchors.right: parent.right - + treeModel: assetProxyModel selectionMode: SelectionMode.ExtendedSelection headerVisible: true @@ -560,9 +564,13 @@ Rectangle { id: bakedColumn title: "Use Baked?" role: "baked" - width: 100 + width: 170 } - + + onSortIndicatorOrderChanged: { + Assets.sortProxyModel(sortIndicatorColumn, sortIndicatorOrder); + } + itemDelegate: Loader { id: itemDelegateLoader @@ -598,7 +606,7 @@ Rectangle { } sourceComponent: getComponent() - + Component { id: labelComponent FiraSansSemiBold { @@ -607,15 +615,15 @@ Rectangle { color: colorScheme == hifi.colorSchemes.light ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - + horizontalAlignment: styleData.column === 1 ? TextInput.AlignHCenter : TextInput.AlignLeft - + elide: Text.ElideMiddle MouseArea { id: mouseArea anchors.fill: parent - + acceptedButtons: Qt.NoButton hoverEnabled: true @@ -637,7 +645,7 @@ Rectangle { color: colorScheme == hifi.colorSchemes.light ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - + elide: Text.ElideRight horizontalAlignment: TextInput.AlignHCenter @@ -659,8 +667,7 @@ Rectangle { text: styleData.value - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - font.family: firaSansSemiBold.name + font.family: "Fira Sans SemiBold" font.pixelSize: hifi.fontSizes.textFieldInput height: hifi.dimensions.tableRowHeight @@ -725,7 +732,7 @@ Rectangle { size: hifi.fontSizes.tableText color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText } - + Timer { id: showTimer interval: 1000 @@ -744,7 +751,7 @@ Rectangle { treeLabelToolTip.visible = false; } }// End_OF( treeLabelToolTip ) - + MouseArea { propagateComposedEvents: true anchors.fill: parent @@ -802,7 +809,7 @@ Rectangle { anchors.left: treeView.left anchors.right: treeView.right anchors.bottomMargin: hifi.dimensions.contentSpacing.y - + RalewayRegular { anchors.verticalCenter: parent.verticalCenter @@ -846,13 +853,18 @@ Rectangle { checked = Qt.binding(isChecked); } - + + function getStatus() { + // kind of hack for ensuring getStatus() will be re-evaluated on updatesCount changes + return updatesCount, assetProxyModel.data(treeView.selection.currentIndex, 0x105); + } + function isEnabled() { if (!treeView.selection.hasSelection) { return false; } - var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); + var status = getStatus(); if (status === "--") { return false; } @@ -870,18 +882,18 @@ Rectangle { } } - return true; + return true; } function isChecked() { if (!treeView.selection.hasSelection) { return false; } - var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); - return isEnabled() && status !== "Not Baked"; - } + var status = getStatus(); + return isEnabled() && status !== "Not Baked"; + } } - + Item { anchors.verticalCenter: parent.verticalCenter width: infoGlyph.size; @@ -905,7 +917,7 @@ Rectangle { "What is baking?", "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } - } + } }// End_OF( infoRow ) HifiControls.TabletContentSection { diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index 0f363d1be9..1ec7ea3e0e 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import Qt.labs.settings 1.0 import "../../styles-uit" diff --git a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml index 22e9dc07a2..50df4dedbc 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import Hifi 1.0 as Hifi import "../../styles-uit" @@ -69,9 +69,8 @@ Rectangle { id: textArea width: parent.width height: parent.height - backgroundVisible: false - textColor: hifi.colors.white + background: Item {} + color: hifi.colors.white text:"" } - } diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index da295917a0..7bce460283 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import Qt.labs.settings 1.0 import "../../styles-uit" diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml index 894a4c1813..d5c5a5ee02 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls diff --git a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml index 2291a42bf6..ab53f03477 100644 --- a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml +++ b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index b6f906ffc2..d93e077b5a 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -1,8 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs -import Qt.labs.settings 1.0 import "." import ".." @@ -184,7 +180,7 @@ Item { decimals: 2; minimumValue: 0.01 maximumValue: 10 - stepSize: 0.05; + realStepSize: 0.05; value: attachment ? attachment.scale : 1.0 colorScheme: hifi.colorSchemes.dark onValueChanged: { diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml index 29f2c0ebf4..228d71fe6f 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControls @@ -57,12 +56,12 @@ Item { colorScheme: hifi.colorSchemes.dark colorLabelInside: hifi.colors.redHighlight decimals: root.decimals - stepSize: root.stepSize + realStepSize: root.stepSize maximumValue: root.maximumValue minimumValue: root.minimumValue - onValueChanged: { - if (value !== vector.x) { - vector.x = value + onRealValueChanged: { + if (realValue !== vector.x) { + vector.x = realValue root.valueChanged(); } } diff --git a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml index 0e0786975e..fa71116574 100644 --- a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml @@ -1,7 +1,6 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.3 import QtQuick.Dialogs 1.2 as OriginalDialogs -import QtQuick.Controls.Styles 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControls diff --git a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml index 50fca94ff1..ce1abc6154 100644 --- a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml @@ -1,5 +1,5 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.3 import "../../../controls-uit" as HifiControls @@ -44,7 +44,7 @@ Column { id: acceptAction text: qsTr("OK") enabled: root.result ? true : false - shortcut: Qt.Key_Return + shortcut: "Return" onTriggered: { root.selected(root.result); root.destroy(); @@ -54,7 +54,7 @@ Column { Action { id: cancelAction text: qsTr("Cancel") - shortcut: Qt.Key_Escape + shortcut: "Esc" onTriggered: { root.canceled(); root.destroy(); diff --git a/interface/resources/qml/hifi/overlays/ImageOverlay.qml b/interface/resources/qml/hifi/overlays/ImageOverlay.qml index cbcf6c7910..f3fbb88c07 100644 --- a/interface/resources/qml/hifi/overlays/ImageOverlay.qml +++ b/interface/resources/qml/hifi/overlays/ImageOverlay.qml @@ -1,5 +1,4 @@ import QtQuick 2.3 -import QtQuick.Controls 1.2 import QtGraphicalEffects 1.0 import "." @@ -20,17 +19,7 @@ Overlay { repeat: false running: false onTriggered: { - if (image.xSize === 0) { - image.xSize = image.sourceSize.width - image.xStart; - } - if (image.ySize === 0) { - image.ySize = image.sourceSize.height - image.yStart; - } - - image.anchors.leftMargin = -image.xStart * root.width / image.xSize; - image.anchors.topMargin = -image.yStart * root.height / image.ySize; - image.anchors.rightMargin = (image.xStart + image.xSize - image.sourceSize.width) * root.width / image.xSize; - image.anchors.bottomMargin = (image.yStart + image.ySize - image.sourceSize.height) * root.height / image.ySize; + recalculateMargins(); } } @@ -44,6 +33,20 @@ Overlay { anchors.fill: parent } + function recalculateMargins() { + if (image.xSize === 0) { + image.xSize = image.sourceSize.width - image.xStart; + } + if (image.ySize === 0) { + image.ySize = image.sourceSize.height - image.yStart; + } + + image.anchors.leftMargin = -image.xStart * root.width / image.xSize; + image.anchors.topMargin = -image.yStart * root.height / image.ySize; + image.anchors.rightMargin = (image.xStart + image.xSize - image.sourceSize.width) * root.width / image.xSize; + image.anchors.bottomMargin = (image.yStart + image.ySize - image.sourceSize.height) * root.height / image.ySize; + } + ColorOverlay { id: color anchors.fill: image @@ -62,6 +65,7 @@ Overlay { case "height": image.ySize = value; break; } } + recalculateMargins(); } function updatePropertiesFromScript(properties) { diff --git a/interface/resources/qml/hifi/overlays/Overlay.qml b/interface/resources/qml/hifi/overlays/Overlay.qml index 80f3233b69..188833bf88 100644 --- a/interface/resources/qml/hifi/overlays/Overlay.qml +++ b/interface/resources/qml/hifi/overlays/Overlay.qml @@ -1,6 +1,5 @@ import Hifi 1.0 import QtQuick 2.3 -import QtQuick.Controls 1.2 Item { id: root diff --git a/interface/resources/qml/hifi/overlays/RectangleOverlay.qml b/interface/resources/qml/hifi/overlays/RectangleOverlay.qml index 514b646c36..d4e70d59d5 100644 --- a/interface/resources/qml/hifi/overlays/RectangleOverlay.qml +++ b/interface/resources/qml/hifi/overlays/RectangleOverlay.qml @@ -1,5 +1,4 @@ import QtQuick 2.3 -import QtQuick.Controls 1.2 import "." diff --git a/interface/resources/qml/hifi/overlays/TextOverlay.qml b/interface/resources/qml/hifi/overlays/TextOverlay.qml index 301a2aa0bf..6d7d2e19b5 100644 --- a/interface/resources/qml/hifi/overlays/TextOverlay.qml +++ b/interface/resources/qml/hifi/overlays/TextOverlay.qml @@ -1,5 +1,4 @@ import QtQuick 2.3 -import QtQuick.Controls 1.2 import "." @@ -20,6 +19,7 @@ Overlay { font.family: "Helvetica" font.pixelSize: 18 lineHeight: 18 + clip: true } } diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml index dd56bc96ab..6c26bd87c5 100644 --- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -8,11 +8,8 @@ import QtQuick 2.5 - -import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 -import QtQuick.Controls.Styles 1.4 + import "../../styles-uit" import "../../controls" import "../../controls-uit" as HifiControls diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index b71719aa48..51dd19bfdd 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -6,8 +6,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 import "../../styles-uit" diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index efd797e12d..4acced86ce 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -1,23 +1,46 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "../../styles-uit" +import QtQuick 2.7 +import QtQuick.Controls 2.3 StackView { id: editRoot objectName: "stack" - initialItem: Qt.resolvedUrl('EditTabView.qml') signal sendToScript(var message); - HifiConstants { id: hifi } + topPadding: 40 + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + property var itemProperties: {"y": editRoot.topPadding, + "width": editRoot.availableWidth, + "height": editRoot.availableHeight } + Component.onCompleted: { + tab.currentIndex = 0 + } + + background: Rectangle { + color: "#404040" //default background color + EditTabView { + id: tab + anchors.fill: parent + currentIndex: -1 + onCurrentIndexChanged: { + editRoot.replace(null, tab.itemAt(currentIndex).visualItem, + itemProperties, + StackView.Immediate) + } + } + } function pushSource(path) { - editRoot.push(Qt.resolvedUrl("../../" + path)); + editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties, + StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); } function popSource() { - editRoot.pop(); + editRoot.pop(StackView.Immediate); } // Passes script messages to the item on the top of the stack @@ -27,3 +50,4 @@ StackView { currentItem.fromScript(message); } } + diff --git a/interface/resources/qml/hifi/tablet/EditTabButton.qml b/interface/resources/qml/hifi/tablet/EditTabButton.qml new file mode 100644 index 0000000000..13894f4d15 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/EditTabButton.qml @@ -0,0 +1,56 @@ +// +// EditTabButton.qml +// qml/hifi/tablet +// +// Created by Vlad Stelmahovsky on 8/16/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import "../../controls-uit" as HifiControls +import "../../styles-uit" + +TabButton { + id: control + property alias title: control.text + property alias active: control.checkable + height: 40 + padding: 0 + spacing: 0 + HifiConstants { id: hifi; } + + contentItem: Text { + id: text + text: control.text + font.pixelSize: 14 + font.bold: true + color: "white" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + fontSizeMode: Text.HorizontalFit + property string glyphtext: "" + HiFiGlyphs { + anchors.centerIn: parent + size: 30 + color: "#ffffff" + text: text.glyphtext + } + Component.onCompleted: { + if (control.text === "P") { + text.text = " "; + text.glyphtext = "\ue004"; + } + } + } + + background: Rectangle { + color: control.checked ? "#404040" :"black" + implicitWidth: control.contentItem.width + 42 > text.paintedWidth ? control.contentItem.width + 42 : + text.paintedWidth + 10 + implicitHeight: 40 + } +} diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index e419b04848..9a7958f95c 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -1,316 +1,286 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls 2.2 // Need both for short-term fix -import QtWebEngine 1.1 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import QtQuick.Controls.Styles 1.4 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 import "../../controls-uit" as HifiControls import "../../styles-uit" - - -TabView { +TabBar { id: editTabView // anchors.fill: parent - height: 60 + width: parent.width + contentWidth: parent.width + padding: 0 + spacing: 0 - Tab { + readonly property HifiConstants hifi: HifiConstants {} + + EditTabButton { title: "CREATE" active: true enabled: true property string originalUrl: "" - Rectangle { - color: "#404040" - id: container + property Component visualItem: Component { - Flickable { - height: parent.height - width: parent.width + Rectangle { + color: "#404040" + id: container - contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height + - header.anchors.topMargin + createEntitiesFlow.anchors.topMargin + - assetServerButton.anchors.topMargin + importButton.anchors.topMargin - contentWidth: width + Flickable { + height: parent.height + width: parent.width + clip: true - ScrollBar.vertical : ScrollBar { - visible: parent.contentHeight > parent.height - width: 20 - background: Rectangle { - color: hifi.colors.tableScrollBackgroundDark - } - } + contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height + + header.anchors.topMargin + createEntitiesFlow.anchors.topMargin + + assetServerButton.anchors.topMargin + importButton.anchors.topMargin + + header.paintedHeight - Text { - id: header - color: "#ffffff" - text: "Choose an Entity Type to Create:" - font.pixelSize: 14 - font.bold: true - anchors.top: parent.top - anchors.topMargin: 28 - anchors.left: parent.left - anchors.leftMargin: 28 - } + contentWidth: width - Flow { - id: createEntitiesFlow - spacing: 35 - anchors.right: parent.right - anchors.rightMargin: 55 - anchors.left: parent.left - anchors.leftMargin: 55 - anchors.top: parent.top - anchors.topMargin: 70 + ScrollBar.vertical : ScrollBar { + visible: parent.contentHeight > parent.height + width: 20 + background: Rectangle { + color: hifi.colors.tableScrollBackgroundDark + } + } + + Text { + id: header + color: "#ffffff" + text: "Choose an Entity Type to Create:" + font.pixelSize: 14 + font.bold: true + anchors.top: parent.top + anchors.topMargin: 28 + anchors.left: parent.left + anchors.leftMargin: 28 + } + + Flow { + id: createEntitiesFlow + spacing: 35 + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: parent.top + anchors.topMargin: 70 - NewEntityButton { - icon: "icons/create-icons/94-model-01.svg" - text: "MODEL" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newModelButton" } - }); - editTabView.currentIndex = 2 + NewEntityButton { + icon: "icons/create-icons/94-model-01.svg" + text: "MODEL" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newModelButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/21-cube-01.svg" + text: "CUBE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/22-sphere-01.svg" + text: "SPHERE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/24-light-01.svg" + text: "LIGHT" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newLightButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/20-text-01.svg" + text: "TEXT" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newTextButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/image.svg" + text: "IMAGE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newImageButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/25-web-1-01.svg" + text: "WEB" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newWebButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/23-zone-01.svg" + text: "ZONE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/90-particles-01.svg" + text: "PARTICLE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } + }); + editTabView.currentIndex = 4 + } + } + + NewEntityButton { + icon: "icons/create-icons/126-material-01.svg" + text: "MATERIAL" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" } + }); + editTabView.currentIndex = 2 + } + } + } + + HifiControls.Button { + id: assetServerButton + text: "Open This Domain's Asset Server" + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: createEntitiesFlow.bottom + anchors.topMargin: 35 + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" } + }); + } + } + + HifiControls.Button { + id: importButton + text: "Import Entities (.json)" + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: assetServerButton.bottom + anchors.topMargin: 20 + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" } + }); + } } } - - NewEntityButton { - icon: "icons/create-icons/21-cube-01.svg" - text: "CUBE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/22-sphere-01.svg" - text: "SPHERE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/24-light-01.svg" - text: "LIGHT" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newLightButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/20-text-01.svg" - text: "TEXT" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newTextButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/image.svg" - text: "IMAGE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newImageButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/25-web-1-01.svg" - text: "WEB" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newWebButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/23-zone-01.svg" - text: "ZONE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/90-particles-01.svg" - text: "PARTICLE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } - }); - editTabView.currentIndex = 4 - } - } - - NewEntityButton { - icon: "icons/create-icons/126-material-01.svg" - text: "MATERIAL" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" } - }); - editTabView.currentIndex = 2 - } - } - } - - HifiControls.Button { - id: assetServerButton - text: "Open This Domain's Asset Server" - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - anchors.right: parent.right - anchors.rightMargin: 55 - anchors.left: parent.left - anchors.leftMargin: 55 - anchors.top: createEntitiesFlow.bottom - anchors.topMargin: 35 - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" } - }); - } - } - - HifiControls.Button { - id: importButton - text: "Import Entities (.json)" - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - anchors.right: parent.right - anchors.rightMargin: 55 - anchors.left: parent.left - anchors.leftMargin: 55 - anchors.top: assetServerButton.bottom - anchors.topMargin: 20 - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" } - }); - } - } + } // Flickable } - } // Flickable } - Tab { + EditTabButton { title: "LIST" active: true enabled: true property string originalUrl: "" - WebView { - id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" - anchors.fill: parent - enabled: true + property Component visualItem: Component { + WebView { + id: entityListToolWebView + url: Paths.defaultScripts + "/system/html/entityList.html" + enabled: true + } } } - Tab { + EditTabButton { title: "PROPERTIES" active: true enabled: true property string originalUrl: "" - WebView { - id: entityPropertiesWebView - url: Paths.defaultScripts + "/system/html/entityProperties.html" - anchors.fill: parent - enabled: true + property Component visualItem: Component { + WebView { + id: entityPropertiesWebView + url: Paths.defaultScripts + "/system/html/entityProperties.html" + enabled: true + } } } - Tab { + EditTabButton { title: "GRID" active: true enabled: true property string originalUrl: "" - WebView { - id: gridControlsWebView - url: Paths.defaultScripts + "/system/html/gridControls.html" - anchors.fill: parent - enabled: true + property Component visualItem: Component { + WebView { + id: gridControlsWebView + url: Paths.defaultScripts + "/system/html/gridControls.html" + enabled: true + } } } - Tab { + EditTabButton { title: "P" active: true enabled: true property string originalUrl: "" - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - anchors.fill: parent - enabled: true - } - } - - - style: TabViewStyle { - frameOverlap: 1 - tab: Rectangle { - color: styleData.selected ? "#404040" :"black" - implicitWidth: text.width + 42 - implicitHeight: 40 - Text { - id: text - anchors.centerIn: parent - text: styleData.title - font.pixelSize: 16 - font.bold: true - color: styleData.selected ? "white" : "white" - property string glyphtext: "" - HiFiGlyphs { - anchors.centerIn: parent - size: 30 - color: "#ffffff" - text: text.glyphtext - } - Component.onCompleted: if (styleData.title == "P") { - text.text = " "; - text.glyphtext = "\ue004"; - } + property Component visualItem: Component { + WebView { + id: particleExplorerWebView + url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" + enabled: true } } - tabBar: Rectangle { - color: "black" - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - } } function fromScript(message) { diff --git a/interface/resources/qml/hifi/tablet/InputRecorder.qml b/interface/resources/qml/hifi/tablet/InputRecorder.qml index 292deb751e..527a6cacb4 100644 --- a/interface/resources/qml/hifi/tablet/InputRecorder.qml +++ b/interface/resources/qml/hifi/tablet/InputRecorder.qml @@ -8,8 +8,6 @@ import QtQuick 2.5 import Hifi 1.0 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs import "../../styles-uit" import "../../controls-uit" as HifiControls diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml index c7fbc9d45a..6df97e67b0 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import QtQuick.Dialogs 1.2 as OriginalDialogs import "../../styles-uit" diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 3debc8b9e7..a349653525 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -10,7 +10,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import "../../styles-uit" @@ -71,6 +70,14 @@ Rectangle { onAccepted: { newModelDialog.keyboardEnabled = false; } + + onTextChanged : { + if (modelURL.text.length === 0){ + button1.enabled = false; + } else { + button1.enabled = true; + } + } MouseArea { anchors.fill: parent @@ -200,6 +207,7 @@ Rectangle { id: button1 text: qsTr("Add") z: -1 + enabled: false onClicked: { newModelDialog.sendToScript({ method: "newModelDialogAdd", diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 0978f23013..bd3a95bca0 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -7,10 +7,8 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 + import "../../styles-uit" import "../../controls" import "../../controls-uit" as HifiControls @@ -623,9 +621,6 @@ Rectangle { } } - - - HiFiGlyphs { id: glyphButton color: enabled ? hifi.buttons.textColor[calibrationButton.color] @@ -655,7 +650,6 @@ Rectangle { } } - MouseArea { anchors.fill: parent hoverEnabled: true diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index d4550d3843..dc67494e27 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -9,8 +9,8 @@ // import Hifi 1.0 -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import "../../controls" @@ -113,7 +113,6 @@ StackView { id: addressBarDialog property bool keyboardEnabled: false - property bool keyboardRaised: false property bool punctuationMode: false width: parent.width @@ -142,7 +141,10 @@ StackView { bottom: parent.bottom } - onHostChanged: updateLocationTextTimer.restart(); + onHostChanged: { + updateLocationTextTimer.restart(); + DialogsManager.hideAddressBar(); + } Rectangle { id: navBar @@ -401,11 +403,10 @@ StackView { addressLine.text = ""; } } - HifiControls.Keyboard { id: keyboard - raised: parent.keyboardEnabled && parent.keyboardRaised + raised: parent.keyboardEnabled numeric: parent.punctuationMode anchors { bottom: parent.bottom @@ -413,7 +414,7 @@ StackView { right: parent.right } } - + } function updateLocationText(enteringAddress) { diff --git a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml index 19548365aa..7e1cdea0db 100644 --- a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../controls-uit" as HifiControls import "../../styles-uit" diff --git a/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml b/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml index 1b4d0feaca..239c2452d4 100644 --- a/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml +++ b/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "tabletWindows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml index 94fb29c6a1..e824036587 100644 --- a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 import "tabletWindows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml index dee2eed9c3..810f5bb43f 100644 --- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "tabletWindows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml index 67794e8f14..8d600975ed 100644 --- a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "tabletWindows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml index b502c26245..ddc116371d 100644 --- a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "tabletWindows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 520841b33f..74f175e049 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -10,7 +10,6 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "../../controls-uit" import "../../styles-uit" diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 636ebfe71f..2d4d31b9aa 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import TabletScriptingInterface 1.0 import "../../styles-uit" diff --git a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml index 60bd7a88e0..d69d760b95 100644 --- a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import "../../controls-uit" as HifiControls import "../../styles-uit" diff --git a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml index 91d6140fc3..bad546a39c 100644 --- a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import "tabletWindows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 8406f23ab2..15db5d8f88 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -1,6 +1,5 @@ import QtQuick 2.0 import Hifi 1.0 -import QtQuick.Controls 1.4 import "../../dialogs" import "../../controls" diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index ead63537f0..08b0104fce 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -478,9 +478,6 @@ Rectangle { itemDelegate: Item { clip: true - //FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } - //FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; } - FiraSansSemiBold { text: getText(); elide: styleData.elideMode @@ -494,7 +491,7 @@ Rectangle { size: hifi.fontSizes.tableText color: hifi.colors.baseGrayHighlight //font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) - //? firaSansSemiBold.name : firaSansRegular.name + //? "Fira Sans SemiBold" : "Fira Sans" function getText() { if (styleData.row === -1) { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index fcb3e9ff92..a5ef3d2686 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -9,10 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtGraphicalEffects 1.0 - import "." import "./preferences" diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml index 9986c85445..fcf022c8ef 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Preference.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 Item { id: root diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 8cf254809d..5036569031 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 import Hifi 1.0 import "../../../../dialogs/preferences" diff --git a/interface/resources/qml/hifi/toolbars/StateImage.qml b/interface/resources/qml/hifi/toolbars/StateImage.qml index ebf1544f2b..9cd311060d 100644 --- a/interface/resources/qml/hifi/toolbars/StateImage.qml +++ b/interface/resources/qml/hifi/toolbars/StateImage.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 Item { property alias imageURL: image.source diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index ff7e835690..49aff06929 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 import Qt.labs.settings 1.0 import "../../windows" diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 7781d1140b..232973b4d3 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 StateImage { id: button diff --git a/interface/resources/qml/styles-uit/+android/HifiConstants.qml b/interface/resources/qml/styles-uit/+android/HifiConstants.qml new file mode 100644 index 0000000000..d5fab57501 --- /dev/null +++ b/interface/resources/qml/styles-uit/+android/HifiConstants.qml @@ -0,0 +1,358 @@ +// +// HiFiConstants.qml +// +// Created by Bradley Austin Davis on 28 Apr 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Window 2.2 + +Item { + readonly property alias colors: colors + readonly property alias colorSchemes: colorSchemes + readonly property alias dimensions: dimensions + readonly property alias fontSizes: fontSizes + readonly property alias glyphs: glyphs + readonly property alias icons: icons + readonly property alias buttons: buttons + readonly property alias effects: effects + + function glyphForIcon(icon) { + // Translates icon enum to glyph char. + var glyph; + switch (icon) { + case hifi.icons.information: + glyph = hifi.glyphs.info; + break; + case hifi.icons.question: + glyph = hifi.glyphs.question; + break; + case hifi.icons.warning: + glyph = hifi.glyphs.alert; + break; + case hifi.icons.critical: + glyph = hifi.glyphs.error; + break; + case hifi.icons.placemark: + glyph = hifi.glyphs.placemark; + break; + default: + glyph = hifi.glyphs.noIcon; + } + return glyph; + } + + Item { + id: colors + + // Base colors + readonly property color baseGray: "#393939" + readonly property color darkGray: "#121212" + readonly property color baseGrayShadow: "#252525" + readonly property color baseGrayHighlight: "#575757" + readonly property color lightGray: "#6a6a6a" + readonly property color lightGrayText: "#afafaf" + readonly property color faintGray: "#e3e3e3" + readonly property color primaryHighlight: "#00b4ef" + readonly property color blueHighlight: "#00b4ef" + readonly property color blueAccent: "#0093C5" + readonly property color redHighlight: "#EA4C5F" + readonly property color redAccent: "#C62147" + readonly property color greenHighlight: "#1ac567" + readonly property color greenShadow: "#359D85" + readonly property color orangeHighlight: "#FFC49C" + readonly property color orangeAccent: "#FF6309" + readonly property color indigoHighlight: "#C0D2FF" + readonly property color indigoAccent: "#9495FF" + readonly property color magentaHighlight: "#EF93D1" + readonly property color magentaAccent: "#A2277C" + readonly property color checkboxCheckedRed: "#FF0000" + readonly property color checkboxCheckedBorderRed: "#D00000" + readonly property color lightBlueHighlight: "#d6f6ff" + + // Semitransparent + readonly property color darkGray30: "#4d121212" + readonly property color darkGray0: "#00121212" + readonly property color baseGrayShadow60: "#99252525" + readonly property color baseGrayShadow50: "#80252525" + readonly property color baseGrayShadow25: "#40252525" + readonly property color baseGrayHighlight40: "#66575757" + readonly property color baseGrayHighlight15: "#26575757" + readonly property color lightGray50: "#806a6a6a" + readonly property color lightGrayText80: "#ccafafaf" + readonly property color faintGray80: "#cce3e3e3" + readonly property color faintGray50: "#80e3e3e3" + + // Other colors + readonly property color white: "#ffffff" + readonly property color gray: "#808080" + readonly property color black: "#000000" + readonly property color locked: "#252525" + // Semitransparent + readonly property color white50: "#80ffffff" + readonly property color white30: "#4dffffff" + readonly property color white25: "#40ffffff" + readonly property color transparent: "#00ffffff" + + // Control specific colors + readonly property color tableRowLightOdd: "#fafafa" + readonly property color tableRowLightEven: "#eeeeee" // Equivavlent to "#1a575757" over #e3e3e3 background + readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background + readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background + readonly property color tableBackgroundLight: tableRowLightEven + readonly property color tableBackgroundDark: tableRowDarkEven + readonly property color tableScrollHandleLight: "#DDDDDD" + readonly property color tableScrollHandleDark: "#707070" + readonly property color tableScrollBackgroundLight: tableRowLightOdd + readonly property color tableScrollBackgroundDark: "#323232" + readonly property color checkboxLightStart: "#ffffff" + readonly property color checkboxLightFinish: "#afafaf" + readonly property color checkboxDarkStart: "#7d7d7d" + readonly property color checkboxDarkFinish: "#6b6a6b" + readonly property color checkboxChecked: primaryHighlight + readonly property color checkboxCheckedBorder: "#36cdff" + readonly property color sliderGutterLight: "#d4d4d4" + readonly property color sliderGutterDark: "#252525" + readonly property color sliderBorderLight: "#afafaf" + readonly property color sliderBorderDark: "#7d7d7d" + readonly property color sliderLightStart: "#ffffff" + readonly property color sliderLightFinish: "#afafaf" + readonly property color sliderDarkStart: "#7d7d7d" + readonly property color sliderDarkFinish: "#6b6a6b" + readonly property color dropDownPressedLight: "#d4d4d4" + readonly property color dropDownPressedDark: "#afafaf" + readonly property color dropDownLightStart: "#ffffff" + readonly property color dropDownLightFinish: "#afafaf" + readonly property color dropDownDarkStart: "#7d7d7d" + readonly property color dropDownDarkFinish: "#6b6a6b" + readonly property color textFieldLightBackground: "#d4d4d4" + readonly property color tabBackgroundDark: "#252525" + readonly property color tabBackgroundLight: "#d4d4d4" + } + + Item { + id: colorSchemes + readonly property int light: 0 + readonly property int dark: 1 + readonly property int faintGray: 2 + } + + Item { + id: dimensions + readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080 + readonly property real borderRadius: largeScreen ? 7.5 : 5.0 + readonly property real borderWidth: largeScreen ? 2 : 1 + readonly property vector2d contentMargin: Qt.vector2d(21, 21) + readonly property vector2d contentSpacing: Qt.vector2d(11, 14) + readonly property real labelPadding: 40 + readonly property real textPadding: 8 + readonly property real sliderHandleSize: 18 + readonly property real sliderGrooveHeight: 8 + readonly property real frameIconSize: 22 + readonly property real spinnerSize: 50 + readonly property real tablePadding: 12 + readonly property real tableRowHeight: largeScreen ? 26 : 23 + readonly property real tableHeaderHeight: 29 + readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) + readonly property real modalDialogTitleHeight: 120 + readonly property real controlLineHeight: 84 // Height of spinbox control on 1920 x 1080 monitor + readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight + readonly property vector2d menuPadding: Qt.vector2d(14, 102) + readonly property real scrollbarBackgroundWidth: 20 + readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 + readonly property real tabletMenuHeader: 90 + readonly property real buttonWidth: 360 + } + + Item { + id: fontSizes // In pixels + readonly property real overlayTitle: dimensions.largeScreen ? 54 : 42 + readonly property real tabName: dimensions.largeScreen ? 12 : 10 + readonly property real sectionName: dimensions.largeScreen ? 36 : 30 + readonly property real inputLabel: dimensions.largeScreen ? 14 : 10 + readonly property real textFieldInput: dimensions.largeScreen ? 48 : 36 + readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 + readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 + readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 + readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33 + readonly property real tableText: dimensions.largeScreen ? 15 : 12 + readonly property real buttonLabel: dimensions.largeScreen ? 42 : 27 + readonly property real iconButton: dimensions.largeScreen ? 13 : 9 + readonly property real listItem: dimensions.largeScreen ? 15 : 11 + readonly property real tabularData: dimensions.largeScreen ? 15 : 11 + readonly property real logs: dimensions.largeScreen ? 16 : 12 + readonly property real code: dimensions.largeScreen ? 16 : 12 + readonly property real rootMenu: dimensions.largeScreen ? 15 : 11 + readonly property real rootMenuDisclosure: dimensions.largeScreen ? 20 : 16 + readonly property real menuItem: dimensions.largeScreen ? 45 : 33 + readonly property real shortcutText: dimensions.largeScreen ? 39 : 27 + readonly property real carat: dimensions.largeScreen ? 38 : 30 + readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22 + } + + Item { + id: icons + // Values per OffscreenUi::Icon + readonly property int none: 0 + readonly property int question: 1 + readonly property int information: 2 + readonly property int warning: 3 + readonly property int critical: 4 + readonly property int placemark: 5 + } + + Item { + id: buttons + readonly property int white: 0 + readonly property int blue: 1 + readonly property int red: 2 + readonly property int black: 3 + readonly property int none: 4 + readonly property int noneBorderless: 5 + readonly property int noneBorderlessWhite: 6 + readonly property int noneBorderlessGray: 7 + readonly property var textColor: [ colors.darkGray, colors.white, colors.white, colors.white, colors.white, colors.blueAccent, colors.white, colors.darkGray ] + readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] + readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] + readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] + readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] + readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow] + readonly property int radius: 15 + } + + QtObject { + id: effects + readonly property int fadeInDuration: 300 + } + Item { + id: glyphs + readonly property string noIcon: "" + readonly property string hmd: "b" + readonly property string screen: "c" + readonly property string keyboard: "d" + readonly property string handControllers: "e" + readonly property string headphonesMic: "f" + readonly property string gamepad: "g" + readonly property string headphones: "h" + readonly property string mic: "i" + readonly property string upload: "j" + readonly property string script: "k" + readonly property string text: "l" + readonly property string cube: "m" + readonly property string sphere: "n" + readonly property string zone: "o" + readonly property string light: "p" + readonly property string web: "q" + readonly property string web2: "r" + readonly property string edit: "s" + readonly property string market: "t" + readonly property string directory: "u" + readonly property string menu: "v" + readonly property string close: "w" + readonly property string closeInverted: "x" + readonly property string pin: "y" + readonly property string pinInverted: "z" + readonly property string resizeHandle: "A" + readonly property string disclosureExpand: "B" + readonly property string reloadSmall: "a" + readonly property string closeSmall: "C" + readonly property string forward: "D" + readonly property string backward: "E" + readonly property string reload: "F" + readonly property string unmuted: "G" + readonly property string muted: "H" + readonly property string minimize: "I" + readonly property string maximize: "J" + readonly property string maximizeInverted: "K" + readonly property string disclosureButtonExpand: "L" + readonly property string disclosureButtonCollapse: "M" + readonly property string scriptStop: "N" + readonly property string scriptReload: "O" + readonly property string scriptRun: "P" + readonly property string scriptNew: "Q" + readonly property string hifiForum: "2" + readonly property string hifiLogoSmall: "S" + readonly property string avatar1: "T" + readonly property string placemark: "U" + readonly property string box: "V" + readonly property string community: "0" + readonly property string grabHandle: "X" + readonly property string search: "Y" + readonly property string disclosureCollapse: "Z" + readonly property string scriptUpload: "R" + readonly property string code: "W" + readonly property string avatar: "<" + readonly property string arrowsH: ":" + readonly property string arrowsV: ";" + readonly property string arrows: "`" + readonly property string compress: "!" + readonly property string expand: "\"" + readonly property string placemark1: "#" + readonly property string circle: "$" + readonly property string handPointer: "9" + readonly property string plusSquareO: "%" + readonly property string sliders: "&" + readonly property string square: "'" + readonly property string alignCenter: "8" + readonly property string alignJustify: ")" + readonly property string alignLeft: "*" + readonly property string alignRight: "^" + readonly property string bars: "7" + readonly property string circleSlash: "," + readonly property string sync: "()" + readonly property string key: "-" + readonly property string link: "." + readonly property string location: "/" + readonly property string caratR: "3" + readonly property string caratL: "4" + readonly property string caratDn: "5" + readonly property string caratUp: "6" + readonly property string folderLg: ">" + readonly property string folderSm: "?" + readonly property string levelUp: "1" + readonly property string info: "[" + readonly property string question: "]" + readonly property string alert: "+" + readonly property string home: "_" + readonly property string error: "=" + readonly property string settings: "@" + readonly property string trash: "{" + readonly property string objectGroup: "\ue000" + readonly property string cm: "}" + readonly property string msvg79: "~" + readonly property string deg: "\\" + readonly property string px: "|" + readonly property string editPencil: "\ue00d" + readonly property string vol_0: "\ue00e" + readonly property string vol_1: "\ue00f" + readonly property string vol_2: "\ue010" + readonly property string vol_3: "\ue011" + readonly property string vol_4: "\ue012" + readonly property string vol_x_0: "\ue013" + readonly property string vol_x_1: "\ue014" + readonly property string vol_x_2: "\ue015" + readonly property string vol_x_3: "\ue016" + readonly property string vol_x_4: "\ue017" + readonly property string source: "\ue01c" + readonly property string playback_play: "\ue01d" + readonly property string stop_square: "\ue01e" + readonly property string avatarTPose: "\ue01f" + readonly property string lock: "\ue006" + readonly property string checkmark: "\ue020" + readonly property string leftRightArrows: "\ue021" + readonly property string hfc: "\ue022" + readonly property string home2: "\ue023" + readonly property string walletKey: "\ue024" + readonly property string lightning: "\ue025" + readonly property string securityImage: "\ue026" + readonly property string wallet: "\ue027" + readonly property string paperPlane: "\ue028" + readonly property string passphrase: "\ue029" + } +} diff --git a/interface/resources/qml/styles-uit/AnonymousProRegular.qml b/interface/resources/qml/styles-uit/AnonymousProRegular.qml index c832910ec2..431ecd0f38 100644 --- a/interface/resources/qml/styles-uit/AnonymousProRegular.qml +++ b/interface/resources/qml/styles-uit/AnonymousProRegular.qml @@ -9,15 +9,12 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root - FontLoader { id: anonymousProRegular; source: "qrc:/fonts/AnonymousPro-Regular.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: anonymousProRegular.name + font.family: "Anonymous Pro" } diff --git a/interface/resources/qml/styles-uit/ButtonLabel.qml b/interface/resources/qml/styles-uit/ButtonLabel.qml index aade5fb439..d227cb4869 100644 --- a/interface/resources/qml/styles-uit/ButtonLabel.qml +++ b/interface/resources/qml/styles-uit/ButtonLabel.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayBold { diff --git a/interface/resources/qml/styles-uit/FiraSansRegular.qml b/interface/resources/qml/styles-uit/FiraSansRegular.qml index 6d73210120..05f6ecf74b 100644 --- a/interface/resources/qml/styles-uit/FiraSansRegular.qml +++ b/interface/resources/qml/styles-uit/FiraSansRegular.qml @@ -9,15 +9,12 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root - FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: firaSansRegular.name + font.family: "Fira Sans" } diff --git a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml index 2bfd319d49..32554c2f25 100644 --- a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml +++ b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml @@ -9,15 +9,12 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root - FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: firaSansSemiBold.name + font.family: "Fira Sans SemiBold" } diff --git a/interface/resources/qml/styles-uit/HiFiGlyphs.qml b/interface/resources/qml/styles-uit/HiFiGlyphs.qml index baab41e166..07f0212f0c 100644 --- a/interface/resources/qml/styles-uit/HiFiGlyphs.qml +++ b/interface/resources/qml/styles-uit/HiFiGlyphs.qml @@ -12,12 +12,11 @@ import QtQuick 2.5 Text { id: root - FontLoader { id: hiFiGlyphs; source: "qrc:/fonts/hifi-glyphs.ttf"; } property int size: 32 font.pixelSize: size width: size height: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: hiFiGlyphs.name + font.family: "hifi-glyphs" } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 43de8333af..9705d84b7b 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -11,44 +11,34 @@ import QtQuick 2.5 import QtQuick.Window 2.2 -Item { - readonly property alias colors: colors - readonly property alias colorSchemes: colorSchemes - readonly property alias dimensions: dimensions - readonly property alias fontSizes: fontSizes - readonly property alias glyphs: glyphs - readonly property alias icons: icons - readonly property alias buttons: buttons - readonly property alias effects: effects +QtObject { function glyphForIcon(icon) { // Translates icon enum to glyph char. var glyph; switch (icon) { - case hifi.icons.information: - glyph = hifi.glyphs.info; + case icons.information: + glyph = glyphs.info; break; - case hifi.icons.question: - glyph = hifi.glyphs.question; + case icons.question: + glyph = glyphs.question; break; - case hifi.icons.warning: - glyph = hifi.glyphs.alert; + case icons.warning: + glyph = glyphs.alert; break; - case hifi.icons.critical: - glyph = hifi.glyphs.error; + case icons.critical: + glyph = glyphs.error; break; - case hifi.icons.placemark: - glyph = hifi.glyphs.placemark; + case icons.placemark: + glyph = glyphs.placemark; break; default: - glyph = hifi.glyphs.noIcon; + glyph = glyphs.noIcon; } return glyph; } - Item { - id: colors - + readonly property QtObject colors: QtObject { // Base colors readonly property color baseGray: "#393939" readonly property color darkGray: "#121212" @@ -134,15 +124,13 @@ Item { readonly property color tabBackgroundLight: "#d4d4d4" } - Item { - id: colorSchemes + readonly property QtObject colorSchemes: QtObject { readonly property int light: 0 readonly property int dark: 1 readonly property int faintGray: 2 } - Item { - id: dimensions + readonly property QtObject dimensions: QtObject { readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080 readonly property real borderRadius: largeScreen ? 7.5 : 5.0 readonly property real borderWidth: largeScreen ? 2 : 1 @@ -165,10 +153,11 @@ Item { readonly property real scrollbarBackgroundWidth: 20 readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 readonly property real tabletMenuHeader: 90 + readonly property real buttonWidth: 120 } - Item { - id: fontSizes // In pixels + readonly property QtObject fontSizes: QtObject { + // In pixels readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14 readonly property real tabName: dimensions.largeScreen ? 12 : 10 readonly property real sectionName: dimensions.largeScreen ? 12 : 10 @@ -193,8 +182,7 @@ Item { readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22 } - Item { - id: icons + readonly property QtObject icons: QtObject { // Values per OffscreenUi::Icon readonly property int none: 0 readonly property int question: 1 @@ -204,8 +192,7 @@ Item { readonly property int placemark: 5 } - Item { - id: buttons + readonly property QtObject buttons: QtObject { readonly property int white: 0 readonly property int blue: 1 readonly property int red: 2 @@ -226,12 +213,11 @@ Item { readonly property int radius: 5 } - QtObject { - id: effects + readonly property QtObject effects: QtObject { readonly property int fadeInDuration: 300 } - Item { - id: glyphs + + readonly property QtObject glyphs: QtObject { readonly property string noIcon: "" readonly property string hmd: "b" readonly property string screen: "c" diff --git a/interface/resources/qml/styles-uit/IconButton.qml b/interface/resources/qml/styles-uit/IconButton.qml index 84c1ef14c1..e5a18e2ae7 100644 --- a/interface/resources/qml/styles-uit/IconButton.qml +++ b/interface/resources/qml/styles-uit/IconButton.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayRegular { diff --git a/interface/resources/qml/styles-uit/InfoItem.qml b/interface/resources/qml/styles-uit/InfoItem.qml index 83781a4ef5..fa7684e8e7 100644 --- a/interface/resources/qml/styles-uit/InfoItem.qml +++ b/interface/resources/qml/styles-uit/InfoItem.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewaySemiBold { diff --git a/interface/resources/qml/styles-uit/InputLabel.qml b/interface/resources/qml/styles-uit/InputLabel.qml index 59657a554d..3853dd5b19 100644 --- a/interface/resources/qml/styles-uit/InputLabel.qml +++ b/interface/resources/qml/styles-uit/InputLabel.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewaySemiBold { diff --git a/interface/resources/qml/styles-uit/ListItem.qml b/interface/resources/qml/styles-uit/ListItem.qml index f707686edc..a69c4b48c2 100644 --- a/interface/resources/qml/styles-uit/ListItem.qml +++ b/interface/resources/qml/styles-uit/ListItem.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayRegular { diff --git a/interface/resources/qml/styles-uit/Logs.qml b/interface/resources/qml/styles-uit/Logs.qml index 577fe2f8d8..45d4436fbf 100644 --- a/interface/resources/qml/styles-uit/Logs.qml +++ b/interface/resources/qml/styles-uit/Logs.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." AnonymousProRegular { diff --git a/interface/resources/qml/styles-uit/OverlayTitle.qml b/interface/resources/qml/styles-uit/OverlayTitle.qml index e23b9eca14..0fb423baab 100644 --- a/interface/resources/qml/styles-uit/OverlayTitle.qml +++ b/interface/resources/qml/styles-uit/OverlayTitle.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayRegular { diff --git a/interface/resources/qml/styles-uit/RalewayBold.qml b/interface/resources/qml/styles-uit/RalewayBold.qml index 963d8d9ba4..7edde91271 100644 --- a/interface/resources/qml/styles-uit/RalewayBold.qml +++ b/interface/resources/qml/styles-uit/RalewayBold.qml @@ -9,16 +9,13 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root - FontLoader { id: ralewayBold; source: "qrc:/fonts/Raleway-Bold.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: ralewayBold.name - font.bold: true // Font seems to need this in order to display bold. + font.family: "Raleway" + font.bold: true } diff --git a/interface/resources/qml/styles-uit/RalewayLight.qml b/interface/resources/qml/styles-uit/RalewayLight.qml index 8957b70c82..666ebc2ea9 100644 --- a/interface/resources/qml/styles-uit/RalewayLight.qml +++ b/interface/resources/qml/styles-uit/RalewayLight.qml @@ -9,15 +9,12 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root - FontLoader { id: ralewayLight; source: "qrc:/fonts/Raleway-Light.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: ralewayLight.name + font.family: "Raleway Light" } diff --git a/interface/resources/qml/styles-uit/RalewayRegular.qml b/interface/resources/qml/styles-uit/RalewayRegular.qml index fd2661928c..e263922095 100644 --- a/interface/resources/qml/styles-uit/RalewayRegular.qml +++ b/interface/resources/qml/styles-uit/RalewayRegular.qml @@ -9,15 +9,12 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root - FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: ralewayRegular.name + font.family: "Raleway" } diff --git a/interface/resources/qml/styles-uit/RalewaySemiBold.qml b/interface/resources/qml/styles-uit/RalewaySemiBold.qml index 7ec9ea3b34..19d8b6b8c9 100644 --- a/interface/resources/qml/styles-uit/RalewaySemiBold.qml +++ b/interface/resources/qml/styles-uit/RalewaySemiBold.qml @@ -8,16 +8,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.7 Text { id: root - FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; } property real size: 32 font.pixelSize: size verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft - font.family: ralewaySemiBold.name + font.family: "Raleway" + font.weight: Font.DemiBold } diff --git a/interface/resources/qml/styles-uit/SectionName.qml b/interface/resources/qml/styles-uit/SectionName.qml index 5438fec7bc..20f8e1e116 100644 --- a/interface/resources/qml/styles-uit/SectionName.qml +++ b/interface/resources/qml/styles-uit/SectionName.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayRegular { diff --git a/interface/resources/qml/styles-uit/ShortcutText.qml b/interface/resources/qml/styles-uit/ShortcutText.qml index a3ab351870..8504ffa2b8 100644 --- a/interface/resources/qml/styles-uit/ShortcutText.qml +++ b/interface/resources/qml/styles-uit/ShortcutText.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayLight { diff --git a/interface/resources/qml/styles-uit/TabName.qml b/interface/resources/qml/styles-uit/TabName.qml index eb4e790e7e..0f620fe8c2 100644 --- a/interface/resources/qml/styles-uit/TabName.qml +++ b/interface/resources/qml/styles-uit/TabName.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." RalewayRegular { diff --git a/interface/resources/qml/styles-uit/TextFieldInput.qml b/interface/resources/qml/styles-uit/TextFieldInput.qml index 010b4d03ad..f2a57e57fc 100644 --- a/interface/resources/qml/styles-uit/TextFieldInput.qml +++ b/interface/resources/qml/styles-uit/TextFieldInput.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import "." FiraSansSemiBold { diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index 33c2818849..60e744bec3 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -9,7 +9,6 @@ // import QtQuick 2.5 -import QtGraphicalEffects 1.0 import "." import "../styles-uit" diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 4d506755f2..406c6be556 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtGraphicalEffects 1.0 import "../styles-uit" @@ -39,7 +37,7 @@ FocusScope { // If someone directly set the visibility to false // toggle it back on and use the targetVisible flag to transition // via fading. - if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { + if (!disableFade && ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0))) { var target = visible; visible = !visible; fadeTargetProperty = target ? 1.0 : 0.0; diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index c3b8399e01..98bfb52c38 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -16,10 +16,11 @@ import "../js/Utils.js" as Utils Item { id: frame + objectName: "Frame" HifiConstants { id: hifi } default property var decoration - + property string qmlFile: "N/A" property bool gradientsSupported: desktop.gradientsSupported readonly property int frameMarginLeft: frame.decoration ? frame.decoration.frameMarginLeft : 0 @@ -43,7 +44,8 @@ Item { Text { id: debugZ visible: DebugQML - text: window ? "Z: " + window.z : "" + color: "red" + text: (window ? "Z: " + window.z : "") + " " + qmlFile y: window ? window.height + 4 : 0 } diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index 1f9b59d2b4..c156b80388 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -10,8 +10,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import "." @@ -39,27 +38,27 @@ Window { property bool keyboardRaised: false property bool punctuationMode: false + readonly property real verticalScrollWidth: 10 + readonly property real verticalScrollShaft: 8 + // Scrollable window content. // FIXME this should not define any visual content in this type. The base window // type should only consist of logic sized areas, with nothing drawn (although the // default value for the frame property does include visual decorations) property var pane: Item { - property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int contentWidth: scrollView.width property int scrollHeight: scrollView.height anchors.fill: parent - anchors.rightMargin: isScrolling ? 11 : 0 Rectangle { id: contentBackground anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 11 : 0 + //anchors.rightMargin: parent.isScrolling ? verticalScrollWidth + 1 : 0 color: hifi.colors.baseGray visible: !window.hideBackground && modality != Qt.ApplicationModal } - LinearGradient { visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal anchors.top: contentBackground.bottom @@ -75,35 +74,28 @@ Window { cached: true } - ScrollView { + Flickable { id: scrollView - contentItem: content - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + contentItem.children: [ content ] + contentHeight: content.height + boundsBehavior: Flickable.StopAtBounds + property bool isScrolling: (contentHeight - height) > 10 ? true : false + + clip: true + + anchors.rightMargin: isScrolling ? verticalScrollWidth : 0 anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 1 : 0 anchors.bottomMargin: footerPane.height - style: ScrollViewStyle { - - padding.right: -7 // Move to right away from content. - - handle: Item { - implicitWidth: 8 - Rectangle { - radius: 4 - color: hifi.colors.white30 - anchors { - fill: parent - leftMargin: 2 // Finesse size and position. - topMargin: 1 - bottomMargin: 1 - } - } - } - - scrollBarBackground: Item { - implicitWidth: 10 + ScrollBar.vertical: ScrollBar { + policy: scrollView.isScrolling ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + parent: scrollView.parent + anchors.top: scrollView.top + anchors.right: scrollView.right + anchors.bottom: scrollView.bottom + anchors.rightMargin: -verticalScrollWidth //compensate scrollview's right margin + background: Item { + implicitWidth: verticalScrollWidth Rectangle { color: hifi.colors.darkGray30 radius: 4 @@ -114,13 +106,18 @@ Window { } } } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false + contentItem: Item { + implicitWidth: verticalScrollShaft + Rectangle { + radius: verticalScrollShaft/2 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } } } } diff --git a/interface/resources/qml/windows/TabletModalWindow.qml b/interface/resources/qml/windows/TabletModalWindow.qml index e21cb6b224..0dbf4689d6 100644 --- a/interface/resources/qml/windows/TabletModalWindow.qml +++ b/interface/resources/qml/windows/TabletModalWindow.qml @@ -8,9 +8,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs -import "." Rectangle { id: modalWindow diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 4ed765db4b..835967c628 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import "." diff --git a/interface/resources/serverless/tutorial.json b/interface/resources/serverless/tutorial.json new file mode 100644 index 0000000000..f690de6643 --- /dev/null +++ b/interface/resources/serverless/tutorial.json @@ -0,0 +1,18 @@ +{ + "Entities": [ + { + "type": "Box", + "dimensions": { + "x": 20, + "y": 1, + "z": 20 + }, + "position" : { + "x": 0, + "y": -12, + "z": 0 + } + } + ], + "Version": 84 +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8e4c35eba9..aec31f2de4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -225,8 +225,8 @@ #ifdef DEBUG_EVENT_QUEUE // This is a HACK that uses private headers included with the qt source distrubution. // To use this feature you need to add these directores to your include path: -// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1/QtCore -// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1 +// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1/QtCore +// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1 #define QT_BOOTSTRAPPED #include #include @@ -351,8 +351,9 @@ static const QString OBJ_EXTENSION = ".obj"; static const QString AVA_JSON_EXTENSION = ".ava.json"; static const QString WEB_VIEW_TAG = "noDownload=true"; static const QString ZIP_EXTENSION = ".zip"; +static const QString CONTENT_ZIP_EXTENSION = ".content.zip"; -static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f; +static const float MIRROR_FULLSCREEN_DISTANCE = 0.789f; static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; @@ -376,9 +377,7 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; -static const QString DOMAIN_SPAWNING_POINT = "/0, -10, 0"; - -const QHash Application::_acceptedExtensions { +const std::vector> Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, { SVO_JSON_EXTENSION, &Application::importSVOFromURL }, { AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl }, @@ -386,6 +385,7 @@ const QHash Application::_acceptedExtensi { JS_EXTENSION, &Application::askToLoadScript }, { FST_EXTENSION, &Application::askToSetAvatarUrl }, { JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent }, + { CONTENT_ZIP_EXTENSION, &Application::askToReplaceDomainContent }, { ZIP_EXTENSION, &Application::importFromZIP }, { JPG_EXTENSION, &Application::importImage }, { PNG_EXTENSION, &Application::importImage } @@ -511,6 +511,27 @@ std::atomic DeadlockWatchdogThread::_maxElapsed; std::atomic DeadlockWatchdogThread::_maxElapsedAverage; ThreadSafeMovingAverage DeadlockWatchdogThread::_movingAverage; +bool isDomainURL(QUrl url) { + if (!url.isValid()) { + return false; + } + if (url.scheme() == URL_SCHEME_HIFI) { + return true; + } + if (url.scheme() != URL_SCHEME_FILE) { + // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can + // be loaded over http(s) + // && url.scheme() != URL_SCHEME_HTTP && + // url.scheme() != URL_SCHEME_HTTPS + return false; + } + if (url.path().endsWith(".json", Qt::CaseInsensitive) || + url.path().endsWith(".json.gz", Qt::CaseInsensitive)) { + return true; + } + return false; +} + #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { public: @@ -540,7 +561,7 @@ public: if (message->message == WM_COPYDATA) { COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)(message->lParam); QUrl url = QUrl((const char*)(pcds->lpData)); - if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) { + if (isDomainURL(url)) { DependencyManager::get()->handleLookupString(url.toString()); return true; } @@ -891,7 +912,7 @@ Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; -const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f; +const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 70.0f; const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; @@ -920,7 +941,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME), _scaleMirror(1.0f), - _rotateMirror(0.0f), + _mirrorYawOffset(0.0f), _raiseMirror(0.0f), _enableProcessOctreeThread(true), _lastNackTime(usecTimestampNow()), @@ -981,6 +1002,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/fontawesome-webfont.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/hifi-glyphs.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/AnonymousPro-Regular.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-Regular.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Light.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf"); _window->setWindowTitle("High Fidelity Interface"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us @@ -1024,7 +1054,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(this); - connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); + connect(domainCheckInTimer, &QTimer::timeout, [this, nodeList] { + if (!isServerlessMode()) { + nodeList->sendDomainServerCheckIn(); + } + }); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); connect(this, &QCoreApplication::aboutToQuit, [domainCheckInTimer] { domainCheckInTimer->stop(); @@ -1086,9 +1120,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const DomainHandler& domainHandler = nodeList->getDomainHandler(); - connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl))); connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); - connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); + connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { @@ -1998,27 +2032,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(_window, SIGNAL(windowMinimizedChanged(bool)), this, SLOT(windowMinimizedChanged(bool))); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); - { - PROFILE_RANGE(render, "Process Default Skybox"); - auto textureCache = DependencyManager::get(); - - QFileSelector fileSelector; - fileSelector.setExtraSelectors(FileUtils::getFileSelectors()); - auto skyboxUrl = fileSelector.select(PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.ktx"); - - _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl.toStdString()); - _defaultSkyboxAmbientTexture = _defaultSkyboxTexture; - - _defaultSkybox->setCubemap(_defaultSkyboxTexture); - } - EntityTreeRenderer::setEntitiesShouldFadeFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); return entityServerNode && !isPhysicsEnabled(); }); _snapshotSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/snap.wav")); - + QVariant testProperty = property(hifi::properties::TEST); qDebug() << testProperty; if (testProperty.isValid()) { @@ -2049,7 +2069,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&_addAssetToWorldErrorTimer, &QTimer::timeout, this, &Application::addAssetToWorldErrorTimeout); connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose); - connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose); + connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose); updateSystemTabletMode(); @@ -2461,7 +2481,6 @@ void Application::initializeGL() { DeadlockWatchdogThread::withPause([&] { // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; - static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; _renderEngine->addJob("UpdateScene"); #ifndef Q_OS_ANDROID _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); @@ -2681,7 +2700,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("Overlays", &_overlays); surfaceContext->setContextProperty("Window", DependencyManager::get().data()); surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); - surfaceContext->setContextProperty("Stats", Stats::getInstance()); surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); @@ -2725,7 +2743,12 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { Stats::show(); - AvatarInputs::show(); + auto surfaceContext = DependencyManager::get()->getSurfaceContext(); + surfaceContext->setContextProperty("Stats", Stats::getInstance()); + + auto offscreenUi = DependencyManager::get(); + auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml"); + offscreenUi->show(qml, "AvatarInputsBar"); } void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { @@ -2788,8 +2811,9 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _thirdPersonHMDCameraBoomValid= false; + if (isHMDMode()) { - auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)); + auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _mirrorYawOffset, 0.0f)); glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix()); // Mirror HMD yaw and roll @@ -2812,12 +2836,15 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { + mirrorBodyOrientation * hmdOffset); } else { - _myCamera.setOrientation(myAvatar->getWorldOrientation() - * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); + auto userInputMapper = DependencyManager::get(); + const float YAW_SPEED = TWO_PI / 5.0f; + float deltaYaw = userInputMapper->getActionState(controller::Action::YAW) * YAW_SPEED * deltaTime; + _mirrorYawOffset += deltaYaw; + _myCamera.setOrientation(myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _mirrorYawOffset, 0.0f))); _myCamera.setPosition(myAvatar->getDefaultEyePosition() + glm::vec3(0, _raiseMirror * myAvatar->getModelScale(), 0) - + (myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * - glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); + + (myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, _mirrorYawOffset, 0.0f))) * + glm::vec3(0.0f, 0.0f, -1.0f) * myAvatar->getBoomLength() * _scaleMirror); } renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; } @@ -3019,31 +3046,23 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location"; static const QString SENT_TO_ENTRY = "entry"; - static const QString SENT_TO_SANDBOX = "sandbox"; QString sentTo; - // If this is a first run we short-circuit the address passed in - if (firstRun.get()) { + // If this is a first run we short-circuit the address passed in + if (firstRun.get()) { #if !defined(Q_OS_ANDROID) - showHelp(); -#endif - if (sandboxIsRunning) { - qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; - DependencyManager::get()->goToLocalSandbox(); - sentTo = SENT_TO_SANDBOX; - } else { - qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; - DependencyManager::get()->goToEntry(); - sentTo = SENT_TO_ENTRY; - } - firstRun.set(false); + showHelp(); +#endif + DependencyManager::get()->goToEntry(); + sentTo = SENT_TO_ENTRY; + firstRun.set(false); - } else { - qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); - DependencyManager::get()->loadSettings(addressLookupString); - sentTo = SENT_TO_PREVIOUS_LOCATION; - } + } else { + qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); + DependencyManager::get()->loadSettings(addressLookupString); + sentTo = SENT_TO_PREVIOUS_LOCATION; + } UserActivityLogger::getInstance().logAction("startup_sent_to", { { "sent_to", sentTo }, @@ -3083,6 +3102,57 @@ bool Application::importFromZIP(const QString& filePath) { return true; } +bool Application::isServerlessMode() const { + auto tree = getEntities()->getTree(); + if (tree) { + return tree->isServerlessMode(); + } + return false; +} + +void Application::setIsServerlessMode(bool serverlessDomain) { + auto tree = getEntities()->getTree(); + if (tree) { + tree->setIsServerlessMode(serverlessDomain); + } +} + +void Application::loadServerlessDomain(QUrl domainURL) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); + return; + } + + if (domainURL.isEmpty()) { + return; + } + + QUuid serverlessSessionID = QUuid::createUuid(); + getMyAvatar()->setSessionUUID(serverlessSessionID); + auto nodeList = DependencyManager::get(); + nodeList->setSessionUUID(serverlessSessionID); + + // there is no domain-server to tell us our permissions, so enable all + NodePermissions permissions; + permissions.setAll(true); + nodeList->setPermissions(permissions); + + // we can't import directly into the main tree because we would need to lock it, and + // Octree::readFromURL calls loop.exec which can run code which will also attempt to lock the tree. + EntityTreePointer tmpTree(new EntityTree()); + tmpTree->setIsServerlessMode(true); + tmpTree->createRootElement(); + auto myAvatar = getMyAvatar(); + tmpTree->setMyAvatar(myAvatar); + bool success = tmpTree->readFromURL(domainURL.toString()); + if (success) { + tmpTree->reaverageOctreeElements(); + tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0); + } + + _fullSceneReceivedCounter++; +} + bool Application::importImage(const QString& urlString) { qCDebug(interfaceapp) << "An image file has been dropped in"; QString filepath(urlString); @@ -3324,8 +3394,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } else { setFullscreen(nullptr); } - } else { - Menu::getInstance()->triggerOption(MenuOption::AddressBar); } break; @@ -3387,13 +3455,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_F: { - if (isOption) { - _physicsEngine->dumpNextStats(); - } - break; - } - case Qt::Key_Asterisk: Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox); break; @@ -3413,22 +3474,25 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_S: if (isShifted && isMeta && !isOption) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); - } else if (!isOption && !isShifted && isMeta) { - AudioInjectorOptions options; - options.localOnly = true; - options.stereo = true; - - if (_snapshotSoundInjector) { - _snapshotSoundInjector->setOptions(options); - _snapshotSoundInjector->restart(); - } else { - QByteArray samples = _snapshotSound->getByteArray(); - _snapshotSoundInjector = AudioInjector::playSound(samples, options); - } - takeSnapshot(true); } break; + case Qt::Key_P: { + AudioInjectorOptions options; + options.localOnly = true; + options.stereo = true; + + if (_snapshotSoundInjector) { + _snapshotSoundInjector->setOptions(options); + _snapshotSoundInjector->restart(); + } else { + QByteArray samples = _snapshotSound->getByteArray(); + _snapshotSoundInjector = AudioInjector::playSound(samples, options); + } + takeSnapshot(true); + break; + } + case Qt::Key_Apostrophe: { if (isMeta) { auto cursor = Cursor::Manager::instance().getCursor(); @@ -3452,38 +3516,6 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::Chat); break; - case Qt::Key_Up: - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - if (!isShifted) { - _scaleMirror *= 0.95f; - } else { - _raiseMirror += 0.05f; - } - } - break; - - case Qt::Key_Down: - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - if (!isShifted) { - _scaleMirror *= 1.05f; - } else { - _raiseMirror -= 0.05f; - } - } - break; - - case Qt::Key_Left: - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - _rotateMirror += PI / 20.0f; - } - break; - - case Qt::Key_Right: - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - _rotateMirror -= PI / 20.0f; - } - break; - #if 0 case Qt::Key_I: if (isShifted) { @@ -4583,7 +4615,7 @@ void Application::initDisplay() { } void Application::init() { - + // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); if (!DISABLE_DEFERRED) { @@ -4608,7 +4640,9 @@ void Application::init() { qCDebug(interfaceapp) << "Loaded settings"; // fire off an immediate domain-server check in now that settings are loaded - DependencyManager::get()->sendDomainServerCheckIn(); + if (!isServerlessMode()) { + DependencyManager::get()->sendDomainServerCheckIn(); + } // This allows collision to be set up properly for shape entities supported by GeometryCache. // This is before entity setup to ensure that it's ready for whenever instance collision is initialized. @@ -4637,7 +4671,7 @@ void Application::init() { auto entityScriptingInterface = DependencyManager::get(); // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts - connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity, + connect(_entitySimulation.get(), &PhysicalEntitySimulation::entityCollisionWithEntity, getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity); // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing @@ -4851,8 +4885,10 @@ void Application::cameraMenuChanged() { auto menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::FullscreenMirror)) { if (!isHMDMode() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { + _mirrorYawOffset = 0.0f; _myCamera.setMode(CAMERA_MODE_MIRROR); getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers + getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT); } } else if (menu->isOptionChecked(MenuOption::FirstPerson)) { if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { @@ -5132,7 +5168,7 @@ void Application::update(float deltaTime) { // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { - if (!_controllerScriptingInterface->areActionsCaptured()) { + if (!_controllerScriptingInterface->areActionsCaptured() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); myAvatar->setDriveKey(MyAvatar::TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X)); @@ -5145,6 +5181,7 @@ void Application::update(float deltaTime) { myAvatar->setDriveKey(MyAvatar::ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z)); } + myAvatar->setSprintMode((bool)userInputMapper->getActionState(controller::Action::SPRINT)); static const std::vector avatarControllerActions = { controller::Action::LEFT_HAND, controller::Action::RIGHT_HAND, @@ -5229,11 +5266,13 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation_physics, "PreStep"); PerformanceTimer perfTimer("preStep)"); - static VectorOfMotionStates motionStates; - _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); - _physicsEngine->removeObjects(motionStates); - _entitySimulation->deleteObjectsRemovedFromPhysics(); + { + const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics(); + _physicsEngine->removeObjects(motionStates); + _entitySimulation->deleteObjectsRemovedFromPhysics(); + } + VectorOfMotionStates motionStates; getEntities()->getTree()->withReadLock([&] { _entitySimulation->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); @@ -5247,7 +5286,7 @@ void Application::update(float deltaTime) { _entitySimulation->applyDynamicChanges(); - avatarManager->getObjectsToRemoveFromPhysics(motionStates); + avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); @@ -5434,7 +5473,7 @@ void Application::update(float deltaTime) { editRenderArgs([this, deltaTime](AppRenderArgs& appRenderArgs) { PerformanceTimer perfTimer("editRenderArgs"); - appRenderArgs._headPose= getHMDSensorPose(); + appRenderArgs._headPose = getHMDSensorPose(); auto myAvatar = getMyAvatar(); @@ -5454,10 +5493,10 @@ void Application::update(float deltaTime) { { QMutexLocker viewLocker(&_viewMutex); // adjust near clip plane to account for sensor scaling. - auto adjustedProjection = glm::perspective(_viewFrustum.getFieldOfView(), - _viewFrustum.getAspectRatio(), - DEFAULT_NEAR_CLIP * sensorToWorldScale, - _viewFrustum.getFarClip()); + auto adjustedProjection = glm::perspective(glm::radians(_fieldOfView.get()), + getActiveDisplayPlugin()->getRecommendedAspectRatio(), + DEFAULT_NEAR_CLIP * sensorToWorldScale, + DEFAULT_FAR_CLIP); _viewFrustum.setProjection(adjustedProjection); _viewFrustum.calculate(); } @@ -5539,6 +5578,7 @@ void Application::update(float deltaTime) { { QMutexLocker viewLocker(&_viewMutex); _myCamera.loadViewFrustum(_displayViewFrustum); + appRenderArgs._view = glm::inverse(_displayViewFrustum.getView()); } { @@ -5741,10 +5781,15 @@ void Application::updateWindowTitle() const { QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; QString username = accountManager->getAccountInfo().getUsername(); - QString currentPlaceName = DependencyManager::get()->getHost(); - if (currentPlaceName.isEmpty()) { - currentPlaceName = nodeList->getDomainHandler().getHostname(); + QString currentPlaceName; + if (isServerlessMode()) { + currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + } else { + currentPlaceName = DependencyManager::get()->getDomainURL().host(); + if (currentPlaceName.isEmpty()) { + currentPlaceName = nodeList->getDomainHandler().getHostname(); + } } QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) @@ -5757,7 +5802,7 @@ void Application::updateWindowTitle() const { _window->setWindowTitle(title); // updateTitleWindow gets called whenever there's a change regarding the domain, so rather - // than placing this within domainChanged, it's placed here to cover the other potential cases. + // than placing this within domainURLChanged, it's placed here to cover the other potential cases. DependencyManager::get< MessagesClient >()->sendLocalMessage("Toolbar-DomainChanged", ""); } @@ -5796,15 +5841,22 @@ void Application::clearDomainAvatars() { DependencyManager::get()->clearOtherAvatars(); } -void Application::domainChanged(const QString& domainHostname) { - updateWindowTitle(); +void Application::domainURLChanged(QUrl domainURL) { // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation(); + setIsServerlessMode(domainURL.scheme() != URL_SCHEME_HIFI); + if (isServerlessMode()) { + loadServerlessDomain(domainURL); + } + updateWindowTitle(); } void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; + + auto nodeList = DependencyManager::get(); + clearDomainOctreeDetails(); } void Application::nodeAdded(SharedNodePointer node) const { @@ -5921,22 +5973,22 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { AABox avatarBox(getMyAvatar()->getWorldPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); // create two functions that use avatarBox (entityScan and elementScan), the second calls the first std::function entityScan = [=](EntityItemPointer& entity) { - if (entity->shouldBePhysical()) { - bool success = false; - AABox entityBox = entity->getAABox(success); - // important: bail for entities that cannot supply a valid AABox - return success && avatarBox.touches(entityBox); - } - return false; - }; + if (entity->shouldBePhysical()) { + bool success = false; + AABox entityBox = entity->getAABox(success); + // important: bail for entities that cannot supply a valid AABox + return success && avatarBox.touches(entityBox); + } + return false; + }; std::function elementScan = [&](const OctreeElementPointer& element, void* unused) { - if (element->getAACube().touches(avatarBox)) { - EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - entityTreeElement->getEntities(entityScan, entities); - return true; - } - return false; - }; + if (element->getAACube().touches(avatarBox)) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->getEntities(entityScan, entities); + return true; + } + return false; + }; entityTree->withReadLock([&] { // Pass the second function to the general-purpose EntityTree::findEntities() @@ -6146,6 +6198,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Selection", DependencyManager::get().data()); scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Wallet", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get().data()); + + scriptEngine->registerGlobalObject("App", this); qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); @@ -6169,14 +6224,12 @@ bool Application::canAcceptURL(const QString& urlString) const { QUrl url(urlString); if (url.query().contains(WEB_VIEW_TAG)) { return false; - } else if (urlString.startsWith(HIFI_URL_SCHEME)) { + } else if (urlString.startsWith(URL_SCHEME_HIFI)) { return true; } - QHashIterator i(_acceptedExtensions); QString lowerPath = url.path().toLower(); - while (i.hasNext()) { - i.next(); - if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { + for (auto& pair : _acceptedExtensions) { + if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) { return true; } } @@ -6184,21 +6237,19 @@ bool Application::canAcceptURL(const QString& urlString) const { } bool Application::acceptURL(const QString& urlString, bool defaultUpload) { - if (urlString.startsWith(HIFI_URL_SCHEME)) { + QUrl url(urlString); + + if (url.scheme() == URL_SCHEME_HIFI) { // this is a hifi URL - have the AddressManager handle it - emit receivedHifiSchemeURL(urlString); QMetaObject::invokeMethod(DependencyManager::get().data(), "handleLookupString", Qt::AutoConnection, Q_ARG(const QString&, urlString)); return true; } - QUrl url(urlString); - QHashIterator i(_acceptedExtensions); QString lowerPath = url.path().toLower(); - while (i.hasNext()) { - i.next(); - if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { - AcceptURLMethod method = i.value(); + for (auto& pair : _acceptedExtensions) { + if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) { + AcceptURLMethod method = pair.second; return (this->*method)(urlString); } } @@ -6385,13 +6436,11 @@ void Application::replaceDomainContent(const QString& url) { QByteArray urlData(url.toUtf8()); auto limitedNodeList = DependencyManager::get(); const auto& domainHandler = limitedNodeList->getDomainHandler(); - limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { - return node->getType() == NodeType::EntityServer && node->getActiveSocket(); - }, [&urlData, limitedNodeList, &domainHandler](const SharedNodePointer& octreeNode) { - auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); - octreeFilePacket->write(urlData); - limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr()); - }); + + auto octreeFilePacket = NLPacket::create(PacketType::DomainContentReplacementFromUrl, urlData.size(), true); + octreeFilePacket->write(urlData); + limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr()); + auto addressManager = DependencyManager::get(); addressManager->handleLookupString(DOMAIN_SPAWNING_POINT); QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT; @@ -6528,7 +6577,7 @@ void Application::addAssetToWorldFromURL(QString url) { } else { filename.remove(".zip"); } - + } if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { @@ -6632,17 +6681,17 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo addAssetToWorldInfo(filename, "Adding " + mapping.mid(1) + " to the Asset Server."); - addAssetToWorldWithNewMapping(path, mapping, 0); + addAssetToWorldWithNewMapping(path, mapping, 0, isZip, isBlocks); } -void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) { +void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy, bool isZip, bool isBlocks) { auto request = DependencyManager::get()->createGetMappingRequest(mapping); QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { const int MAX_COPY_COUNT = 100; // Limit number of duplicate assets; recursion guard. auto result = request->getError(); if (result == GetMappingRequest::NotFound) { - addAssetToWorldUpload(filePath, mapping); + addAssetToWorldUpload(filePath, mapping, isZip, isBlocks); } else if (result != GetMappingRequest::NoError) { QString errorInfo = "Could not map asset name: " + mapping.left(mapping.length() - QString::number(copy).length() - 1); @@ -6654,7 +6703,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin } copy++; mapping = mapping.insert(mapping.lastIndexOf("."), "-" + QString::number(copy)); - addAssetToWorldWithNewMapping(filePath, mapping, copy); + addAssetToWorldWithNewMapping(filePath, mapping, copy, isZip, isBlocks); } else { QString errorInfo = "Too many copies of asset name: " + mapping.left(mapping.length() - QString::number(copy).length() - 1); @@ -6667,7 +6716,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin request->start(); } -void Application::addAssetToWorldUpload(QString filePath, QString mapping) { +void Application::addAssetToWorldUpload(QString filePath, QString mapping, bool isZip, bool isBlocks) { qInfo(interfaceapp) << "Uploading" << filePath << "to Asset Server as" << mapping; auto upload = DependencyManager::get()->createUpload(filePath); QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { @@ -6676,7 +6725,7 @@ void Application::addAssetToWorldUpload(QString filePath, QString mapping) { qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { - addAssetToWorldSetMapping(filePath, mapping, hash); + addAssetToWorldSetMapping(filePath, mapping, hash, isZip, isBlocks); } // Remove temporary directory created by Clara.io market place download. @@ -6693,7 +6742,7 @@ void Application::addAssetToWorldUpload(QString filePath, QString mapping) { upload->start(); } -void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash) { +void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash, bool isZip, bool isBlocks) { auto request = DependencyManager::get()->createSetMappingRequest(mapping, hash); connect(request, &SetMappingRequest::finished, this, [=](SetMappingRequest* request) mutable { if (request->getError() != SetMappingRequest::NoError) { @@ -6701,9 +6750,10 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { - // to prevent files that aren't models from being loaded into world automatically - if (filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION) || - filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) { + // to prevent files that aren't models or texture files from being loaded into world automatically + if ((filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION)) || + ((filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) && + ((!isBlocks) && (!isZip)))) { addAssetToWorldAddEntity(filePath, mapping); } else { qCDebug(interfaceapp) << "Zipped contents are not supported entity files"; @@ -7034,7 +7084,7 @@ void Application::packageModel() { void Application::openUrl(const QUrl& url) const { if (!url.isEmpty()) { - if (url.scheme() == HIFI_URL_SCHEME) { + if (url.scheme() == URL_SCHEME_HIFI) { DependencyManager::get()->handleLookupString(url.toString()); } else { // address manager did not handle - ask QDesktopServices to handle @@ -7348,10 +7398,35 @@ bool Application::isThrottleRendering() const { } bool Application::hasFocus() const { - if (_displayPlugin) { - return getActiveDisplayPlugin()->hasFocus(); + bool result = (QApplication::activeWindow() != nullptr); +#if defined(Q_OS_WIN) + // On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't + // take user focus away from their current window. So also check whether the application is the user's current foreground + // window. + result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow(); +#endif + return result; +} + +void Application::setFocus() { + // Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and + // flashes the taskbar icon. + auto window = qApp->getWindow(); + window->activateWindow(); +} + +void Application::raise() { + auto windowState = qApp->getWindow()->windowState(); + if (windowState & Qt::WindowMinimized) { + if (windowState & Qt::WindowMaximized) { + qApp->getWindow()->showMaximized(); + } else if (windowState & Qt::WindowFullScreen) { + qApp->getWindow()->showFullScreen(); + } else { + qApp->getWindow()->showNormal(); + } } - return (QApplication::activeWindow() != nullptr); + qApp->getWindow()->raise(); } void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) { @@ -7375,7 +7450,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const { return _displayPlugin; } - if (!_displayPlugin) { + if (!_aboutToQuit && !_displayPlugin) { const_cast(this)->updateDisplayMode(); Q_ASSERT(_displayPlugin); } @@ -7575,7 +7650,7 @@ void Application::updateDisplayMode() { menu->setIsOptionChecked(MenuOption::FirstPerson, true); cameraMenuChanged(); } - + // Remove the mirror camera option from menu if in HMD mode auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror); mirrorAction->setVisible(!isHmd); diff --git a/interface/src/Application.h b/interface/src/Application.h index 8190a0a13b..d7fbb48a58 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -161,6 +161,8 @@ public: QRect getRecommendedHUDRect() const; glm::vec2 getDeviceSize() const; bool hasFocus() const; + void setFocus(); + void raise(); void showCursor(const Cursor::Icon& cursor); @@ -272,10 +274,6 @@ public: void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); - graphics::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; } - gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; } - gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; } - OverlayID getTabletScreenID() const; OverlayID getTabletHomeButtonID() const; QUuid getTabletFrameID() const; // may be an entity or an overlay @@ -286,6 +284,8 @@ public: bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } void saveNextPhysicsStats(QString filename); + bool isServerlessMode() const; + void replaceDomainContent(const QString& url); signals: @@ -297,7 +297,6 @@ signals: void activeDisplayPluginChanged(); void uploadRequest(QString path); - void receivedHifiSchemeURL(const QString& url); public slots: QVector pasteEntities(float x, float y, float z); @@ -321,11 +320,11 @@ public slots: // FIXME: Move addAssetToWorld* methods to own class? void addAssetToWorldFromURL(QString url); void addAssetToWorldFromURLRequestFinished(); - void addAssetToWorld(QString filePath, QString zipFile, bool isZip, bool isBlocks); + void addAssetToWorld(QString filePath, QString zipFile, bool isZip = false, bool isBlocks = false); void addAssetToWorldUnzipFailure(QString filePath); - void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy); - void addAssetToWorldUpload(QString filePath, QString mapping); - void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash); + void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy, bool isZip = false, bool isBlocks = false); + void addAssetToWorldUpload(QString filePath, QString mapping, bool isZip = false, bool isBlocks = false); + void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash, bool isZip = false, bool isBlocks = false); void addAssetToWorldAddEntity(QString filePath, QString mapping); void handleUnzip(QString sourceFile, QStringList destinationFile, bool autoAdd, bool isZip, bool isBlocks); @@ -393,6 +392,11 @@ public slots: const QString getPreferredCursor() const { return _preferredCursor.get(); } void setPreferredCursor(const QString& cursor); + void setIsServerlessMode(bool serverlessDomain); + void loadServerlessDomain(QUrl domainURL); + + Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); } + private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); void onDesktopRootContextCreated(QQmlContext* qmlContext); @@ -425,7 +429,7 @@ private slots: void setSessionUUID(const QUuid& sessionUUID) const; - void domainChanged(const QString& domainHostname); + void domainURLChanged(QUrl domainURL); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; void nodeActivated(SharedNodePointer node); @@ -571,7 +575,7 @@ private: Setting::Handle _preferredCursor; float _scaleMirror; - float _rotateMirror; + float _mirrorYawOffset; float _raiseMirror; QSet _keysPressed; @@ -605,7 +609,7 @@ private: GLCanvas* _glWidget{ nullptr }; typedef bool (Application::* AcceptURLMethod)(const QString &); - static const QHash _acceptedExtensions; + static const std::vector> _acceptedExtensions; glm::uvec2 _renderResolution; @@ -621,6 +625,7 @@ private: struct AppRenderArgs { render::Args _renderArgs; glm::mat4 _eyeToWorld; + glm::mat4 _view; glm::mat4 _eyeOffsets[2]; glm::mat4 _eyeProjections[2]; glm::mat4 _headPose; @@ -676,10 +681,6 @@ private: ConnectionMonitor _connectionMonitor; - graphics::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ; - gpu::TexturePointer _defaultSkyboxTexture; - gpu::TexturePointer _defaultSkyboxAmbientTexture; - QTimer _addAssetToWorldResizeTimer; QHash _addAssetToWorldResizeList; diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index 5cc072df37..e1015ca5d1 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -90,10 +90,10 @@ void Application::paintGL() { { PROFILE_RANGE(render, "/gpuContextReset"); - _gpuContext->beginFrame(HMDSensorPose); + _gpuContext->beginFrame(_appRenderArgs._view, HMDSensorPose); // Reset the gpu::Context Stages // Back to the default framebuffer; - gpu::doInBatch(_gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("Application_render::gpuContextReset", _gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); } @@ -216,7 +216,7 @@ void Application::runRenderFrame(RenderArgs* renderArgs) { // Make sure the WorldBox is in the scene // For the record, this one RenderItem is the first one we created and added to the scene. - // We could meoee that code elsewhere but you know... + // We could move that code elsewhere but you know... if (!render::Item::isValidID(WorldBoxRenderData::_item)) { auto worldBoxRenderData = std::make_shared(); auto worldBoxRenderPayload = std::make_shared(worldBoxRenderData); diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 94fdacb5c0..33cfc481d7 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -49,7 +49,7 @@ void DiscoverabilityManager::updateLocation() { auto accountManager = DependencyManager::get(); auto addressManager = DependencyManager::get(); auto& domainHandler = DependencyManager::get()->getDomainHandler(); - bool discoverable = (_mode.get() != Discoverability::None); + bool discoverable = (_mode.get() != Discoverability::None) && !domainHandler.isServerless(); if (accountManager->isLoggedIn()) { @@ -129,7 +129,7 @@ void DiscoverabilityManager::updateLocation() { // Update Steam if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { - steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingShareableAddress()); + steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingPublicAddress()); } } diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 73408377c0..d7d73e962a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -86,10 +86,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } - qCDebug(interfaceapp) << "adjusting LOD DOWN" - << "fps =" << currentFPS - << "targetFPS =" << getLODDecreaseFPS() - << "octreeSizeScale =" << _octreeSizeScale; emit LODDecreased(); // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime // to provide an FPS just above the decrease threshold. It will drift close to its @@ -111,10 +107,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; } - qCDebug(interfaceapp) << "adjusting LOD UP" - << "fps =" << currentFPS - << "targetFPS =" << getLODDecreaseFPS() - << "octreeSizeScale =" << _octreeSizeScale; emit LODIncreased(); // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime // to provide an FPS just below the increase threshold. It will drift close to its diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9e11ad801d..3517c10d3a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -9,11 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include -#include -#include #include @@ -45,13 +44,16 @@ #include "ui/StandAloneJSConsole.h" #include "InterfaceLogging.h" #include "LocationBookmarks.h" -#include "Menu.h" +#include "DeferredLightingEffect.h" + +#include "AmbientOcclusionEffect.h" +#include "RenderShadowTask.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif - +#include "Menu.h" extern bool DEV_DECIMATE_TEXTURES; @@ -307,43 +309,6 @@ Menu::Menu() { // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); - // Developer > Console... - addActionToQMenuAndActionHash(developerMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole); - - // Developer > Scripting >>> - MenuWrapper* scriptingOptionsMenu = developerMenu->addMenu("Scripting"); - - // Developer > Scripting > Log... - addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog())); - - // Developer > Scripting > Entity Script Server Log - auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0, - qApp, SLOT(toggleEntityScriptServerLogDialog())); - - QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { - auto nodeList = DependencyManager::get(); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - }); - - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - - // Developer > Scripting > Script Log (HMD Friendly)... - action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "Script Log (HMD Friendly)...", Qt::NoButton, - qApp, SLOT(showScriptLogs())); - - // Developer > Scripting > API Debugger - action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger..."); - connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); - }); - // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, @@ -360,6 +325,36 @@ Menu::Menu() { // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); + action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, true); + connect(action, &QAction::triggered, [action] { + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + if (renderConfig) { + auto mainViewShadowTaskConfig = renderConfig->getConfig("RenderMainView.RenderShadowTask"); + if (mainViewShadowTaskConfig) { + if (action->isChecked()) { + mainViewShadowTaskConfig->setPreset("Enabled"); + } else { + mainViewShadowTaskConfig->setPreset("None"); + } + } + } + }); + + action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion, 0, false); + connect(action, &QAction::triggered, [action] { + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + if (renderConfig) { + auto mainViewAmbientOcclusionConfig = renderConfig->getConfig("RenderMainView.AmbientOcclusion"); + if (mainViewAmbientOcclusionConfig) { + if (action->isChecked()) { + mainViewAmbientOcclusionConfig->setPreset("Enabled"); + } else { + mainViewAmbientOcclusionConfig->setPreset("None"); + } + } + } + }); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DefaultSkybox, 0, true); @@ -397,6 +392,9 @@ Menu::Menu() { textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture512MB, 0, false)); textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture1024MB, 0, false)); textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture2048MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture4096MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture6144MB, 0, false)); + textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture8192MB, 0, false)); connect(textureGroup, &QActionGroup::triggered, [textureGroup] { auto checked = textureGroup->checkedAction(); auto text = checked->text(); @@ -413,6 +411,12 @@ Menu::Menu() { newMaxTextureMemory = MB_TO_BYTES(1024); } else if (MenuOption::RenderMaxTexture2048MB == text) { newMaxTextureMemory = MB_TO_BYTES(2048); + } else if (MenuOption::RenderMaxTexture4096MB == text) { + newMaxTextureMemory = MB_TO_BYTES(4096); + } else if (MenuOption::RenderMaxTexture6144MB == text) { + newMaxTextureMemory = MB_TO_BYTES(6144); + } else if (MenuOption::RenderMaxTexture8192MB == text) { + newMaxTextureMemory = MB_TO_BYTES(8192); } gpu::Texture::setAllowedGPUMemoryUsage(newMaxTextureMemory); }); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 5adde90b0f..7c559414a2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -155,6 +155,10 @@ namespace MenuOption { const QString RenderMaxTexture512MB = "512 MB"; const QString RenderMaxTexture1024MB = "1024 MB"; const QString RenderMaxTexture2048MB = "2048 MB"; + const QString RenderMaxTexture3072MB = "3072 MB"; + const QString RenderMaxTexture4096MB = "4096 MB"; + const QString RenderMaxTexture6144MB = "6144 MB"; + const QString RenderMaxTexture8192MB = "8192 MB"; const QString RenderResolution = "Scale Resolution"; const QString RenderResolutionOne = "1"; const QString RenderResolutionTwoThird = "2/3"; @@ -204,6 +208,8 @@ namespace MenuOption { const QString WorldAxes = "World Axes"; const QString DesktopTabletToToolbar = "Desktop Tablet Becomes Toolbar"; const QString HMDTabletToToolbar = "HMD Tablet Becomes Toolbar"; + const QString Shadows = "Shadows"; + const QString AmbientOcclusion = "Ambient Occlusion"; } #endif // hifi_Menu_h diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 45f1756eb7..a4bf0bcefe 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -107,7 +107,7 @@ public: args->_displayMode = RenderArgs::MONO; args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) { batch.disableContextStereo(); batch.disableContextViewCorrection(); }); @@ -196,7 +196,7 @@ public: args->_displayMode = cachedArgs->_displayMode; args->_renderMode = cachedArgs->_renderMode; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("EndSecondaryCameraFrame::run", args->_context, [&](gpu::Batch& batch) { batch.restoreContextStereo(); batch.restoreContextViewCorrection(); }); diff --git a/interface/src/avatar/AvatarActionFarGrab.h b/interface/src/avatar/AvatarActionFarGrab.h index bcaf7f2f3c..97d4a6bb03 100644 --- a/interface/src/avatar/AvatarActionFarGrab.h +++ b/interface/src/avatar/AvatarActionFarGrab.h @@ -15,6 +15,24 @@ #include #include +/**jsdoc + * The "far-grab" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and + * orientation, optionally relative to another entity. Collisions between the entity and the user's avatar are disabled during + * the far-grab. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-FarGrab + * @property {Vec3} targetPosition=0,0,0 - The target position. + * @property {Quat} targetRotation=0,0,0,1 - The target rotation. + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * relative to this entity's position and rotation. + * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the + * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * is applied using an exponential decay. + * @property {number} angularTimeScale=3.4e+38 - Controls how long it takes for the entity's orientation to catch up with the + * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * action is applied using an exponential decay. + */ class AvatarActionFarGrab : public ObjectActionTractor { public: AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index d4fe8574ca..9d568e6d73 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -416,6 +416,26 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "hold" {@link Entities.ActionType|ActionType} positions and rotates an entity relative to an avatar's hand. + * Collisions between the entity and the user's avatar are disabled during the hold. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Hold + * @property {Uuid} holderID=MyAvatar.sessionUUID - The ID of the avatar holding the entity. + * @property {Vec3} relativePosition=0,0,0 - The target position relative to the avatar's hand. + * @property {Vec3} relativeRotation=0,0,0,1 - The target rotation relative to the avatar's hand. + * @property {number} timeScale=3.4e+38 - Controls how long it takes for the entity's position and rotation to catch up with + * the target. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action is + * applied using an exponential decay. + * @property {string} hand=right - The hand holding the entity: "left" or "right". + * @property {boolean} kinematic=false - If true, the entity is made kinematic during the action; the entity won't + * lag behind the hand but constraint actions such as "hinge" won't act properly. + * @property {boolean} kinematicSetVelocity=false - If true and kinematic is true, the + * entity's velocity property will be set during the action, e.g., so that other scripts may use the value. + * @property {boolean} ignoreIK=false - If true, the entity follows the HMD controller rather than the avatar's + * hand. + */ QVariantMap AvatarActionHold::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&]{ diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b6fa3fde96..a9cab9582d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1848,12 +1848,6 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); } -void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) { - if (model->isActive() && model->isRenderable()) { - model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true); - } -} - void MyAvatar::initHeadBones() { int neckJointIndex = -1; if (_skeletonModel->isLoaded()) { @@ -2043,8 +2037,12 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) { + _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + + _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(), + render::ItemKey::TAG_BITS_NONE, true); } } } @@ -2188,7 +2186,6 @@ void MyAvatar::updateActionMotor(float deltaTime) { glm::vec3 direction = forward + right; if (state == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { - // we can fly --> support vertical motion glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; direction += up; } @@ -2206,10 +2203,11 @@ void MyAvatar::updateActionMotor(float deltaTime) { if (state == CharacterController::State::Hover) { // we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed + float motorSpeed = glm::length(_actionMotorVelocity); - float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED; + float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar; float speedGrowthTimescale = 2.0f; - float speedIncreaseFactor = 1.8f; + float speedIncreaseFactor = 1.8f * _walkSpeedScalar; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor; const float maxBoostSpeed = getSensorToWorldScale() * MAX_BOOST_SPEED; @@ -2225,7 +2223,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { _actionMotorVelocity = motorSpeed * direction; } else { // we're interacting with a floor --> simple horizontal speed and exponential decay - _actionMotorVelocity = getSensorToWorldScale() * _walkSpeed.get() * direction; + _actionMotorVelocity = getSensorToWorldScale() * (_walkSpeed.get() * _walkSpeedScalar) * direction; } float boomChange = getDriveKey(ZOOM); @@ -2762,6 +2760,18 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const { } } +void MyAvatar::triggerVerticalRecenter() { + _follow.setForceActivateVertical(true); +} + +void MyAvatar::triggerHorizontalRecenter() { + _follow.setForceActivateHorizontal(true); +} + +void MyAvatar::triggerRotationRecenter() { + _follow.setForceActivateRotation(true); +} + // old school meat hook style glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { glm::vec3 headPosition; @@ -2818,7 +2828,11 @@ float MyAvatar::getUserEyeHeight() const { } float MyAvatar::getWalkSpeed() const { - return _walkSpeed.get(); + return _walkSpeed.get() * _walkSpeedScalar; +} + +void MyAvatar::setSprintMode(bool sprint) { + _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } void MyAvatar::setWalkSpeed(float value) { @@ -2955,7 +2969,9 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); + return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; + } bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -2972,6 +2988,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const float MAX_FORWARD_LEAN = 0.15f; const float MAX_BACKWARD_LEAN = 0.1f; + if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { return true; } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { @@ -2979,6 +2996,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, } return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; + } bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -2986,6 +3004,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co const float CYLINDER_BOTTOM = -1.5f; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } @@ -3003,6 +3022,19 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); } + } else { + if (!isActive(Rotation) && getForceActivateRotation()) { + activate(Rotation); + setForceActivateRotation(false); + } + if (!isActive(Horizontal) && getForceActivateHorizontal()) { + activate(Horizontal); + setForceActivateHorizontal(false); + } + if (!isActive(Vertical) && getForceActivateVertical()) { + activate(Vertical); + setForceActivateVertical(false); + } } glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix; @@ -3052,6 +3084,30 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co } } +bool MyAvatar::FollowHelper::getForceActivateRotation() const { + return _forceActivateRotation; +} + +void MyAvatar::FollowHelper::setForceActivateRotation(bool val) { + _forceActivateRotation = val; +} + +bool MyAvatar::FollowHelper::getForceActivateVertical() const { + return _forceActivateVertical; +} + +void MyAvatar::FollowHelper::setForceActivateVertical(bool val) { + _forceActivateVertical = val; +} + +bool MyAvatar::FollowHelper::getForceActivateHorizontal() const { + return _forceActivateHorizontal; +} + +void MyAvatar::FollowHelper::setForceActivateHorizontal(bool val) { + _forceActivateHorizontal = val; +} + float MyAvatar::getAccelerationEnergy() { glm::vec3 velocity = getWorldVelocity(); int changeInVelocity = abs(velocity.length() - priorVelocity.length()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index fa5206e128..6f82c7dfb9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -299,7 +299,7 @@ public: * (such as look at vectors, hand sensors etc.). Each animation specified in the avatar-animation.json file is known as an animation role. * Animation roles map to easily understandable actions that the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd." * getAnimationRoles() is used get the list of animation roles defined in the avatar-animation.json. - * @function MyAvatar.getAnimatationRoles + * @function MyAvatar.getAnimationRoles * @example This example prints the list of animation roles defined in the avatar's avatar-animation.json file to the debug log. * var roles = MyAvatar.getAnimationRoles(); * print("Animation Roles:"); @@ -346,7 +346,7 @@ public: * restoreRoleAnimation() is used to restore a specified animation role's default animation clip. If you have not specified an override animation * for the specified role, this function will have no effect. * @function MyAvatar.restoreRoleAnimation - * @param rule {string} The animation role clip to restore + * @param role {string} The animation role clip to restore */ Q_INVOKABLE void restoreRoleAnimation(const QString& role); @@ -395,6 +395,7 @@ public: // Set what driving keys are being pressed to control thrust levels void clearDriveKeys(); void setDriveKey(DriveKeys key, float val); + void setSprintMode(bool sprint); float getDriveKey(DriveKeys key) const; Q_INVOKABLE float getRawDriveKey(DriveKeys key) const; void relayDriveKeysToCharacterController(); @@ -403,6 +404,32 @@ public: Q_INVOKABLE void enableDriveKey(DriveKeys key); Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const; + /**jsdoc + *The triggerVerticalRecenter function activates one time the recentering + *behaviour in the vertical direction. This call is only takes effect when the property + *MyAvatar.hmdLeanRecenterEnabled is set to false. + *@function MyAvatar.triggerVerticalRecenter + * + */ + + /**jsdoc + *The triggerHorizontalRecenter function activates one time the recentering behaviour + *in the horizontal direction. This call is only takes effect when the property + *MyAvatar.hmdLeanRecenterEnabled is set to false. + *@function MyAvatar.triggerHorizontalRecenter + */ + + /**jsdoc + *The triggerRotationRecenter function activates one time the recentering behaviour + *in the rotation of the root of the avatar. This call is only takes effect when the property + *MyAvatar.hmdLeanRecenterEnabled is set to false. + *@function MyAvatar.triggerRotationRecenter + */ + + Q_INVOKABLE void triggerVerticalRecenter(); + Q_INVOKABLE void triggerHorizontalRecenter(); + Q_INVOKABLE void triggerRotationRecenter(); + eyeContactTarget getEyeContactTarget(); const MyHead* getMyHead() const; @@ -681,8 +708,6 @@ private: // These are made private for MyAvatar so that you will use the "use" methods instead virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; - void setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visiblity); - virtual void updatePalms() override {} void lateUpdatePalms(); @@ -804,6 +829,15 @@ private: bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); + bool getForceActivateRotation() const; + void setForceActivateRotation(bool val); + bool getForceActivateVertical() const; + void setForceActivateVertical(bool val); + bool getForceActivateHorizontal() const; + void setForceActivateHorizontal(bool val); + std::atomic _forceActivateRotation{ false }; + std::atomic _forceActivateVertical{ false }; + std::atomic _forceActivateHorizontal{ false }; }; FollowHelper _follow; @@ -838,7 +872,9 @@ private: std::map _controllerPoseMap; mutable std::mutex _controllerPoseMapMutex; - bool _hmdLeanRecenterEnabled = true; + bool _hmdLeanRecenterEnabled { true }; + bool _sprint { false }; + AnimPose _prePhysicsRoomPose; std::mutex _holdActionsMutex; std::vector _holdActions; @@ -868,6 +904,7 @@ private: // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; + float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 712c505e8a..573d4db33c 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -35,12 +35,19 @@ QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) { QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) { QString response = reply.readAll(); qWarning(commerce) << "FAILED" << label << response; - QJsonObject result - { - { "status", "fail" }, - { "message", response } - }; - return result; + + // tempResult will be NULL if the response isn't valid JSON. + QJsonDocument tempResult = QJsonDocument::fromJson(response.toLocal8Bit()); + if (tempResult.isNull()) { + QJsonObject result + { + { "status", "fail" }, + { "message", response } + }; + return result; + } else { + return tempResult.object(); + } } #define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply& reply) { emit NAME##Result(apiResponse(#NAME, reply)); } #define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply& reply) { emit NAME##Result(failResponse(#NAME, reply)); } @@ -52,6 +59,8 @@ Handler(inventory) Handler(transferHfcToNode) Handler(transferHfcToUsername) Handler(alreadyOwned) +Handler(availableUpdates) +Handler(updateItem) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -80,9 +89,13 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) { auto wallet = DependencyManager::get(); - requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + requestParams["public_keys"] = QJsonArray::fromStringList(cachedPublicKeys); + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); + } else { + qDebug(commerce) << "User attempted to call keysQuery, but cachedPublicKeys was empty!"; + } } void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { @@ -287,7 +300,7 @@ void Ledger::account() { // The api/failResponse is called just for the side effect of logging. void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); } void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); } -void Ledger::updateLocation(const QString& asset_id, const QString location, const bool controlledFailure) { +void Ledger::updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings, const bool controlledFailure) { auto wallet = DependencyManager::get(); auto walletScriptingInterface = DependencyManager::get(); uint walletStatus = walletScriptingInterface->getWalletStatus(); @@ -296,14 +309,21 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con emit walletScriptingInterface->walletNotSetup(); qDebug(commerce) << "User attempted to update the location of a certificate, but their wallet wasn't ready. Status:" << walletStatus; } else { - QStringList keys = wallet->listPublicKeys(); - QString key = keys[0]; - QJsonObject transaction; - transaction["certificate_id"] = asset_id; - transaction["place_name"] = location; - QJsonDocument transactionDoc{ transaction }; - auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); - signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + QString key = cachedPublicKeys[0]; + QJsonObject transaction; + transaction["certificate_id"] = asset_id; + transaction["place_name"] = location; + if (alsoUpdateSiblings) { + transaction["also_update_siblings"] = true; + } + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure); + } else { + qDebug(commerce) << "User attempted to update the location of a certificate, but cachedPublicKeys was empty!"; + } } } @@ -324,7 +344,9 @@ void Ledger::certificateInfoSuccess(QNetworkReply& reply) { qInfo(commerce) << "certificateInfo" << "response" << QJsonDocument(replyObject).toJson(QJsonDocument::Compact); emit certificateInfoResult(replyObject); } -void Ledger::certificateInfoFailure(QNetworkReply& reply) { failResponse("certificateInfo", reply); } +void Ledger::certificateInfoFailure(QNetworkReply& reply) { + emit certificateInfoResult(failResponse("certificateInfo", reply)); +} void Ledger::certificateInfo(const QString& certificateId) { QString endpoint = "proof_of_purchase_status/transfer"; QJsonObject request; @@ -359,7 +381,32 @@ void Ledger::alreadyOwned(const QString& marketplaceId) { auto wallet = DependencyManager::get(); QString endpoint = "already_owned"; QJsonObject request; - request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - request["marketplace_item_id"] = marketplaceId; - send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + request["marketplace_item_id"] = marketplaceId; + send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); + } else { + qDebug(commerce) << "User attempted to use the alreadyOwned endpoint, but cachedPublicKeys was empty!"; + } +} + +void Ledger::getAvailableUpdates(const QString& itemId) { + auto wallet = DependencyManager::get(); + QString endpoint = "available_updates"; + QJsonObject request; + request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + if (!itemId.isEmpty()) { + request["marketplace_item_id"] = itemId; + } + send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); +} + +void Ledger::updateItem(const QString& hfc_key, const QString& certificate_id) { + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["certificate_id"] = certificate_id; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, hfc_key, "update_item", "updateItemSuccess", "updateItemFailure"); } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 703ebda2dc..1ccab9c2b1 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -31,11 +31,13 @@ public: void inventory(const QStringList& keys); void history(const QStringList& keys, const int& pageNumber); void account(); - void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); + void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage); void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage); void alreadyOwned(const QString& marketplaceId); + void getAvailableUpdates(const QString& itemId = ""); + void updateItem(const QString& hfc_key, const QString& certificate_id); enum CertificateStatus { CERTIFICATE_STATUS_UNKNOWN = 0, @@ -57,6 +59,8 @@ signals: void transferHfcToNodeResult(QJsonObject result); void transferHfcToUsernameResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); + void availableUpdatesResult(QJsonObject result); + void updateItemResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -83,6 +87,10 @@ public slots: void transferHfcToUsernameFailure(QNetworkReply& reply); void alreadyOwnedSuccess(QNetworkReply& reply); void alreadyOwnedFailure(QNetworkReply& reply); + void availableUpdatesSuccess(QNetworkReply& reply); + void availableUpdatesFailure(QNetworkReply& reply); + void updateItemSuccess(QNetworkReply& reply); + void updateItemFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 557193c074..7e91ffaa62 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -38,7 +38,8 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult); connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); - connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); + connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); + connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { @@ -138,6 +139,11 @@ void QmlCommerce::setSoftReset() { wallet->setSoftReset(); } +void QmlCommerce::clearWallet() { + auto wallet = DependencyManager::get(); + wallet->clear(); +} + void QmlCommerce::setPassphrase(const QString& passphrase) { auto wallet = DependencyManager::get(); wallet->setPassphrase(passphrase); @@ -184,7 +190,9 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou ledger->transferHfcToUsername(key, username, amount, optionalMessage); } -void QmlCommerce::replaceContentSet(const QString& itemHref) { +void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) { + auto ledger = DependencyManager::get(); + ledger->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true); qApp->replaceDomainContent(itemHref); QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, @@ -344,3 +352,20 @@ bool QmlCommerce::openApp(const QString& itemHref) { return true; } + +void QmlCommerce::getAvailableUpdates(const QString& itemId) { + auto ledger = DependencyManager::get(); + ledger->getAvailableUpdates(itemId); +} + +void QmlCommerce::updateItem(const QString& certificateId) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + return emit updateItemResult(result); + } + QString key = keys[0]; + ledger->updateItem(key, certificateId); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 6a4eaa2be2..f58726e1d7 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -43,6 +43,8 @@ signals: void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); + void availableUpdatesResult(QJsonObject result); + void updateItemResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -67,6 +69,7 @@ protected: Q_INVOKABLE void setPassphrase(const QString& passphrase); Q_INVOKABLE void changePassphrase(const QString& oldPassphrase, const QString& newPassphrase); Q_INVOKABLE void setSoftReset(); + Q_INVOKABLE void clearWallet(); Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); @@ -81,13 +84,16 @@ protected: Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage); - Q_INVOKABLE void replaceContentSet(const QString& itemHref); + Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); Q_INVOKABLE QString getInstalledApps(); Q_INVOKABLE bool installApp(const QString& appHref); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); + Q_INVOKABLE void getAvailableUpdates(const QString& itemId = ""); + Q_INVOKABLE void updateItem(const QString& certificateId); + private: QString _appsPath; }; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index fad82115d6..060f8de09b 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -343,19 +343,23 @@ Wallet::Wallet() { auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { getWalletStatus(); - _publicKeys.clear(); - - if (_securityImage) { - delete _securityImage; - } - _securityImage = nullptr; - - // tell the provider we got nothing - updateImageProvider(); - _passphrase->clear(); + clear(); }); } +void Wallet::clear() { + _publicKeys.clear(); + + if (_securityImage) { + delete _securityImage; + } + _securityImage = nullptr; + + // tell the provider we got nothing + updateImageProvider(); + _passphrase->clear(); +} + Wallet::~Wallet() { if (_securityImage) { delete _securityImage; diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index d771f404e5..8a7d6b8c07 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -49,8 +49,9 @@ public: bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); } bool walletIsAuthenticatedWithPassphrase(); bool changePassphrase(const QString& newPassphrase); - void setSoftReset() { _isOverridingServer = true; } + void setSoftReset() { _isOverridingServer = true; } bool wasSoftReset() { bool was = _isOverridingServer; _isOverridingServer = false; return was; } + void clear(); void getWalletStatus(); enum WalletStatus { diff --git a/interface/src/main.cpp b/interface/src/main.cpp index e80dc1d213..51ec4b1327 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -129,7 +129,7 @@ int main(int argc, const char* argv[]) { if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) { if (parser.isSet(urlOption)) { QUrl url = QUrl(parser.value(urlOption)); - if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) { + if (url.isValid() && url.scheme() == URL_SCHEME_HIFI) { qDebug() << "Writing URL to local socket"; socket.write(url.toString().toUtf8()); if (!socket.waitForBytesWritten(5000)) { diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index a4fe516590..bd71e47cf0 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -81,26 +81,14 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta }); } -void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { - if (!id.isNull() && props.isValid()) { - QVariantMap propMap = props.toMap(); - propMap.remove("visible"); - qApp->getOverlays().editOverlay(id, propMap); - } -} +PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) { + PickResultPointer visualPickResult = pickResult; + auto rayPickResult = std::static_pointer_cast(visualPickResult); + IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("position", vec3toVariant(pickRay.origin)); - startProps.insert("visible", true); - startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - glm::vec3 endVec; - if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) { - endVec = pickRay.origin + pickRay.direction * distance; - } else { + if (type != IntersectionType::HUD) { + glm::vec3 endVec; + PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); if (!_lockEndObject.id.isNull()) { glm::vec3 pos; glm::quat rot; @@ -122,17 +110,54 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter } const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); - } else { + glm::vec3 direction = endVec - pickRay.origin; + float distance = glm::distance(pickRay.origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + + rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; + rayPickResult->objectID = _lockEndObject.id; + rayPickResult->intersection = endVec; + rayPickResult->distance = distance; + rayPickResult->surfaceNormal = -normalizedDirection; + rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); + } else if (type != IntersectionType::NONE && _lockEnd) { if (type == IntersectionType::ENTITY) { - endVec = DependencyManager::get()->getEntityTransform(objectID)[3]; + endVec = DependencyManager::get()->getEntityTransform(rayPickResult->objectID)[3]; } else if (type == IntersectionType::OVERLAY) { - endVec = vec3FromVariant(qApp->getOverlays().getProperty(objectID, "position").value); + endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value); } else if (type == IntersectionType::AVATAR) { - endVec = DependencyManager::get()->getAvatar(objectID)->getPosition(); + endVec = DependencyManager::get()->getAvatar(rayPickResult->objectID)->getPosition(); } + glm::vec3 direction = endVec - pickRay.origin; + float distance = glm::distance(pickRay.origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + rayPickResult->intersection = endVec; + rayPickResult->distance = distance; + rayPickResult->surfaceNormal = -normalizedDirection; + rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); } } - + return visualPickResult; +} + +void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + QVariantMap propMap = props.toMap(); + propMap.remove("visible"); + qApp->getOverlays().editOverlay(id, propMap); + } +} + +void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) { + if (!renderState.getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(pickRay.origin)); + startProps.insert("visible", true); + startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); + } + glm::vec3 endVec = pickRay.origin + pickRay.direction * distance; + QVariant end = vec3toVariant(endVec); if (!renderState.getPathID().isNull()) { QVariantMap pathProps; @@ -195,15 +220,15 @@ void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && (type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) { - PickRay pickRay(rayPickResult->pickVariant); + PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay(); QUuid uid = rayPickResult->objectID; float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; - updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false); + updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay); disableRenderState(_defaultRenderStates[_currentRenderState].second); } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { disableRenderState(_renderStates[_currentRenderState]); PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true); + updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay); } else if (!_currentRenderState.empty()) { disableRenderState(_renderStates[_currentRenderState]); disableRenderState(_defaultRenderStates[_currentRenderState].second); @@ -386,4 +411,4 @@ glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::v default: return glm::vec2(NAN); } -} \ No newline at end of file +} diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index efc5c02729..964881be42 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -82,6 +82,7 @@ public: protected: PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; + PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; PickedObject getHoveredObject(const PickResultPointer& pickResult) override; Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; @@ -102,7 +103,7 @@ private: LockEndObject _lockEndObject; void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); - void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState); + void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay); void disableRenderState(const RenderState& renderState); struct TriggerState { diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 43e0c059f0..1bf6dd2f8e 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -174,4 +174,12 @@ void PickScriptingInterface::registerMetaTypes(QScriptEngine* engine) { engine->globalObject().setProperty("PickType", pickTypes); qScriptRegisterMetaType(engine, pickTypesToScriptValue, pickTypesFromScriptValue); -} \ No newline at end of file +} + +unsigned int PickScriptingInterface::getPerFrameTimeBudget() const { + return DependencyManager::get()->getPerFrameTimeBudget(); +} + +void PickScriptingInterface::setPerFrameTimeBudget(unsigned int numUsecs) { + DependencyManager::get()->setPerFrameTimeBudget(numUsecs); +} diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 98427e34ca..288d3008bb 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -185,6 +185,14 @@ public: */ Q_INVOKABLE bool isMouse(unsigned int uid); + Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget) + /**jsdoc + * The max number of usec to spend per frame updating Pick results. + * @typedef {number} Picks.perFrameTimeBudget + */ + unsigned int getPerFrameTimeBudget() const; + void setPerFrameTimeBudget(unsigned int numUsecs); + public slots: static constexpr unsigned int PICK_NOTHING() { return 0; } static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); } @@ -202,4 +210,4 @@ public slots: static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } }; -#endif // hifi_PickScriptingInterface_h \ No newline at end of file +#endif // hifi_PickScriptingInterface_h diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index dc227c21ba..c5769ef4bb 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -69,18 +69,18 @@ void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, QJSValue startedCallback, QJSValue completedCallback, bool dropEvent) { static const QString helpText = - "Upload your asset to a specific folder by entering the full path. Specifying\n" + "Upload your asset to a specific folder by entering the full path. Specifying " "a new folder name will automatically create that folder for you."; static const QString dropHelpText = "This file will be added to your Asset Server.\n" - "Use the field below to place your file in a specific folder or to rename it.\n" + "Use the field below to place your file in a specific folder or to rename it. " "Specifying a new folder name will automatically create that folder for you."; auto offscreenUi = DependencyManager::get(); auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path", dropEvent ? dropHelpText : helpText, mapping); - if (!result.isValid()) { + if (!result.isValid() || result.toString() == "") { completedCallback.call({ -1 }); return; } @@ -140,7 +140,7 @@ void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue auto assetClient = DependencyManager::get(); auto request = assetClient->createDeleteMappingsRequest(paths); - connect(request, &DeleteMappingsRequest::finished, this, [this, callback](DeleteMappingsRequest* request) mutable { + connect(request, &DeleteMappingsRequest::finished, this, [callback](DeleteMappingsRequest* request) mutable { if (callback.isCallable()) { QJSValueList args { request->getErrorString() }; callback.call(args); @@ -152,11 +152,15 @@ void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue request->start(); } +void AssetMappingsScriptingInterface::sortProxyModel(int column, Qt::SortOrder order) { + _proxyModel.sort(column, order); +} + void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) { auto assetClient = DependencyManager::get(); auto request = assetClient->createGetAllMappingsRequest(); - connect(request, &GetAllMappingsRequest::finished, this, [this, callback](GetAllMappingsRequest* request) mutable { + connect(request, &GetAllMappingsRequest::finished, this, [callback](GetAllMappingsRequest* request) mutable { auto mappings = request->getMappings(); auto map = callback.engine()->newObject(); @@ -179,7 +183,7 @@ void AssetMappingsScriptingInterface::renameMapping(QString oldPath, QString new auto assetClient = DependencyManager::get(); auto request = assetClient->createRenameMappingRequest(oldPath, newPath); - connect(request, &RenameMappingRequest::finished, this, [this, callback](RenameMappingRequest* request) mutable { + connect(request, &RenameMappingRequest::finished, this, [callback](RenameMappingRequest* request) mutable { if (callback.isCallable()) { QJSValueList args{ request->getErrorString() }; callback.call(args); @@ -195,7 +199,7 @@ void AssetMappingsScriptingInterface::setBakingEnabled(QStringList paths, bool e auto assetClient = DependencyManager::get(); auto request = assetClient->createSetBakingEnabledRequest(paths, enabled); - connect(request, &SetBakingEnabledRequest::finished, this, [this, callback](SetBakingEnabledRequest* request) mutable { + connect(request, &SetBakingEnabledRequest::finished, this, [callback](SetBakingEnabledRequest* request) mutable { if (callback.isCallable()) { QJSValueList args{ request->getErrorString() }; callback.call(args); @@ -287,7 +291,7 @@ void AssetMappingModel::refresh() { item->setData(parts[i], Qt::UserRole + 2); item->setData("atp:" + fullPath, Qt::UserRole + 3); item->setData(fullPath, Qt::UserRole + 4); - + if (lastItem) { lastItem->appendRow(item); } else { diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h index 1a4c7dae48..b27a72fbd0 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.h +++ b/interface/src/scripting/AssetMappingsScriptingInterface.h @@ -82,6 +82,7 @@ public: Q_INVOKABLE void getAllMappings(QJSValue callback = QJSValue()); Q_INVOKABLE void renameMapping(QString oldPath, QString newPath, QJSValue callback = QJSValue()); Q_INVOKABLE void setBakingEnabled(QStringList paths, bool enabled, QJSValue callback = QJSValue()); + Q_INVOKABLE void sortProxyModel(int column, Qt::SortOrder order = Qt::AscendingOrder); protected: QSet _pendingRequests; diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index a130b46877..34a3630b78 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -182,7 +183,6 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) { } void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD) { - auto oldDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; selectedDevice = device; @@ -200,32 +200,137 @@ void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); } -void AudioDeviceList::onDevicesChanged(const QList& devices, bool isHMD) { - QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; +// Function returns 'strings similarity' as a number. The lesser number - the more similar strings are. Absolutely equal strings should return 0. +// Optimized version kindly provided by Ken +int levenshteinDistance(const QString& s1, const QString& s2) { + const int m = s1.size(); + const int n = s2.size(); - const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName; + if (m == 0) { + return n; + } + if (n == 0) { + return m; + } + + auto cost = (int*)alloca((n + 1) * sizeof(int)); + + for (int j = 0; j <= n; j++) { + cost[j] = j; + } + + for (int i = 0; i < m; i++) { + + int prev = i; + cost[0] = i + 1; + + for (int j = 0; j < n; j++) { + + int temp = cost[j + 1]; + cost[j + 1] = (s1[i] == s2[j]) ? prev : std::min(cost[j], std::min(temp, prev)) + 1; + prev = temp; + } + } + return cost[n]; +} + +std::shared_ptr getSimilarDevice(const QString& deviceName, const QList>& devices) { + + int minDistance = INT_MAX; + int minDistanceIndex = 0; + + for (auto i = 0; i < devices.length(); ++i) { + auto distance = levenshteinDistance(deviceName, devices[i]->info.deviceName()); + if (distance < minDistance) { + minDistance = distance; + minDistanceIndex = i; + } + } + + return devices[minDistanceIndex]; +} + +void AudioDeviceList::onDevicesChanged(const QList& devices) { beginResetModel(); - _devices.clear(); + QList> newDevices; + bool hmdIsSelected = false; + bool desktopIsSelected = false; + + foreach(const QAudioDeviceInfo& deviceInfo, devices) { + for (bool isHMD : {false, true}) { + auto &backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; + if (deviceInfo.deviceName() == backupSelectedDeviceName) { + QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; + selectedDevice = deviceInfo; + backupSelectedDeviceName.clear(); + } + } + } foreach(const QAudioDeviceInfo& deviceInfo, devices) { AudioDevice device; - bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; device.info = deviceInfo; device.display = device.info.deviceName() .replace("High Definition", "HD") .remove("Device") .replace(" )", ")"); - if (!selectedDevice.isNull()) { - isSelected = (device.info == selectedDevice); - } else { - //no selected device for context. fallback to saved - isSelected = (device.info.deviceName() == savedDeviceName); + + for (bool isHMD : {false, true}) { + QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; + bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; + + if (!selectedDevice.isNull()) { + isSelected = (device.info == selectedDevice); + } + else { + //no selected device for context. fallback to saved + const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName; + isSelected = (device.info.deviceName() == savedDeviceName); + } + + if (isSelected) { + if (isHMD) { + hmdIsSelected = isSelected; + } else { + desktopIsSelected = isSelected; + } + + // check if this device *is not* in old devices list - it means it was just re-plugged so needs to be selected explicitly + bool isNewDevice = true; + for (auto& oldDevice : _devices) { + if (oldDevice->info.deviceName() == device.info.deviceName()) { + isNewDevice = false; + break; + } + } + + if (isNewDevice) { + emit selectedDevicePlugged(device.info, isHMD); + } + } } + qDebug() << "adding audio device:" << device.display << device.selectedDesktop << device.selectedHMD << _mode; - _devices.push_back(newDevice(device)); + newDevices.push_back(newDevice(device)); } + if (!newDevices.isEmpty()) { + if (!hmdIsSelected) { + _backupSelectedHMDDeviceName = !_selectedHMDDevice.isNull() ? _selectedHMDDevice.deviceName() : _hmdSavedDeviceName; + auto device = getSimilarDevice(_backupSelectedHMDDeviceName, newDevices); + device->selectedHMD = true; + emit selectedDevicePlugged(device->info, true); + } + if (!desktopIsSelected) { + _backupSelectedDesktopDeviceName = !_selectedDesktopDevice.isNull() ? _selectedDesktopDevice.deviceName() : _desktopSavedDeviceName; + auto device = getSimilarDevice(_backupSelectedDesktopDeviceName, newDevices); + device->selectedDesktop = true; + emit selectedDevicePlugged(device->info, false); + } + } + + _devices.swap(newDevices); endResetModel(); } @@ -271,12 +376,10 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { // connections are made after client is initialized, so we must also fetch the devices const QList& devicesInput = client->getAudioDevices(QAudio::AudioInput); const QList& devicesOutput = client->getAudioDevices(QAudio::AudioOutput); - //setup HMD devices - _inputs.onDevicesChanged(devicesInput, true); - _outputs.onDevicesChanged(devicesOutput, true); - //setup Desktop devices - _inputs.onDevicesChanged(devicesInput, false); - _outputs.onDevicesChanged(devicesOutput, false); + + //setup devices + _inputs.onDevicesChanged(devicesInput); + _outputs.onDevicesChanged(devicesOutput); } AudioDevices::~AudioDevices() {} @@ -375,11 +478,19 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList& devices, bool isHMD); + void onDevicesChanged(const QList& devices); protected: friend class AudioDevices; @@ -64,6 +65,8 @@ protected: const QAudio::Mode _mode; QAudioDeviceInfo _selectedDesktopDevice; QAudioDeviceInfo _selectedHMDDevice; + QString _backupSelectedDesktopDeviceName; + QString _backupSelectedHMDDeviceName; QList> _devices; QString _hmdSavedDeviceName; QString _desktopSavedDeviceName; @@ -117,13 +120,13 @@ public: AudioDevices(bool& contextIsHMD); virtual ~AudioDevices(); - void chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD); - void chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD); - signals: void nop(); private slots: + void chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD); + void chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD); + void onContextChanged(const QString& context); void onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice, bool isHMD); diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 95be7b29e9..49ecc4774d 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -89,6 +89,16 @@ void ControllerScriptingInterface::setVPadEnabled(const bool enable) { virtualPadManager.enable(enable); } +void ControllerScriptingInterface::setVPadHidden(const bool hidden) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + virtualPadManager.hide(hidden); +} + +void ControllerScriptingInterface::setVPadExtraBottomMargin(const int margin) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + virtualPadManager.setExtraBottomMargin(margin); +} + void ControllerScriptingInterface::emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); } void ControllerScriptingInterface::emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); } diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 1f223814a3..9b24e2a06e 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -66,6 +66,8 @@ public slots: virtual QVariant getRecommendedHUDRect() const; virtual void setVPadEnabled(bool enable); + virtual void setVPadHidden(bool hidden); // Call it when a window should hide it + virtual void setVPadExtraBottomMargin(int margin); signals: void keyPressEvent(const KeyEvent& event); diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 233e61c8ae..5f762ab511 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -110,7 +110,7 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c } if (!(*highlightStyle).isBoundToList()) { - setupHandler(listName); + enableListToScene(listName); (*highlightStyle).setBoundToList(true); } @@ -134,6 +134,7 @@ bool SelectionScriptingInterface::disableListHighlight(const QString& listName) auto highlightStyle = _highlightStyleMap.find(listName); if (highlightStyle != _highlightStyleMap.end()) { if ((*highlightStyle).isBoundToList()) { + disableListToScene(listName); } _highlightStyleMap.erase(highlightStyle); @@ -172,6 +173,18 @@ render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QStr } } +bool SelectionScriptingInterface::enableListToScene(const QString& listName) { + setupHandler(listName); + + return true; +} + +bool SelectionScriptingInterface::disableListToScene(const QString& listName) { + removeHandler(listName); + + return true; +} + template bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) { { QWriteLocker lock(&_selectionListsLock); @@ -303,6 +316,15 @@ void SelectionScriptingInterface::setupHandler(const QString& selectionName) { (*handler)->initialize(selectionName); } +void SelectionScriptingInterface::removeHandler(const QString& selectionName) { + QWriteLocker lock(&_selectionHandlersLock); + auto handler = _handlerMap.find(selectionName); + if (handler != _handlerMap.end()) { + delete handler.value(); + _handlerMap.erase(handler); + } +} + void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) { { QWriteLocker lock(&_selectionHandlersLock); @@ -456,4 +478,4 @@ QVariantMap SelectionHighlightStyle::toVariantMap() const { properties["isOutlineSmooth"] = _style._isOutlineSmooth; return properties; -} \ No newline at end of file +} diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 8295375870..ed6efb39c6 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -160,13 +160,16 @@ public: * If the Selection doesn't exist, it will be created. * All objects in the list will be displayed with the highlight effect as specified from the highlightStyle. * The function can be called several times with different values in the style to modify it. - * + * * @function Selection.enableListHighlight * @param listName {string} name of the selection * @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle). * @returns {bool} true if the selection was successfully enabled for highlight. + * + * Note: This function will implicitly call Selection.enableListToScene */ Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); + /**jsdoc * Disable highlighting for the named selection. * If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false. @@ -174,8 +177,30 @@ public: * @function Selection.disableListHighlight * @param listName {string} name of the selection * @returns {bool} true if the selection was successfully disabled for highlight, false otherwise. + * + * Note: This function will implicitly call Selection.disableListToScene */ Q_INVOKABLE bool disableListHighlight(const QString& listName); + /**jsdoc + * Enable scene selection for the named selection. + * If the Selection doesn't exist, it will be created. + * All objects in the list will be sent to a scene selection. + * + * @function Selection.enableListToScene + * @param listName {string} name of the selection + * @returns {bool} true if the selection was successfully enabled on the scene. + */ + Q_INVOKABLE bool enableListToScene(const QString& listName); + /**jsdoc + * Disable scene selection for the named selection. + * If the Selection doesn't exist or wasn't enabled on the scene then nothing happens simply returning false. + * + * @function Selection.disableListToScene + * @param listName {string} name of the selection + * @returns {bool} true if the selection was successfully disabled on the scene, false otherwise. + */ + Q_INVOKABLE bool disableListToScene(const QString& listName); + /**jsdoc * Query the highlight style values for the named selection. * If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object. @@ -223,9 +248,9 @@ private: template bool removeFromGameplayObjects(const QString& listName, T idToRemove); void setupHandler(const QString& selectionName); + void removeHandler(const QString& selectionName); - }; #endif // hifi_SelectionScriptingInterface_h diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index 2fe55eaea0..9e0271601b 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -15,6 +15,11 @@ #include #include +/**jsdoc + * The Settings API provides a facility to store and retrieve values that persist between Interface runs. + * @namespace Settings + */ + class SettingsScriptingInterface : public QObject { Q_OBJECT SettingsScriptingInterface() { }; @@ -22,8 +27,37 @@ public: static SettingsScriptingInterface* getInstance(); public slots: + + /**jsdoc + * Retrieve the value from a named setting. + * @function Settings.getValue + * @param {string} key - The name of the setting. + * @param {string|number|boolean|object} [defaultValue=""] - The value to return if the setting doesn't exist. + * @returns {string|number|boolean|object} The value stored in the named setting if it exists, otherwise the + * defaultValue. + * @example Retrieve non-existent setting values. + * var value1 = Settings.getValue("Script Example/Nonexistent Key"); + * print("Value: " + (typeof value1) + " " + JSON.stringify(value1)); // string "" + * + * var value2 = Settings.getValue("Script Example/Nonexistent Key", true); + * print("Value: " + (typeof value2) + " " + JSON.stringify(value2)); // boolean true + */ QVariant getValue(const QString& setting); QVariant getValue(const QString& setting, const QVariant& defaultValue); + + /**jsdoc + * Store a value in a named setting. If the setting already exists its value is overwritten, otherwise a new setting is + * created. If the value is set to null or undefined, the setting is deleted. + * @function Settings.setValue + * @param {string} key - The name of the setting. Be sure to use a unique name if creating a new setting. + * @param {string|number|boolean|object|undefined} value - The value to store in the setting. If null or + * undefined is specified, the setting is deleted. + * @example Store and retrieve an object value. + * Settings.setValue("Script Example/My Key", { x: 0, y: 10, z: 0 }); + * + * var value = Settings.getValue("Script Example/My Key"); + * print("Value: " + (typeof value) + " " + JSON.stringify(value)); // object {"x":0,"y":10,"z":0} + */ void setValue(const QString& setting, const QVariant& value); }; diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index 74a15db0ce..9e7c0e142e 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -156,3 +156,7 @@ void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) fn.call(); } +void TestScriptingInterface::clearCaches() { + qApp->reloadResourceCaches(); +} + diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index aca07d110b..687cb41689 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -78,6 +78,11 @@ public slots: Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); + /**jsdoc + * Clear all caches (menu command Reload Content) + */ + void clearCaches(); + private: bool waitForCondition(qint64 maxWaitMs, std::function condition); }; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 12b20566ed..9c46f9e98a 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -51,7 +51,7 @@ WindowScriptingInterface::WindowScriptingInterface() { } }); - connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::geometryChanged); + connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::onWindowGeometryChanged); } WindowScriptingInterface::~WindowScriptingInterface() { @@ -74,17 +74,19 @@ QScriptValue WindowScriptingInterface::hasFocus() { void WindowScriptingInterface::setFocus() { // It's forbidden to call focus() from another thread. qApp->postLambdaEvent([] { - auto window = qApp->getWindow(); - window->activateWindow(); - window->setFocus(); + qApp->setFocus(); + }); +} + +void WindowScriptingInterface::raise() { + // It's forbidden to call raise() from another thread. + qApp->postLambdaEvent([] { + qApp->raise(); }); } void WindowScriptingInterface::raiseMainWindow() { - // It's forbidden to call raise() from another thread. - qApp->postLambdaEvent([] { - qApp->getWindow()->raise(); - }); + raise(); } /// Display an alert box @@ -126,7 +128,7 @@ void WindowScriptingInterface::promptAsync(const QString& message, const QString } void WindowScriptingInterface::disconnectedFromDomain() { - emit domainChanged(""); + emit domainChanged(QUrl()); } QString fixupPathForMac(const QString& directory) { @@ -259,7 +261,6 @@ void WindowScriptingInterface::browseAsync(const QString& title, const QString& setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } emit browseChanged(result); - emit openFileChanged(result); // Deprecated signal; to be removed in due course. }); } @@ -389,11 +390,22 @@ glm::vec2 WindowScriptingInterface::getDeviceSize() const { } int WindowScriptingInterface::getX() { - return qApp->getWindow()->x(); + return qApp->getWindow()->geometry().x(); } int WindowScriptingInterface::getY() { - return qApp->getWindow()->y(); + auto menu = qApp->getPrimaryMenu(); + int menuHeight = menu ? menu->geometry().height() : 0; + return qApp->getWindow()->geometry().y() + menuHeight; +} + +void WindowScriptingInterface::onWindowGeometryChanged(const QRect& windowGeometry) { + auto geometry = windowGeometry; + auto menu = qApp->getPrimaryMenu(); + if (menu) { + geometry.setY(geometry.y() + menu->geometry().height()); + } + emit geometryChanged(geometry); } void WindowScriptingInterface::copyToClipboard(const QString& text) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 67fdea0bc5..dd74358064 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -33,8 +33,10 @@ * @property {number} innerHeight - The height of the drawable area of the Interface window (i.e., without borders or other * chrome), in pixels. Read-only. * @property {object} location - Provides facilities for working with your current metaverse location. See {@link location}. - * @property {number} x - The x coordinate of the top left corner of the Interface window on the display. Read-only. - * @property {number} y - The y coordinate of the top left corner of the Interface window on the display. Read-only. + * @property {number} x - The x display coordinate of the top left corner of the drawable area of the Interface window. + * Read-only. + * @property {number} y - The y display coordinate of the top left corner of the drawable area of the Interface window. + * Read-only. */ class WindowScriptingInterface : public QObject, public Dependency { @@ -62,14 +64,22 @@ public slots: QScriptValue hasFocus(); /**jsdoc - * Make the Interface window have focus. + * Make the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to + * indicate that Interface wants attention but focus isn't taken away from the application that the user is using. * @function Window.setFocus */ void setFocus(); /**jsdoc - * Raise the Interface window if it is minimized, and give it focus. + * Raise the Interface window if it is minimized. If raised, the window gains focus. + * @function Window.raise + */ + void raise(); + + /**jsdoc + * Raise the Interface window if it is minimized. If raised, the window gains focus. * @function Window.raiseMainWindow + * @deprecated Use {@link Window.raise|raise} instead. */ void raiseMainWindow(); @@ -179,7 +189,6 @@ public slots: * Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A * {@link Window.browseChanged|browseChanged} signal is emitted when a file is chosen; no signal is emitted if the user * cancels the dialog. - * @deprecated A deprecated {@link Window.openFileChanged|openFileChanged} signal is also emitted when a file is chosen. * @function Window.browseAsync * @param {string} title="" - The title to display at the top of the dialog. * @param {string} directory="" - The initial directory to start browsing at. @@ -515,6 +524,7 @@ public slots: void closeMessageBox(int id); private slots: + void onWindowGeometryChanged(const QRect& geometry); void onMessageBoxSelected(int button); void disconnectedFromDomain(); @@ -524,7 +534,7 @@ signals: * Triggered when you change the domain you're visiting. Warning: Is not emitted if you go to domain that * isn't running. * @function Window.domainChanged - * @param {string} domain - The domain's IP address. + * @param {string} domainURL - The domain's URL. * @returns {Signal} * @example Report when you change domains. * function onDomainChanged(domain) { @@ -533,7 +543,7 @@ signals: * * Window.domainChanged.connect(onDomainChanged); */ - void domainChanged(const QString& domain); + void domainChanged(QUrl domainURL); /**jsdoc * Triggered when you try to navigate to a *.json, *.svo, or *.svo.json URL in a Web browser within Interface. @@ -660,15 +670,6 @@ signals: */ void browseChanged(QString filename); - /**jsdoc - * Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog. - * @function Window.openFileChanged - * @deprecated This signal is being replaced with {@link Window.browseChanged|browseChanged} and will be removed. - * @param {string} filename - The path and name of the file the user chose in the dialog. - * @returns {Signal} - */ - void openFileChanged(QString filename); - /**jsdoc * Triggered when the user OKs a {@link Window.promptAsync|promptAsync} dialog. * @function Window.promptTextChanged diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 1a23674fa3..87bf09a252 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -45,7 +45,6 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare connect(&domainHandler, &DomainHandler::connectedToDomain, this, &AddressBarDialog::hostChanged); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &AddressBarDialog::hostChanged); connect(DependencyManager::get().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed); - connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL); } void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) { diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index 7806436774..1254fabbd2 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -36,7 +36,6 @@ signals: void backEnabledChanged(); void forwardEnabledChanged(); void useFeedChanged(); - void receivedHifiSchemeURL(const QString& url); void hostChanged(); protected: diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 7cce12c8e0..9a7ebdf784 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -64,7 +64,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { } // Execute the batch into our framebuffer - doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { + doInBatch("ApplicationOverlay::render", renderArgs->_context, [&](gpu::Batch& batch) { PROFILE_RANGE_BATCH(batch, "ApplicationOverlayRender"); renderArgs->_batch = &batch; batch.enableStereo(false); diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index fe79bec6ef..3053cb8855 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -16,22 +16,19 @@ #include "Application.h" #include "Menu.h" -HIFI_QML_DEF(AvatarInputs) - static AvatarInputs* INSTANCE{ nullptr }; Setting::Handle showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, false }; AvatarInputs* AvatarInputs::getInstance() { if (!INSTANCE) { - AvatarInputs::registerType(); + INSTANCE = new AvatarInputs(); Q_ASSERT(INSTANCE); } return INSTANCE; } -AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) { - INSTANCE = this; +AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); } diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index fb48df9d73..47f520a639 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -19,7 +19,7 @@ public: \ private: \ type _##name{ initialValue }; -class AvatarInputs : public QQuickItem { +class AvatarInputs : public QObject { Q_OBJECT HIFI_QML_DECL @@ -32,7 +32,7 @@ class AvatarInputs : public QQuickItem { public: static AvatarInputs* getInstance(); Q_INVOKABLE float loudnessToAudioLevel(float loudness); - AvatarInputs(QQuickItem* parent = nullptr); + AvatarInputs(QObject* parent = nullptr); void update(); bool showAudioTools() const { return _showAudioTools; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index b309ecd5e3..289166648e 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -24,10 +24,6 @@ #include "SnapshotAnimated.h" #include "UserActivityLogger.h" -#include "AmbientOcclusionEffect.h" -#include "AntialiasingEffect.h" -#include "RenderShadowTask.h" - void setupPreferences() { auto preferences = DependencyManager::get(); auto nodeList = DependencyManager::get(); @@ -284,30 +280,6 @@ void setupPreferences() { } #endif - - { - static const QString GRAPHICS_QUALITY("Graphics Quality"); - auto renderConfig = qApp->getRenderEngine()->getConfiguration(); - if (renderConfig) { - auto mainViewAmbientOcclusionConfig = renderConfig->getConfig("RenderMainView.AmbientOcclusion"); - if (mainViewAmbientOcclusionConfig) { - auto getter = [mainViewAmbientOcclusionConfig]()->QString { return mainViewAmbientOcclusionConfig->getPreset(); }; - auto setter = [mainViewAmbientOcclusionConfig](QString preset) { mainViewAmbientOcclusionConfig->setPreset(preset); }; - auto preference = new ComboBoxPreference(GRAPHICS_QUALITY, "Ambient occlusion", getter, setter); - preference->setItems(mainViewAmbientOcclusionConfig->getPresetList()); - preferences->addPreference(preference); - } - - auto mainViewShadowConfig = renderConfig->getConfig("RenderMainView.RenderShadowTask"); - if (mainViewShadowConfig) { - auto getter = [mainViewShadowConfig]()->QString { return mainViewShadowConfig->getPreset(); }; - auto setter = [mainViewShadowConfig](QString preset) { mainViewShadowConfig->setPreset(preset); }; - auto preference = new ComboBoxPreference(GRAPHICS_QUALITY, "Shadows", getter, setter); - preference->setItems(mainViewShadowConfig->getPresetList()); - preferences->addPreference(preference); - } - } - } { static const QString NETWORKING("Networking"); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 9b3089d78d..69103a40b5 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -95,7 +95,7 @@ QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) { QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QString& userSelectedFilename) { // adding URL to snapshot - QUrl currentURL = DependencyManager::get()->currentShareableAddress(); + QUrl currentURL = DependencyManager::get()->currentPublicAddress(); shot.setText(URL, currentURL.toString()); QString username = DependencyManager::get()->getAccountInfo().getUsername(); diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 3408cb8512..37505db629 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -65,7 +65,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { } else { emit DependencyManager::get()->snapshotShared(true, contents); - delete this; + this->deleteLater(); } } @@ -75,23 +75,27 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { if (replyString.size() == 0) { replyString = reply.errorString(); } + replyString = replyString.left(1000); // Only print first 1000 characters of error + qDebug() << "Snapshot upload reply error (truncated):" << replyString; emit DependencyManager::get()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename? - delete this; + this->deleteLater(); } void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { QString replyString = reply.readAll(); emit DependencyManager::get()->snapshotShared(false, replyString); - delete this; + this->deleteLater(); } void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { QString replyString = reply.readAll(); - qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation; + qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation; if (replyString.size() == 0) { replyString = reply.errorString(); } + replyString = replyString.left(1000); // Only print first 1000 characters of error + qDebug() << "Snapshot story upload reply error (truncated):" << replyString; emit DependencyManager::get()->snapshotShared(true, replyString); - delete this; + this->deleteLater(); } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index ff42ed09e7..64ee492716 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -46,10 +46,7 @@ static Stats* INSTANCE{ nullptr }; QString getTextureMemoryPressureModeString(); #endif Stats* Stats::getInstance() { - if (!INSTANCE) { - Stats::registerType(); - Q_ASSERT(INSTANCE); - } + Q_ASSERT(INSTANCE); return INSTANCE; } @@ -482,7 +479,7 @@ void Stats::updateStats(bool force) { float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC; _gameUpdateStats = QString("/idle/update = %1 ms").arg(dt); - QVector categories = { "devices", "physics", "otherAvatars", "MyAvatar", "pickManager", "postUpdateLambdas", "misc" }; + QVector categories = { "devices", "physics", "otherAvatars", "MyAvatar", "misc" }; for (int32_t j = 0; j < categories.size(); ++j) { QString recordKey = "/idle/update/" + categories[j]; itr = allRecords.find(recordKey); @@ -502,39 +499,10 @@ void Stats::updateStats(bool force) { _gameUpdateStats = ""; emit gameUpdateStatsChanged(); } - - itr = allRecords.find("/paintGL/display/EngineRun/Engine"); - std::priority_queue renderEngineStats; - if (itr != allRecords.end()) { - float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC; - _renderEngineStats = QString("/render = %1 ms").arg(dt); - - QVector categories = { "RenderMainView", "SecondaryCameraJob", "UpdateScene"}; - for (int32_t j = 0; j < categories.size(); ++j) { - QString recordKey = "/paintGL/display/EngineRun/Engine/" + categories[j]; - itr = allRecords.find(recordKey); - if (itr != allRecords.end()) { - float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC; - QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt); - renderEngineStats.push(SortableStat(message, dt)); - } - } - while (!renderEngineStats.empty()) { - SortableStat stat = renderEngineStats.top(); - _renderEngineStats += stat.message; - renderEngineStats.pop(); - } - emit renderEngineStatsChanged(); - } else if (_renderEngineStats != "") { - _renderEngineStats = ""; - emit renderEngineStatsChanged(); - } } else if (_showGameUpdateStats) { _showGameUpdateStats = false; _gameUpdateStats = ""; - _renderEngineStats = ""; emit gameUpdateStatsChanged(); - emit renderEngineStatsChanged(); } } diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index e5fc0beaf6..af3189f20b 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -106,7 +106,6 @@ class Stats : public QQuickItem { STATS_PROPERTY(QString, lodStatus, QString()) STATS_PROPERTY(QString, timingStats, QString()) STATS_PROPERTY(QString, gameUpdateStats, QString()) - STATS_PROPERTY(QString, renderEngineStats, QString()) STATS_PROPERTY(int, serverElements, 0) STATS_PROPERTY(int, serverInternal, 0) STATS_PROPERTY(int, serverLeaves, 0) @@ -240,7 +239,6 @@ signals: void localLeavesChanged(); void timingStatsChanged(); void gameUpdateStatsChanged(); - void renderEngineStatsChanged(); void glContextSwapchainMemoryChanged(); void qmlTextureMemoryChanged(); void texturePendingTransfersChanged(); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index ff5a202910..f4efd1301d 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -37,7 +37,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _drawInFront(base3DOverlay->_drawInFront), _drawHUDLayer(base3DOverlay->_drawHUDLayer), - _isGrabbable(base3DOverlay->_isGrabbable) + _isGrabbable(base3DOverlay->_isGrabbable), + _isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera) { setTransform(base3DOverlay->getTransform()); } @@ -142,6 +143,13 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { setIsGrabbable(isGrabbable.toBool()); } + auto isVisibleInSecondaryCamera = properties["isVisibleInSecondaryCamera"]; + if (isVisibleInSecondaryCamera.isValid()) { + bool value = isVisibleInSecondaryCamera.toBool(); + setIsVisibleInSecondaryCamera(value); + needRenderItemUpdate = true; + } + if (properties["position"].isValid()) { setLocalPosition(vec3FromVariant(properties["position"])); needRenderItemUpdate = true; @@ -221,6 +229,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {boolean} isVisibleInSecondaryCamera=false - If true, the overlay is rendered in secondary + * camera views. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". @@ -259,6 +269,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "grabbable") { return _isGrabbable; } + if (property == "isVisibleInSecondaryCamera") { + return _isVisibleInSecondaryCamera; + } if (property == "parentID") { return getParentID(); } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index bbf064fddd..ab83a64273 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -48,6 +48,7 @@ public: bool getDrawInFront() const { return _drawInFront; } bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } + virtual bool getIsVisibleInSecondaryCamera() const override { return _isVisibleInSecondaryCamera; } void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } @@ -55,6 +56,7 @@ public: virtual void setDrawInFront(bool value) { _drawInFront = value; } virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } + virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } virtual AABox getBounds() const override = 0; @@ -92,6 +94,7 @@ protected: bool _drawInFront; bool _drawHUDLayer; bool _isGrabbable { false }; + bool _isVisibleInSecondaryCamera { false }; mutable bool _renderVariableDirty { true }; QString _name; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index dd05e5c6a8..aca186a589 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -50,7 +50,9 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_OWNING_AVATAR_ID; auto entityScriptingInterface = DependencyManager::get().data(); - connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay); + connect(entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity, this, &ContextOverlayInterface::clickDownOnEntity); + connect(entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity, this, &ContextOverlayInterface::holdingClickOnEntity); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &ContextOverlayInterface::mouseReleaseOnEntity); connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, &ContextOverlayInterface::contextOverlays_hoverEnterEntity); connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &ContextOverlayInterface::contextOverlays_hoverLeaveEntity); connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() { @@ -97,6 +99,31 @@ void ContextOverlayInterface::setEnabled(bool enabled) { _enabled = enabled; } +void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { + if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID)) { + _mouseDownEntity = entityItemID; + _mouseDownEntityTimestamp = usecTimestampNow(); + } else { + if (!_currentEntityWithContextOverlay.isNull()) { + disableEntityHighlight(_currentEntityWithContextOverlay); + destroyContextOverlay(_currentEntityWithContextOverlay, event); + } + } +} + +static const float CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC = 400.0f; +void ContextOverlayInterface::holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { + if (!_mouseDownEntity.isNull() && ((usecTimestampNow() - _mouseDownEntityTimestamp) > (CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC * USECS_PER_MSEC))) { + _mouseDownEntity = EntityItemID(); + } +} + +void ContextOverlayInterface::mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { + if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID) && _mouseDownEntity == entityItemID) { + createOrDestroyContextOverlay(entityItemID, event); + } +} + bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { if (_enabled && event.getButton() == PointerEvent::SecondaryButton) { if (contextOverlayFilterPassed(entityItemID)) { diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index fcdf2d5820..b80a3a70fb 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -64,6 +64,10 @@ signals: void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); public slots: + void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID); @@ -84,6 +88,8 @@ private: }; bool _verboseLogging{ true }; bool _enabled { true }; + EntityItemID _mouseDownEntity{}; + quint64 _mouseDownEntityTimestamp; EntityItemID _currentEntityWithContextOverlay{}; EntityItemID _lastInspectedEntity{}; QString _entityMarketplaceID; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 6603a44d46..7edc03490c 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -35,7 +35,19 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : _url(modelOverlay->_url), _updateModel(false), _scaleToFit(modelOverlay->_scaleToFit), - _loadPriority(modelOverlay->_loadPriority) + _loadPriority(modelOverlay->_loadPriority), + + _animationURL(modelOverlay->_animationURL), + _animationFPS(modelOverlay->_animationFPS), + _animationCurrentFrame(modelOverlay->_animationCurrentFrame), + _animationRunning(modelOverlay->_animationRunning), + _animationLoop(modelOverlay->_animationLoop), + _animationFirstFrame(modelOverlay->_animationFirstFrame), + _animationLastFrame(modelOverlay->_animationLastFrame), + _animationHold(modelOverlay->_animationHold), + _animationAllowTranslation(modelOverlay->_animationAllowTranslation) + + // Joint translations and rotations aren't copied because the model needs to load before they can be applied. { _model->setLoadingPriority(_loadPriority); if (_url.isValid()) { @@ -89,8 +101,11 @@ void ModelOverlay::update(float deltatime) { } if (_visibleDirty) { _visibleDirty = false; - // don't show overlays in mirrors - _model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false); + // don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true + _model->setVisibleInScene(getVisible(), scene, + render::ItemKey::TAG_BITS_0 | + (_isVisibleInSecondaryCamera ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE), + false); } if (_drawInFrontDirty) { _drawInFrontDirty = false; @@ -338,20 +353,25 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {string} url - The URL of the FBX or OBJ model used for the overlay. + * @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load + * first. * @property {Vec3} dimensions - The dimensions of the overlay. Synonym: size. * @property {Vec3} scale - The scale factor applied to the model's dimensions. * @property {object.} textures - Maps the named textures in the model to the JPG or PNG images in the urls. - * @property {Array.} jointNames - The names of the joints - if any - in the model. Read-only - * @property {Array.} jointRotations - The relative rotations of the model's joints. - * @property {Array.} jointTranslations - The relative translations of the model's joints. + * @property {Array.} jointNames - The names of the joints - if any - in the model. Read-only. + * @property {Array.} jointRotations - The relative rotations of the model's joints. Not copied if overlay is + * cloned. + * @property {Array.} jointTranslations - The relative translations of the model's joints. Not copied if overlay is + * cloned. * @property {Array.} jointOrientations - The absolute orientations of the model's joints, in world coordinates. - * Read-only + * Read-only. * @property {Array.} jointPositions - The absolute positions of the model's joints, in world coordinates. - * Read-only + * Read-only. * @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play. * @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at. * @property {number} animationSettings.firstFrame=0 - The frame to start playing at. * @property {number} animationSettings.lastFrame=0 - The frame to finish playing at. + * @property {number} animationSettings.currentFrame=0 - The current frame being played. * @property {boolean} animationSettings.running=false - Whether or not the animation is playing. * @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop. * @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and @@ -381,6 +401,10 @@ QVariant ModelOverlay::getProperty(const QString& property) { } } + if (property == "loadPriority") { + return _loadPriority; + } + if (property == "jointNames") { if (_model && _model->isActive()) { // note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty @@ -681,5 +705,9 @@ scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() { } auto result = _model->getScriptableModel(); result.objectID = getID(); + { + std::lock_guard lock(_materialsLock); + result.appendMaterials(_materials); + } return result; } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 88a1729d68..3ef3f23fec 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -36,6 +36,11 @@ public: void clearSubRenderItemIDs(); void setSubRenderItemIDs(const render::ItemIDs& ids); + virtual void setIsVisibleInSecondaryCamera(bool value) override { + Base3DOverlay::setIsVisibleInSecondaryCamera(value); + _visibleDirty = true; + } + void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index f1be23ed39..2f27d50f7e 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -56,6 +56,8 @@ public: bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; + virtual bool getIsVisibleInSecondaryCamera() const { return false; } + xColor getColor(); float getAlpha(); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 35274e4fbe..6556cedb21 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -54,6 +54,7 @@ Overlays::Overlays() { } void Overlays::cleanupAllOverlays() { + _shuttingDown = true; QMap overlaysHUD; QMap overlaysWorld; { @@ -147,6 +148,10 @@ void Overlays::enable() { // Note, can't be invoked by scripts, but can be called by the InterfaceParentFinder // class on packet processing threads Overlay::Pointer Overlays::getOverlay(OverlayID id) const { + if (_shuttingDown) { + return nullptr; + } + QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { return _overlaysHUD[id]; @@ -157,6 +162,10 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const { } OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties) { + if (_shuttingDown) { + return UNKNOWN_OVERLAY_ID; + } + if (QThread::currentThread() != thread()) { OverlayID result; PROFILE_RANGE(script, __FUNCTION__); @@ -261,6 +270,10 @@ OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties) } OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { + if (_shuttingDown) { + return UNKNOWN_OVERLAY_ID; + } + OverlayID thisID = OverlayID(QUuid::createUuid()); overlay->setOverlayID(thisID); overlay->setStackOrder(_stackOrder++); @@ -283,6 +296,10 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { } OverlayID Overlays::cloneOverlay(OverlayID id) { + if (_shuttingDown) { + return UNKNOWN_OVERLAY_ID; + } + if (QThread::currentThread() != thread()) { OverlayID result; PROFILE_RANGE(script, __FUNCTION__); @@ -301,6 +318,10 @@ OverlayID Overlays::cloneOverlay(OverlayID id) { } bool Overlays::editOverlay(OverlayID id, const QVariant& properties) { + if (_shuttingDown) { + return false; + } + auto thisOverlay = getOverlay(id); if (!thisOverlay) { return false; @@ -320,6 +341,10 @@ bool Overlays::editOverlay(OverlayID id, const QVariant& properties) { } bool Overlays::editOverlays(const QVariant& propertiesById) { + if (_shuttingDown) { + return false; + } + bool defer2DOverlays = QThread::currentThread() != thread(); QVariantMap deferrred; @@ -351,6 +376,10 @@ bool Overlays::editOverlays(const QVariant& propertiesById) { } void Overlays::deleteOverlay(OverlayID id) { + if (_shuttingDown) { + return; + } + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "deleteOverlay", Q_ARG(OverlayID, id)); return; @@ -374,6 +403,9 @@ void Overlays::deleteOverlay(OverlayID id) { } QString Overlays::getOverlayType(OverlayID overlayId) { + if (_shuttingDown) { + return ""; + } if (QThread::currentThread() != thread()) { QString result; PROFILE_RANGE(script, __FUNCTION__); @@ -388,8 +420,23 @@ QString Overlays::getOverlayType(OverlayID overlayId) { return ""; } +QObject* Overlays::getOverlayObject(OverlayID id) { + if (QThread::currentThread() != thread()) { + QObject* result; + PROFILE_RANGE(script, __FUNCTION__); + BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id)); + return result; + } + + Overlay::Pointer thisOverlay = getOverlay(id); + if (thisOverlay) { + return qobject_cast(&(*thisOverlay)); + } + return nullptr; +} + OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) { - if (!_enabled) { + if (_shuttingDown || !_enabled) { return UNKNOWN_OVERLAY_ID; } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 3efe94c206..c2f6e3e693 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -44,6 +44,8 @@ void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPro const OverlayID UNKNOWN_OVERLAY_ID = OverlayID(); /**jsdoc + * The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection} or + * {@link Overlays.findRayIntersectionVector|findRayIntersectionVector}. * @typedef {object} Overlays.RayToOverlayIntersectionResult * @property {boolean} intersects - true if the {@link PickRay} intersected with a 3D overlay, otherwise * false. @@ -75,7 +77,8 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R * yourself and that aren't persisted to the domain. They are used for UI. * @namespace Overlays * @property {Uuid} keyboardFocusOverlay - Get or set the {@link Overlays.OverlayType|web3d} overlay that has keyboard focus. - * If no overlay is set, get returns null; set to null to clear keyboard focus. + * If no overlay has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to + * clear keyboard focus. */ class Overlays : public QObject { @@ -116,7 +119,7 @@ public slots: * @function Overlays.addOverlay * @param {Overlays.OverlayType} type - The type of the overlay to add. * @param {Overlays.OverlayProperties} properties - The properties of the overlay to add. - * @returns {Uuid} The ID of the newly created overlay. + * @returns {Uuid} The ID of the newly created overlay if successful, otherwise {@link Uuid|Uuid.NULL}. * @example Add a cube overlay in front of your avatar. * var overlay = Overlays.addOverlay("cube", { * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), @@ -131,7 +134,7 @@ public slots: * Create a clone of an existing overlay. * @function Overlays.cloneOverlay * @param {Uuid} overlayID - The ID of the overlay to clone. - * @returns {Uuid} The ID of the new overlay. + * @returns {Uuid} The ID of the new overlay if successful, otherwise {@link Uuid|Uuid.NULL}. * @example Add an overlay in front of your avatar, clone it, and move the clone to be above the * original. * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })); @@ -232,6 +235,50 @@ public slots: */ QString getOverlayType(OverlayID overlayId); + /**jsdoc + * Get the overlay script object. In particular, this is useful for accessing the event bridge for a web3d + * overlay. + * @function Overlays.getOverlayObject + * @param {Uuid} overlayID - The ID of the overlay to get the script object of. + * @returns {object} The script object for the overlay if found. + * @example Receive "hello" messages from a web3d overlay. + * // HTML file: name "web3d.html". + * + * + * + * HELLO + * + * + *

HELLO

+ * + * + * + * + * // Script file. + * var web3dOverlay = Overlays.addOverlay("web3d", { + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.5, z: -3 })), + * rotation: MyAvatar.orientation, + * url: Script.resolvePath("web3d.html"), + * alpha: 1.0 + * }); + * + * function onWebEventReceived(event) { + * print("onWebEventReceived() : " + JSON.stringify(event)); + * } + * + * overlayObject = Overlays.getOverlayObject(web3dOverlay); + * overlayObject.webEventReceived.connect(onWebEventReceived); + * + * Script.scriptEnding.connect(function () { + * Overlays.deleteOverlay(web3dOverlay); + * }); + */ + QObject* getOverlayObject(OverlayID id); + /**jsdoc * Get the ID of the 2D overlay at a particular point on the screen or HUD. * @function Overlays.getOverlayAtPoint @@ -322,10 +369,8 @@ public slots: * @function Overlays.findRayIntersection * @param {PickRay} pickRay - The PickRay to use for finding overlays. * @param {boolean} [precisionPicking=false] - Unused; exists to match Entity API. - * @param {Array.} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited - * to overlays in the list. - * @param {Array.} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't - * exclude overlays in the list. + * @param {Array.} [overlayIDsToInclude=[]] - If not empty then the search is restricted to these overlays. + * @param {Array.} [overlayIDsToExclude=[]] - Overlays to ignore during the search. * @param {boolean} [visibleOnly=false] - Unused; exists to match Entity API. * @param {boolean} [collidableOnly=false] - Unused; exists to match Entity API. * @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by pickRay, taking @@ -531,7 +576,7 @@ public slots: * Set the Web3D overlay that has keyboard focus. * @function Overlays.setKeyboardFocusOverlay * @param {Uuid} overlayID - The ID of the {@link Overlays.OverlayType|web3d} overlay to set keyboard focus to. Use - * {@link Uuid|Uuid.NULL} or null to unset keyboard focus from an overlay. + * null or {@link Uuid|Uuid.NULL} to unset keyboard focus from an overlay. */ void setKeyboardFocusOverlay(const OverlayID& id); @@ -679,6 +724,7 @@ private: unsigned int _stackOrder { 1 }; bool _enabled = true; + std::atomic _shuttingDown{ false }; PointerEvent calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray, RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType); diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index f99ced0021..185547a333 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -49,7 +49,11 @@ namespace render { builder.withInvisible(); } - builder.withTagBits(render::ItemKey::TAG_BITS_0); // Only draw overlays in main view + // always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view + uint32_t viewTaskBits = render::ItemKey::TAG_BITS_0 | + (overlay->getIsVisibleInSecondaryCamera() ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE); + + builder.withTagBits(viewTaskBits); return builder.build(); } diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index f4a9034187..2a583e0450 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -39,18 +39,20 @@ void QmlOverlay::buildQmlElement(const QUrl& url) { auto offscreenUi = DependencyManager::get(); offscreenUi->load(url, [=](QQmlContext* context, QObject* object) { - QQuickItem* rawPtr = dynamic_cast(object); - // Create a shared ptr with a custom deleter lambda, that calls deleteLater - _qmlElement = std::shared_ptr(rawPtr, [](QQuickItem* ptr) { - if (ptr) { - ptr->deleteLater(); - } - }); + _qmlElement = dynamic_cast(object); + connect(_qmlElement, &QObject::destroyed, this, &QmlOverlay::qmlElementDestroyed); }); } +void QmlOverlay::qmlElementDestroyed() { + _qmlElement = nullptr; +} + QmlOverlay::~QmlOverlay() { - _qmlElement.reset(); + if (_qmlElement) { + _qmlElement->deleteLater(); + } + _qmlElement = nullptr; } // QmlOverlay replaces Overlay's properties with those defined in the QML file used but keeps Overlay2D's properties. @@ -62,15 +64,13 @@ void QmlOverlay::setProperties(const QVariantMap& properties) { Overlay2D::setProperties(properties); auto bounds = _bounds; - std::weak_ptr weakQmlElement = _qmlElement; // check to see if qmlElement still exists - auto qmlElement = weakQmlElement.lock(); - if (qmlElement) { - qmlElement->setX(bounds.left()); - qmlElement->setY(bounds.top()); - qmlElement->setWidth(bounds.width()); - qmlElement->setHeight(bounds.height()); - QMetaObject::invokeMethod(qmlElement.get(), "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties)); + if (_qmlElement) { + _qmlElement->setX(bounds.left()); + _qmlElement->setY(bounds.top()); + _qmlElement->setWidth(bounds.width()); + _qmlElement->setHeight(bounds.height()); + QMetaObject::invokeMethod(_qmlElement, "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties)); } } diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h index ced2b6fa1f..7f2cf5a918 100644 --- a/interface/src/ui/overlays/QmlOverlay.h +++ b/interface/src/ui/overlays/QmlOverlay.h @@ -32,10 +32,11 @@ public: void render(RenderArgs* args) override; private: + Q_INVOKABLE void qmlElementDestroyed(); Q_INVOKABLE void buildQmlElement(const QUrl& url); protected: - std::shared_ptr _qmlElement; + QQuickItem* _qmlElement{ nullptr }; }; #endif // hifi_QmlOverlay_h diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index e9ec0d6cf4..c27faf6f0f 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -67,6 +67,32 @@ Shape3DOverlay* Shape3DOverlay::createClone() const { } +/**jsdoc + *

A shape {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical shapes:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDimensionsDescription
"Circle"2DA circle oriented in 3D.
"Cone"3D
"Cube"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Line"1DA line oriented in 3D.
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
+ * @typedef {string} Overlays.Shape + */ static const std::array shapeStrings { { "Line", "Triangle", @@ -80,7 +106,7 @@ static const std::array shapeStrings "Octahedron", "Dodecahedron", "Icosahedron", - "Torus", + "Torus", // Not implemented yet. "Cone", "Cylinder" } }; @@ -145,7 +171,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * - * @property {Shape} shape=Hexagon - The geometrical shape of the overlay. + * @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay. */ QVariant Shape3DOverlay::getProperty(const QString& property) { if (property == "shape") { diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index bed20be698..9c920efb93 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -20,7 +20,8 @@ #include const int FIXED_FONT_POINT_SIZE = 40; -const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 80.0f; // this is a ratio determined through experimentation +const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 92.0f; // Determined through experimentation to fit font to line + // height. const float LINE_SCALE_RATIO = 1.2f; QString const Text3DOverlay::TYPE = "text3d"; @@ -114,7 +115,7 @@ void Text3DOverlay::render(RenderArgs* args) { glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND); glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND); - DependencyManager::get()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false, false); + DependencyManager::get()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, quadColor, _geometryId); // Same font properties as textSize() diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index fba36f0c77..e7641ab2c2 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -40,8 +40,10 @@ QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml")); * * @property {number} margin=0 - Sets the leftMargin and topMargin values, in pixels. * Write-only. - * @property {number} leftMargin=0 - The left margin's size, in pixels. Write-only. - * @property {number} topMargin=0 - The top margin's size, in pixels. Write-only. + * @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin. + * Write-only. + * @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin. + * Write-only. * @property {string} text="" - The text to display. Text does not automatically wrap; use \n for a line break. Text * is clipped to the bounds. Write-only. * @property {number} font.size=18 - The size of the text, in pixels. Write-only. diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 57d1cc0acf..10050c94d0 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -63,6 +63,19 @@ static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; + +static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { + AbstractViewStateInterface::instance()->postLambdaEvent([surface] { + if (AbstractViewStateInterface::instance()->isAboutToQuit()) { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; + } else { + surface->deleteLater(); + } + }); +}; + Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); _touchDevice.setType(QTouchDevice::TouchScreen); @@ -75,7 +88,8 @@ Web3DOverlay::Web3DOverlay() { connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); //need to be intialized before Tablet 1st open - _webSurface = DependencyManager::get()->acquire(_url); + _webSurface = DependencyManager::get()->acquire(QML); + _cachedWebSurface = true; _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED @@ -114,6 +128,7 @@ void Web3DOverlay::destroyWebSurface() { if (!_webSurface) { return; } + QQuickItem* rootItem = _webSurface->getRootItem(); if (rootItem && rootItem->objectName() == "tabletRoot") { @@ -135,10 +150,15 @@ void Web3DOverlay::destroyWebSurface() { QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); - auto offscreenCache = DependencyManager::get(); - // FIXME prevents crash on shutdown, but we shoudln't have to do this check - if (offscreenCache) { - offscreenCache->release(QML, _webSurface); + + // If the web surface was fetched out of the cache, release it back into the cache + if (_cachedWebSurface) { + auto offscreenCache = DependencyManager::get(); + // FIXME prevents crash on shutdown, but we shoudln't have to do this check + if (offscreenCache) { + offscreenCache->release(QML, _webSurface); + } + _cachedWebSurface = false; } _webSurface.reset(); } @@ -147,6 +167,8 @@ void Web3DOverlay::buildWebSurface() { if (_webSurface) { return; } + // FIXME the context save here is most likely unecessary since the QML surfaces now render + // off the main thread, and all GL context work is done off the main thread (I *think*) gl::withSavedContext([&] { // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) @@ -156,10 +178,13 @@ void Web3DOverlay::buildWebSurface() { if (isWebContent()) { _webSurface = DependencyManager::get()->acquire(QML); + _cachedWebSurface = true; _webSurface->getRootItem()->setProperty("url", _url); _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); } else { - _webSurface = DependencyManager::get()->acquire(_url); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); + _webSurface->load(_url); + _cachedWebSurface = false; setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 4098e98488..d888424cbc 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -88,6 +88,7 @@ private: InputMode _inputMode { Touch }; QSharedPointer _webSurface; + bool _cachedWebSurface{ false }; gpu::TexturePointer _texture; QString _url; QString _scriptURL; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 849ea5ee6b..dc004fe60d 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -882,6 +882,11 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { +#ifdef Q_OS_ANDROID + // disable IK on android + return underPoses; +#endif + // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 2c37d2d089..e00cad9bc7 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -78,6 +78,20 @@ int AnimSkeleton::getParentIndex(int jointIndex) const { return _joints[jointIndex].parentIndex; } +std::vector AnimSkeleton::getChildrenOfJoint(int jointIndex) const { + // Children and grandchildren, etc. + std::vector result; + if (jointIndex != -1) { + for (int i = jointIndex + 1; i < (int)_joints.size(); i++) { + if (_joints[i].parentIndex == jointIndex + || (std::find(result.begin(), result.end(), _joints[i].parentIndex) != result.end())) { + result.push_back(i); + } + } + } + return result; +} + const QString& AnimSkeleton::getJointName(int jointIndex) const { return _joints[jointIndex].name; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 664358f414..27dbf5ea92 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -43,6 +43,7 @@ public: const AnimPose& getPostRotationPose(int jointIndex) const; int getParentIndex(int jointIndex) const; + std::vector getChildrenOfJoint(int jointIndex) const; AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const; diff --git a/libraries/animation/src/ElbowConstraint.cpp b/libraries/animation/src/ElbowConstraint.cpp index 17c6bb2da6..deaf1a0945 100644 --- a/libraries/animation/src/ElbowConstraint.cpp +++ b/libraries/animation/src/ElbowConstraint.cpp @@ -66,16 +66,12 @@ bool ElbowConstraint::apply(glm::quat& rotation) const { bool twistWasClamped = (twistAngle != clampedTwistAngle); // update rotation - const float MIN_SWING_REAL_PART = 0.99999f; - if (twistWasClamped || fabsf(swingRotation.w) < MIN_SWING_REAL_PART) { - if (twistWasClamped) { - twistRotation = glm::angleAxis(clampedTwistAngle, _axis); - } - // we discard all swing and only keep twist - rotation = twistRotation * _referenceRotation; - return true; + if (twistWasClamped) { + twistRotation = glm::angleAxis(clampedTwistAngle, _axis); } - return false; + // we discard all swing and only keep twist + rotation = twistRotation * _referenceRotation; + return true; } glm::quat ElbowConstraint::computeCenterRotation() const { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d6791ab0b8..0833b28142 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -199,6 +199,8 @@ void Rig::destroyAnimGraph() { _internalPoseSet._overridePoses.clear(); _internalPoseSet._overrideFlags.clear(); _numOverrides = 0; + _leftEyeJointChildren.clear(); + _rightEyeJointChildren.clear(); } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { @@ -225,12 +227,17 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); _rootJointIndex = geometry.rootJointIndex; + _leftEyeJointIndex = geometry.leftEyeJointIndex; + _rightEyeJointIndex = geometry.rightEyeJointIndex; _leftHandJointIndex = geometry.leftHandJointIndex; _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; _rightHandJointIndex = geometry.rightHandJointIndex; _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; + + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); } void Rig::reset(const FBXGeometry& geometry) { @@ -253,6 +260,8 @@ void Rig::reset(const FBXGeometry& geometry) { buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); _rootJointIndex = geometry.rootJointIndex; + _leftEyeJointIndex = geometry.leftEyeJointIndex; + _rightEyeJointIndex = geometry.rightEyeJointIndex; _leftHandJointIndex = geometry.leftHandJointIndex; _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; @@ -260,6 +269,9 @@ void Rig::reset(const FBXGeometry& geometry) { _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); + if (!_animGraphURL.isEmpty()) { _animNode.reset(); initAnimGraph(_animGraphURL); @@ -1234,6 +1246,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) { + const bool ENABLE_POLE_VECTORS = false; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; int hipsIndex = indexOfJoint("Hips"); @@ -1256,7 +1269,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - if (!leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); // smooth toward desired pole vector from previous pole vector... to reduce jitter @@ -1303,7 +1316,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - if (!rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); // smooth toward desired pole vector from previous pole vector... to reduce jitter @@ -1430,6 +1443,15 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm // directly set absolutePose rotation _internalPoseSet._absolutePoses[index].rot() = deltaQuat * headQuat; + + // Update eye joint's children. + auto children = index == _leftEyeJointIndex ? _leftEyeJointChildren : _rightEyeJointChildren; + for (int i = 0; i < (int)children.size(); i++) { + int jointIndex = children[i]; + int parentIndex = _animSkeleton->getParentIndex(jointIndex); + _internalPoseSet._absolutePoses[jointIndex] = + _internalPoseSet._absolutePoses[parentIndex] * _internalPoseSet._relativePoses[jointIndex]; + } } } @@ -1534,18 +1556,21 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateFeet(leftFootEnabled, rightFootEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]); + + if (headEnabled) { + // Blend IK chains toward the joint limit centers, this should stablize head and hand ik. + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); + } else { + // Blend IK chains toward the UnderPoses, so some of the animaton motion is present in the IK solution. + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); + } + // if the hips or the feet are being controlled. if (hipsEnabled || rightFootEnabled || leftFootEnabled) { - // for more predictable IK solve from the center of the joint limits, not from the underpose - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); - // replace the feet animation with the default pose, this is to prevent unexpected toe wiggling. _animVars.set("defaultPoseOverlayAlpha", 1.0f); _animVars.set("defaultPoseOverlayBoneSet", (int)AnimOverlay::BothFeetBoneSet); } else { - // augment the IK with the underPose. - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); - // feet should follow source animation _animVars.unset("defaultPoseOverlayAlpha"); _animVars.unset("defaultPoseOverlayBoneSet"); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7230d05e2a..e30b5d655c 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -267,6 +267,11 @@ protected: int _rootJointIndex { -1 }; + int _leftEyeJointIndex { -1 }; + int _rightEyeJointIndex { -1 }; + std::vector _leftEyeJointChildren; + std::vector _rightEyeJointChildren; + int _leftHandJointIndex { -1 }; int _leftElbowJointIndex { -1 }; int _leftShoulderJointIndex { -1 }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 873d294303..e2dae92a94 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1397,9 +1397,11 @@ void AudioClient::setNoiseReduction(bool enable) { } -void AudioClient::setIsStereoInput(bool isStereoInput) { - if (isStereoInput != _isStereoInput) { +bool AudioClient::setIsStereoInput(bool isStereoInput) { + bool stereoInputChanged = false; + if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) { _isStereoInput = isStereoInput; + stereoInputChanged = true; if (_isStereoInput) { _desiredInputFormat.setChannelCount(2); @@ -1419,6 +1421,8 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { // restart the input device switchInputToAudioDevice(_inputDeviceInfo); } + + return stereoInputChanged; } bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3e6e83b598..3bfbdb49ce 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -192,7 +192,8 @@ public slots: void toggleMute(); bool isMuted() { return _muted; } - virtual void setIsStereoInput(bool stereo) override; + virtual bool setIsStereoInput(bool stereo) override; + virtual bool isStereoInput() override { return _isStereoInput; } void setNoiseReduction(bool isNoiseGateEnabled); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp index 3df469b830..0b8921a117 100644 --- a/libraries/audio-client/src/AudioPeakValues.cpp +++ b/libraries/audio-client/src/AudioPeakValues.cpp @@ -43,14 +43,19 @@ void AudioClient::checkPeakValues() { // prepare the windows environment CoInitialize(NULL); + std::unique_lock lock(_deviceMutex, std::defer_lock); + // if disabled, clean up active clients if (!_enablePeakValues) { - activeClients.clear(); + if (lock.try_lock()) { + // deferred, if timer callbacks overlap + activeClients.clear(); + } return; } // lock the devices so the _inputDevices list is static - std::unique_lock lock(_deviceMutex); + lock.lock(); HRESULT result; // initialize the payload diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 37731c31f7..30cbceeb0e 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -28,7 +28,7 @@ class AbstractAudioInterface : public QObject { Q_OBJECT public: AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {}; - + static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, bool isStereo, const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, PacketType packetType, QString codecName = QString("")); @@ -40,8 +40,10 @@ public: public slots: virtual bool shouldLoopbackInjectors() { return false; } - - virtual void setIsStereoInput(bool stereo) = 0; + + virtual bool setIsStereoInput(bool stereo) = 0; + + virtual bool isStereoInput() = 0; }; Q_DECLARE_METATYPE(AbstractAudioInterface*) diff --git a/libraries/audio/src/AudioSRCData.h b/libraries/audio/src/AudioSRCData.h index 872a8b7ea9..4a51038683 100644 --- a/libraries/audio/src/AudioSRCData.h +++ b/libraries/audio/src/AudioSRCData.h @@ -294,786 +294,1042 @@ static const float prototypeFilterLQ[PROTOTYPE_COEFS_LQ] = { }; // Minimum-phase equiripple FIR lowpass -// taps = 96, phases = 32 +// taps = 128, phases = 32 // -// passband = 20.4khz @ 44.1khz sample rate -// stopband = 22.3khz @ 44.1khz sample rate +// passband = 20.8khz @ 44.1khz sample rate +// stopband = 22.25khz @ 44.1khz sample rate // passband ripple = +-0.01dB // stopband attn = -120dB (-60dB at Fs/2) // -static const int PROTOTYPE_TAPS_MQ = 96; // filter taps per phase +static const int PROTOTYPE_TAPS_MQ = 128; // filter taps per phase static const int PROTOTYPE_PHASES_MQ = 32; // oversampling factor static const int PROTOTYPE_COEFS_MQ = PROTOTYPE_TAPS_MQ * PROTOTYPE_PHASES_MQ; static const float prototypeFilterMQ[PROTOTYPE_COEFS_MQ] = { - 0.00000000e+00f, 2.66261387e-05f, 2.32573075e-05f, 3.40598514e-05f, - 4.71976232e-05f, 6.41453728e-05f, 8.54998397e-05f, 1.11687756e-04f, - 1.43801203e-04f, 1.82845116e-04f, 2.30066959e-04f, 2.86345358e-04f, - 3.53237802e-04f, 4.32445137e-04f, 5.25448765e-04f, 6.34181809e-04f, - 7.60751248e-04f, 9.07447815e-04f, 1.07664885e-03f, 1.27106372e-03f, - 1.49358697e-03f, 1.74743568e-03f, 2.03588950e-03f, 2.36271119e-03f, - 2.73178932e-03f, 3.14735017e-03f, 3.61394575e-03f, 4.13622717e-03f, - 4.71948430e-03f, 5.36900611e-03f, 6.09056802e-03f, 6.89015275e-03f, - 7.77420063e-03f, 8.74929355e-03f, 9.82247765e-03f, 1.10010827e-02f, - 1.22926302e-02f, 1.37050920e-02f, 1.52467086e-02f, 1.69259236e-02f, - 1.87515164e-02f, 2.07324611e-02f, 2.28780155e-02f, 2.51976248e-02f, - 2.77009067e-02f, 3.03976923e-02f, 3.32978310e-02f, 3.64113360e-02f, - 3.97483567e-02f, 4.33188718e-02f, 4.71330394e-02f, 5.12008208e-02f, - 5.55321084e-02f, 6.01366326e-02f, 6.50238954e-02f, 7.02031239e-02f, - 7.56832648e-02f, 8.14728338e-02f, 8.75798730e-02f, 9.40119584e-02f, - 1.00776061e-01f, 1.07878507e-01f, 1.15324915e-01f, 1.23120096e-01f, - 1.31268060e-01f, 1.39771791e-01f, 1.48633353e-01f, 1.57853754e-01f, - 1.67432845e-01f, 1.77369245e-01f, 1.87660373e-01f, 1.98302270e-01f, - 2.09289566e-01f, 2.20615469e-01f, 2.32271687e-01f, 2.44248375e-01f, - 2.56534006e-01f, 2.69115519e-01f, 2.81978070e-01f, 2.95105197e-01f, - 3.08478608e-01f, 3.22078299e-01f, 3.35882501e-01f, 3.49867623e-01f, - 3.64008344e-01f, 3.78277579e-01f, 3.92646502e-01f, 4.07084554e-01f, - 4.21559509e-01f, 4.36037587e-01f, 4.50483333e-01f, 4.64859851e-01f, - 4.79128834e-01f, 4.93250606e-01f, 5.07184249e-01f, 5.20887717e-01f, - 5.34317956e-01f, 5.47430992e-01f, 5.60182068e-01f, 5.72525885e-01f, - 5.84416587e-01f, 5.95808093e-01f, 6.06654109e-01f, 6.16908418e-01f, - 6.26525013e-01f, 6.35458287e-01f, 6.43663262e-01f, 6.51095737e-01f, - 6.57712516e-01f, 6.63471582e-01f, 6.68332383e-01f, 6.72255982e-01f, - 6.75205267e-01f, 6.77145171e-01f, 6.78042866e-01f, 6.77868024e-01f, - 6.76592912e-01f, 6.74192735e-01f, 6.70645661e-01f, 6.65933116e-01f, - 6.60039959e-01f, 6.52954537e-01f, 6.44668978e-01f, 6.35179268e-01f, - 6.24485354e-01f, 6.12591298e-01f, 5.99505382e-01f, 5.85240204e-01f, - 5.69812635e-01f, 5.53244046e-01f, 5.35560224e-01f, 5.16791389e-01f, - 4.96972230e-01f, 4.76141864e-01f, 4.54343785e-01f, 4.31625775e-01f, - 4.08039850e-01f, 3.83642160e-01f, 3.58492768e-01f, 3.32655624e-01f, - 3.06198278e-01f, 2.79191781e-01f, 2.51710369e-01f, 2.23831288e-01f, - 1.95634574e-01f, 1.67202627e-01f, 1.38620172e-01f, 1.09973678e-01f, - 8.13511972e-02f, 5.28420521e-02f, 2.45362976e-02f, -3.47536059e-03f, - -3.11022613e-02f, -5.82539130e-02f, -8.48404277e-02f, -1.10773085e-01f, - -1.35964505e-01f, -1.60329143e-01f, -1.83783713e-01f, -2.06247521e-01f, - -2.27642912e-01f, -2.47895575e-01f, -2.66934994e-01f, -2.84694759e-01f, - -3.01112940e-01f, -3.16132369e-01f, -3.29701030e-01f, -3.41772346e-01f, - -3.52305311e-01f, -3.61264967e-01f, -3.68622493e-01f, -3.74355402e-01f, - -3.78447769e-01f, -3.80890353e-01f, -3.81680737e-01f, -3.80823346e-01f, - -3.78329604e-01f, -3.74217856e-01f, -3.68513368e-01f, -3.61248333e-01f, - -3.52461771e-01f, -3.42199371e-01f, -3.30513331e-01f, -3.17462278e-01f, - -3.03110942e-01f, -2.87529909e-01f, -2.70795402e-01f, -2.52988932e-01f, - -2.34196901e-01f, -2.14510337e-01f, -1.94024366e-01f, -1.72837915e-01f, - -1.51053161e-01f, -1.28775054e-01f, -1.06110949e-01f, -8.31699904e-02f, - -6.00625904e-02f, -3.68999700e-02f, -1.37935592e-02f, 9.14553458e-03f, - 3.18070733e-02f, 5.40822352e-02f, 7.58641385e-02f, 9.70484745e-02f, - 1.17533938e-01f, 1.37222760e-01f, 1.56021286e-01f, 1.73840407e-01f, - 1.90596011e-01f, 2.06209480e-01f, 2.20608090e-01f, 2.33725388e-01f, - 2.45501544e-01f, 2.55883728e-01f, 2.64826361e-01f, 2.72291364e-01f, - 2.78248393e-01f, 2.82675023e-01f, 2.85556849e-01f, 2.86887621e-01f, - 2.86669233e-01f, 2.84911794e-01f, 2.81633533e-01f, 2.76860737e-01f, - 2.70627618e-01f, 2.62976159e-01f, 2.53955831e-01f, 2.43623409e-01f, - 2.32042617e-01f, 2.19283824e-01f, 2.05423637e-01f, 1.90544470e-01f, - 1.74734152e-01f, 1.58085383e-01f, 1.40695263e-01f, 1.22664710e-01f, - 1.04097951e-01f, 8.51019110e-02f, 6.57856138e-02f, 4.62595576e-02f, - 2.66351206e-02f, 7.02392479e-03f, -1.24628349e-02f, -3.17149760e-02f, - -5.06239785e-02f, -6.90836040e-02f, -8.69905146e-02f, -1.04244853e-01f, - -1.20750775e-01f, -1.36417108e-01f, -1.51157814e-01f, -1.64892426e-01f, - -1.77546604e-01f, -1.89052578e-01f, -1.99349459e-01f, -2.08383604e-01f, - -2.16108994e-01f, -2.22487452e-01f, -2.27488844e-01f, -2.31091294e-01f, - -2.33281282e-01f, -2.34053710e-01f, -2.33411914e-01f, -2.31367695e-01f, - -2.27941159e-01f, -2.23160659e-01f, -2.17062542e-01f, -2.09691005e-01f, - -2.01097713e-01f, -1.91341620e-01f, -1.80488420e-01f, -1.68610294e-01f, - -1.55785433e-01f, -1.42097452e-01f, -1.27634978e-01f, -1.12491085e-01f, - -9.67626792e-02f, -8.05498893e-02f, -6.39555117e-02f, -4.70843056e-02f, - -3.00423695e-02f, -1.29364721e-02f, 4.12658213e-03f, 2.10406497e-02f, - 3.77009117e-02f, 5.40045195e-02f, 6.98511685e-02f, 8.51438453e-02f, - 9.97893434e-02f, 1.13698854e-01f, 1.26788523e-01f, 1.38979975e-01f, - 1.50200835e-01f, 1.60385073e-01f, 1.69473512e-01f, 1.77414145e-01f, - 1.84162459e-01f, 1.89681664e-01f, 1.93943026e-01f, 1.96925891e-01f, - 1.98617880e-01f, 1.99014945e-01f, 1.98121379e-01f, 1.95949757e-01f, - 1.92520816e-01f, 1.87863336e-01f, 1.82013990e-01f, 1.75016936e-01f, - 1.66923648e-01f, 1.57792538e-01f, 1.47688544e-01f, 1.36682666e-01f, - 1.24851512e-01f, 1.12276865e-01f, 9.90449817e-02f, 8.52461277e-02f, - 7.09739571e-02f, 5.63248677e-02f, 4.13973844e-02f, 2.62914655e-02f, - 1.11078925e-02f, -4.05239147e-03f, -1.90890617e-02f, -3.39029253e-02f, - -4.83966164e-02f, -6.24753705e-02f, -7.60474312e-02f, -8.90248310e-02f, - -1.01323842e-01f, -1.12865634e-01f, -1.23576716e-01f, -1.33389421e-01f, - -1.42242349e-01f, -1.50080822e-01f, -1.56857133e-01f, -1.62530890e-01f, - -1.67069336e-01f, -1.70447432e-01f, -1.72648091e-01f, -1.73662223e-01f, - -1.73488855e-01f, -1.72134990e-01f, -1.69615638e-01f, -1.65953690e-01f, - -1.61179685e-01f, -1.55331592e-01f, -1.48454607e-01f, -1.40600737e-01f, - -1.31828470e-01f, -1.22202323e-01f, -1.11792473e-01f, -1.00674140e-01f, - -8.89271262e-02f, -7.66352871e-02f, -6.38858531e-02f, -5.07689244e-02f, - -3.73767275e-02f, -2.38031024e-02f, -1.01427700e-02f, 3.50934751e-03f, - 1.70586918e-02f, 3.04117852e-02f, 4.34768954e-02f, 5.61645819e-02f, - 6.83884030e-02f, 8.00654394e-02f, 9.11168932e-02f, 1.01468633e-01f, - 1.11051648e-01f, 1.19802610e-01f, 1.27664173e-01f, 1.34585462e-01f, - 1.40522346e-01f, 1.45437787e-01f, 1.49302022e-01f, 1.52092772e-01f, - 1.53795398e-01f, 1.54402941e-01f, 1.53916196e-01f, 1.52343637e-01f, - 1.49701396e-01f, 1.46013026e-01f, 1.41309468e-01f, 1.35628632e-01f, - 1.29015253e-01f, 1.21520496e-01f, 1.13201567e-01f, 1.04121296e-01f, - 9.43477288e-02f, 8.39535107e-02f, 7.30154990e-02f, 6.16141003e-02f, - 4.98327306e-02f, 3.77571918e-02f, 2.54750964e-02f, 1.30751821e-02f, - 6.46703310e-04f, -1.17211963e-02f, -2.39401591e-02f, -3.59231836e-02f, - -4.75853156e-02f, -5.88442141e-02f, -6.96206901e-02f, -7.98393844e-02f, - -8.94291504e-02f, -9.83236922e-02f, -1.06461911e-01f, -1.13788431e-01f, - -1.20253885e-01f, -1.25815350e-01f, -1.30436540e-01f, -1.34088139e-01f, - -1.36747929e-01f, -1.38400952e-01f, -1.39039622e-01f, -1.38663699e-01f, - -1.37280328e-01f, -1.34903937e-01f, -1.31556108e-01f, -1.27265426e-01f, - -1.22067204e-01f, -1.16003256e-01f, -1.09121554e-01f, -1.01475836e-01f, - -9.31252402e-02f, -8.41338287e-02f, -7.45701074e-02f, -6.45065373e-02f, - -5.40189487e-02f, -4.31860278e-02f, -3.20886964e-02f, -2.08095155e-02f, - -9.43209682e-03f, 1.95952348e-03f, 1.32815415e-02f, 2.44509086e-02f, - 3.53860502e-02f, 4.60073767e-02f, 5.62379012e-02f, 6.60038045e-02f, - 7.52349145e-02f, 8.38653047e-02f, 9.18337009e-02f, 9.90839224e-02f, - 1.05565308e-01f, 1.11233075e-01f, 1.16048569e-01f, 1.19979672e-01f, - 1.23000853e-01f, 1.25093440e-01f, 1.26245746e-01f, 1.26453062e-01f, - 1.25717722e-01f, 1.24049103e-01f, 1.21463405e-01f, 1.17983660e-01f, - 1.13639474e-01f, 1.08466740e-01f, 1.02507425e-01f, 9.58092271e-02f, - 8.84251889e-02f, 8.04132568e-02f, 7.18358762e-02f, 6.27595398e-02f, - 5.32541869e-02f, 4.33927283e-02f, 3.32505260e-02f, 2.29047308e-02f, - 1.24337991e-02f, 1.91679812e-03f, -8.56707996e-03f, -1.89392067e-02f, - -2.91220390e-02f, -3.90397544e-02f, -4.86187492e-02f, -5.77882518e-02f, - -6.64808274e-02f, -7.46328184e-02f, -8.21849070e-02f, -8.90824700e-02f, - -9.52760405e-02f, -1.00721606e-01f, -1.05380953e-01f, -1.09221973e-01f, - -1.12218800e-01f, -1.14352078e-01f, -1.15609043e-01f, -1.15983584e-01f, - -1.15476334e-01f, -1.14094552e-01f, -1.11852147e-01f, -1.08769485e-01f, - -1.04873243e-01f, -1.00196154e-01f, -9.47768230e-02f, -8.86593336e-02f, - -8.18929115e-02f, -7.45315642e-02f, -6.66336429e-02f, -5.82614122e-02f, - -4.94804661e-02f, -4.03593526e-02f, -3.09689748e-02f, -2.13820268e-02f, - -1.16724544e-02f, -1.91492456e-03f, 7.81582939e-03f, 1.74454843e-02f, - 2.69007705e-02f, 3.61099984e-02f, 4.50035684e-02f, 5.35145862e-02f, - 6.15792651e-02f, 6.91374558e-02f, 7.61331132e-02f, 8.25146874e-02f, - 8.82354960e-02f, 9.32540795e-02f, 9.75345512e-02f, 1.01046761e-01f, - 1.03766581e-01f, 1.05676059e-01f, 1.06763536e-01f, 1.07023704e-01f, - 1.06457645e-01f, 1.05072793e-01f, 1.02882886e-01f, 9.99077707e-02f, - 9.61733515e-02f, 9.17112437e-02f, 8.65585836e-02f, 8.07577320e-02f, - 7.43559143e-02f, 6.74048223e-02f, 5.99602286e-02f, 5.20815485e-02f, - 4.38313441e-02f, 3.52748647e-02f, 2.64794784e-02f, 1.75142425e-02f, - 8.44923569e-03f, -6.44874246e-04f, -9.69744245e-03f, -1.86383724e-02f, - -2.73986645e-02f, -3.59109639e-02f, -4.41100113e-02f, -5.19332538e-02f, - -5.93212174e-02f, -6.62180197e-02f, -7.25717930e-02f, -7.83350496e-02f, - -8.34650279e-02f, -8.79240738e-02f, -9.16798665e-02f, -9.47056367e-02f, - -9.69804367e-02f, -9.84892103e-02f, -9.92229391e-02f, -9.91786555e-02f, - -9.83594994e-02f, -9.67746323e-02f, -9.44391692e-02f, -9.13740384e-02f, - -8.76057968e-02f, -8.31664374e-02f, -7.80930677e-02f, -7.24276448e-02f, - -6.62166182e-02f, -5.95105803e-02f, -5.23638154e-02f, -4.48338833e-02f, - -3.69811953e-02f, -2.88684416e-02f, -2.05602211e-02f, -1.21223919e-02f, - -3.62166500e-03f, 4.87497472e-03f, 1.33008118e-02f, 2.15898817e-02f, - 2.96774903e-02f, 3.75007710e-02f, 4.49990900e-02f, 5.21145974e-02f, - 5.87926126e-02f, 6.49820878e-02f, 7.06359734e-02f, 7.57115940e-02f, - 8.01709511e-02f, 8.39810244e-02f, 8.71140281e-02f, 8.95475866e-02f, - 9.12649204e-02f, 9.22549366e-02f, 9.25123481e-02f, 9.20376276e-02f, - 9.08370647e-02f, 8.89226233e-02f, 8.63119090e-02f, 8.30279355e-02f, - 7.90990135e-02f, 7.45584160e-02f, 6.94441713e-02f, 6.37987430e-02f, - 5.76686372e-02f, 5.11040823e-02f, 4.41585505e-02f, 3.68884245e-02f, - 2.93523999e-02f, 2.16111886e-02f, 1.37268658e-02f, 5.76249790e-03f, - -2.21844412e-03f, -1.01525268e-02f, -1.79769055e-02f, -2.56297984e-02f, - -3.30509798e-02f, -4.01822180e-02f, -4.69678148e-02f, -5.33549679e-02f, - -5.92942039e-02f, -6.47397915e-02f, -6.96500034e-02f, -7.39875785e-02f, - -7.77198807e-02f, -8.08192090e-02f, -8.32629929e-02f, -8.50339403e-02f, - -8.61201727e-02f, -8.65153352e-02f, -8.62185642e-02f, -8.52345328e-02f, - -8.35733873e-02f, -8.12506452e-02f, -7.82870548e-02f, -7.47083936e-02f, - -7.05453033e-02f, -6.58330086e-02f, -6.06109755e-02f, -5.49226267e-02f, - -4.88150284e-02f, -4.23383952e-02f, -3.55457806e-02f, -2.84925949e-02f, - -2.12361386e-02f, -1.38352158e-02f, -6.34954674e-03f, 1.16060193e-03f, - 8.63496366e-03f, 1.60137521e-02f, 2.32381310e-02f, 3.02506552e-02f, - 3.69957912e-02f, 4.34202737e-02f, 4.94736003e-02f, 5.51084064e-02f, - 6.02807966e-02f, 6.49507475e-02f, 6.90823813e-02f, 7.26442553e-02f, - 7.56096025e-02f, 7.79565049e-02f, 7.96681192e-02f, 8.07327409e-02f, - 8.11438831e-02f, 8.09003904e-02f, 8.00063059e-02f, 7.84709455e-02f, - 7.63087509e-02f, 7.35391471e-02f, 7.01864251e-02f, 6.62794512e-02f, - 6.18515053e-02f, 5.69399870e-02f, 5.15860100e-02f, 4.58342039e-02f, - 3.97322256e-02f, 3.33304486e-02f, 2.66814783e-02f, 1.98397856e-02f, - 1.28612257e-02f, 5.80259393e-03f, -1.27886112e-03f, -8.32584019e-03f, - -1.52814930e-02f, -2.20899334e-02f, -2.86965591e-02f, -3.50486065e-02f, - -4.10954995e-02f, -4.67893136e-02f, -5.20850948e-02f, -5.69412403e-02f, - -6.13198474e-02f, -6.51869890e-02f, -6.85129647e-02f, -7.12725306e-02f, - -7.34451585e-02f, -7.50150843e-02f, -7.59714899e-02f, -7.63085524e-02f, - -7.60255166e-02f, -7.51266167e-02f, -7.36211027e-02f, -7.15230741e-02f, - -6.88514539e-02f, -6.56297130e-02f, -6.18857220e-02f, -5.76515554e-02f, - -5.29631207e-02f, -4.78599233e-02f, -4.23847313e-02f, -3.65832079e-02f, - -3.05035130e-02f, -2.41959566e-02f, -1.77125171e-02f, -1.11065052e-02f, - -4.43199940e-03f, 2.25646608e-03f, 8.90442951e-03f, 1.54579021e-02f, - 2.18638281e-02f, 2.80705462e-02f, 3.40280794e-02f, 3.96887204e-02f, - 4.50072604e-02f, 4.99414316e-02f, 5.44522079e-02f, 5.85041536e-02f, - 6.20656397e-02f, 6.51091720e-02f, 6.76114887e-02f, 6.95538952e-02f, - 7.09222580e-02f, 7.17071898e-02f, 7.19040960e-02f, 7.15131728e-02f, - 7.05394351e-02f, 6.89926231e-02f, 6.68871545e-02f, 6.42419416e-02f, - 6.10802598e-02f, 5.74295313e-02f, 5.33211189e-02f, 4.87899934e-02f, - 4.38745121e-02f, 3.86160670e-02f, 3.30586967e-02f, 2.72487970e-02f, - 2.12346816e-02f, 1.50661848e-02f, 8.79427315e-03f, 2.47061577e-03f, - -3.85286592e-03f, -1.01243770e-02f, -1.62927172e-02f, -2.23076581e-02f, - -2.81203948e-02f, -3.36839076e-02f, -3.89533679e-02f, -4.38864517e-02f, - -4.84437422e-02f, -5.25890421e-02f, -5.62895706e-02f, -5.95163477e-02f, - -6.22443207e-02f, -6.44526403e-02f, -6.61247499e-02f, -6.72485494e-02f, - -6.78164946e-02f, -6.78255920e-02f, -6.72774647e-02f, -6.61782995e-02f, - -6.45388112e-02f, -6.23740965e-02f, -5.97035328e-02f, -5.65505921e-02f, - -5.29426881e-02f, -4.89108139e-02f, -4.44894260e-02f, -3.97160733e-02f, - -3.46310671e-02f, -2.92771850e-02f, -2.36993008e-02f, -1.79439902e-02f, - -1.20591515e-02f, -6.09362869e-03f, -9.67646869e-05f, 5.88193161e-03f, - 1.17933059e-02f, 1.75888522e-02f, 2.32211575e-02f, 2.86443333e-02f, - 3.38142963e-02f, 3.86892021e-02f, 4.32297427e-02f, 4.73994738e-02f, - 5.11651476e-02f, 5.44969062e-02f, 5.73685597e-02f, 5.97578063e-02f, - 6.16463827e-02f, 6.30201712e-02f, 6.38694025e-02f, 6.41886234e-02f, - 6.39767725e-02f, 6.32372156e-02f, 6.19775942e-02f, 6.02098911e-02f, - 5.79502219e-02f, 5.52186945e-02f, 5.20393403e-02f, 4.84397091e-02f, - 4.44508421e-02f, 4.01068641e-02f, 3.54447399e-02f, 3.05039700e-02f, - 2.53262499e-02f, 1.99550952e-02f, 1.44355284e-02f, 8.81365390e-03f, - 3.13628120e-03f, -2.54943138e-03f, -8.19639523e-03f, -1.37579753e-02f, - -1.91883895e-02f, -2.44430618e-02f, -2.94789913e-02f, -3.42551088e-02f, - -3.87326249e-02f, -4.28753310e-02f, -4.66498901e-02f, -5.00261035e-02f, - -5.29771799e-02f, -5.54799037e-02f, -5.75148383e-02f, -5.90665317e-02f, - -6.01234993e-02f, -6.06784603e-02f, -6.07283027e-02f, -6.02740708e-02f, - -5.93210354e-02f, -5.78785948e-02f, -5.59601225e-02f, -5.35829951e-02f, - -5.07682523e-02f, -4.75405757e-02f, -4.39279759e-02f, -3.99615901e-02f, - -3.56753971e-02f, -3.11059445e-02f, -2.62920307e-02f, -2.12743678e-02f, - -1.60952574e-02f, -1.07981986e-02f, -5.42758786e-03f, -2.82820897e-05f, - 5.35475816e-03f, 1.06767918e-02f, 1.58937793e-02f, 2.09626000e-02f, - 2.58415407e-02f, 3.04905869e-02f, 3.48717018e-02f, 3.89492209e-02f, - 4.26901199e-02f, 4.60642317e-02f, 4.90445439e-02f, 5.16074029e-02f, - 5.37326813e-02f, 5.54039762e-02f, 5.66086803e-02f, 5.73381350e-02f, - 5.75876520e-02f, 5.73565533e-02f, 5.66481680e-02f, 5.54698025e-02f, - 5.38326410e-02f, 5.17516706e-02f, 4.92455315e-02f, 4.63363691e-02f, - 4.30496184e-02f, 3.94137631e-02f, 3.54601772e-02f, 3.12227524e-02f, - 2.67376509e-02f, 2.20430273e-02f, 1.71786786e-02f, 1.21856622e-02f, - 7.10604036e-03f, 1.98245601e-03f, -3.14218264e-03f, -8.22510738e-03f, - -1.32239734e-02f, -1.80973012e-02f, -2.28047577e-02f, -2.73075056e-02f, - -3.15685177e-02f, -3.55529069e-02f, -3.92281902e-02f, -4.25645528e-02f, - -4.55351013e-02f, -4.81160565e-02f, -5.02870006e-02f, -5.20309689e-02f, - -5.33346001e-02f, -5.41883074e-02f, -5.45862315e-02f, -5.45263860e-02f, - -5.40105902e-02f, -5.30444830e-02f, -5.16374652e-02f, -4.98025685e-02f, - -4.75564009e-02f, -4.49189535e-02f, -4.19134372e-02f, -3.85660549e-02f, - -3.49058685e-02f, -3.09643739e-02f, -2.67754233e-02f, -2.23747988e-02f, - -1.77999754e-02f, -1.30897755e-02f, -8.28403078e-03f, -3.42329111e-03f, - 1.45156003e-03f, 6.29963147e-03f, 1.10803882e-02f, 1.57539476e-02f, - 2.02814494e-02f, 2.46253752e-02f, 2.87498179e-02f, 3.26208554e-02f, - 3.62067406e-02f, 3.94782545e-02f, 4.24088471e-02f, 4.49749385e-02f, - 4.71560491e-02f, 4.89349963e-02f, 5.02980346e-02f, 5.12349025e-02f, - 5.17389769e-02f, 5.18073110e-02f, 5.14405369e-02f, 5.06430401e-02f, - 4.94227353e-02f, 4.77911027e-02f, 4.57630135e-02f, 4.33566667e-02f, - 4.05933433e-02f, 3.74973028e-02f, 3.40955069e-02f, 3.04173973e-02f, - 2.64947139e-02f, 2.23610806e-02f, 1.80518625e-02f, 1.36037814e-02f, - 9.05461294e-03f, 4.44289505e-03f, -1.92440179e-04f, -4.81233436e-03f, - -9.37799893e-03f, -1.38512169e-02f, -1.81946099e-02f, -2.23720431e-02f, - -2.63488613e-02f, -3.00921879e-02f, -3.35711998e-02f, -3.67574146e-02f, - -3.96248452e-02f, -4.21502967e-02f, -4.43135012e-02f, -4.60972960e-02f, - -4.74877737e-02f, -4.84743534e-02f, -4.90498864e-02f, -4.92107432e-02f, - -4.89567473e-02f, -4.82912269e-02f, -4.72210048e-02f, -4.57562287e-02f, - -4.39103965e-02f, -4.17001535e-02f, -3.91451580e-02f, -3.62679524e-02f, - -3.30936915e-02f, -2.96499930e-02f, -2.59666361e-02f, -2.20753766e-02f, - -1.80096001e-02f, -1.38040903e-02f, -9.49470398e-03f, -5.11805324e-03f, - -7.11263614e-04f, 3.68845212e-03f, 8.04397633e-03f, 1.23187361e-02f, - 1.64768878e-02f, 2.04837107e-02f, 2.43058243e-02f, 2.79115316e-02f, - 3.12710307e-02f, 3.43566819e-02f, 3.71432406e-02f, 3.96080600e-02f, - 4.17312589e-02f, 4.34958998e-02f, 4.48881324e-02f, 4.58972728e-02f, - 4.65159011e-02f, 4.67399486e-02f, 4.65686301e-02f, 4.60045573e-02f, - 4.50536126e-02f, 4.37249572e-02f, 4.20309099e-02f, 3.99868149e-02f, - 3.76109658e-02f, 3.49244023e-02f, 3.19507068e-02f, 2.87158399e-02f, - 2.52478981e-02f, 2.15768722e-02f, 1.77343611e-02f, 1.37533396e-02f, - 9.66788343e-03f, 5.51281005e-03f, 1.32343973e-03f, -2.86465940e-03f, - -7.01611627e-03f, -1.10959241e-02f, -1.50697748e-02f, -1.89043738e-02f, - -2.25676833e-02f, -2.60292100e-02f, -2.92602731e-02f, -3.22341600e-02f, - -3.49264870e-02f, -3.73152937e-02f, -3.93812165e-02f, -4.11077217e-02f, - -4.24811803e-02f, -4.34909828e-02f, -4.41296386e-02f, -4.43928125e-02f, - -4.42793688e-02f, -4.37913678e-02f, -4.29339923e-02f, -4.17156128e-02f, - -4.01475749e-02f, -3.82441650e-02f, -3.60224960e-02f, -3.35022818e-02f, - -3.07057642e-02f, -2.76574300e-02f, -2.43838133e-02f, -2.09133302e-02f, - -1.72759525e-02f, -1.35029658e-02f, -9.62678336e-03f, -5.68051801e-03f, - -1.69782450e-03f, 2.28741709e-03f, 6.24142092e-03f, 1.01307640e-02f, - 1.39226541e-02f, 1.75852206e-02f, 2.10877784e-02f, 2.44011197e-02f, - 2.74976630e-02f, 3.03517861e-02f, 3.29399891e-02f, 3.52410789e-02f, - 3.72364000e-02f, 3.89098736e-02f, 4.02482712e-02f, 4.12411943e-02f, - 4.18812295e-02f, 4.21639571e-02f, 4.20880553e-02f, 4.16551548e-02f, - 4.08700173e-02f, 3.97403235e-02f, 3.82766485e-02f, 3.64924037e-02f, - 3.44036670e-02f, 3.20290679e-02f, 2.93895781e-02f, 2.65084084e-02f, - 2.34107263e-02f, 2.01234514e-02f, 1.66750736e-02f, 1.30953481e-02f, - 9.41506492e-03f, 5.66577314e-03f, 1.87953721e-03f, -1.91138340e-03f, - -5.67474970e-03f, -9.37866958e-03f, -1.29918571e-02f, -1.64838496e-02f, - -1.98253468e-02f, -2.29883663e-02f, -2.59465660e-02f, -2.86753884e-02f, - -3.11523087e-02f, -3.33570024e-02f, -3.52715334e-02f, -3.68804752e-02f, - -3.81710220e-02f, -3.91331489e-02f, -3.97596215e-02f, -4.00461207e-02f, - -3.99911716e-02f, -3.95962751e-02f, -3.88657519e-02f, -3.78068352e-02f, - -3.64294686e-02f, -3.47463159e-02f, -3.27725952e-02f, -3.05259482e-02f, - -2.80262873e-02f, -2.52956368e-02f, -2.23579079e-02f, -1.92387018e-02f, - -1.59651184e-02f, -1.25654653e-02f, -9.06903728e-03f, -5.50589383e-03f, - -1.90658612e-03f, 1.69819644e-03f, 5.27767591e-03f, 8.80149662e-03f, - 1.22398002e-02f, 1.55635605e-02f, 1.87448095e-02f, 2.17568784e-02f, - 2.45746067e-02f, 2.71745749e-02f, 2.95352660e-02f, 3.16372730e-02f, - 3.34634806e-02f, 3.49991083e-02f, 3.62319569e-02f, 3.71524003e-02f, - 3.77535229e-02f, 3.80311360e-02f, 3.79838595e-02f, 3.76130115e-02f, - 3.69227353e-02f, 3.59198588e-02f, 3.46138556e-02f, 3.30167422e-02f, - 3.11429955e-02f, 2.90094255e-02f, 2.66349737e-02f, 2.40406079e-02f, - 2.12490922e-02f, 1.82848227e-02f, 1.51735699e-02f, 1.19423378e-02f, - 8.61899874e-03f, 5.23220696e-03f, 1.81100681e-03f, -1.61529876e-03f, - -5.01747104e-03f, -8.36657631e-03f, -1.16341674e-02f, -1.47926336e-02f, - -1.78153429e-02f, -2.06769168e-02f, -2.33533934e-02f, -2.58225130e-02f, - -2.80638107e-02f, -3.00588248e-02f, -3.17912703e-02f, -3.32471461e-02f, - -3.44148496e-02f, -3.52852564e-02f, -3.58518459e-02f, -3.61106776e-02f, - -3.60604528e-02f, -3.57025239e-02f, -3.50408687e-02f, -3.40820566e-02f, - -3.28351332e-02f, -3.13116305e-02f, -2.95253828e-02f, -2.74924261e-02f, - -2.52308529e-02f, -2.27606768e-02f, -2.01036311e-02f, -1.72829328e-02f, - -1.43232049e-02f, -1.12501406e-02f, -8.09033460e-03f, -4.87102079e-03f, - -1.61991454e-03f, 1.63511391e-03f, 4.86626363e-03f, 8.04597224e-03f, - 1.11472224e-02f, 1.41437409e-02f, 1.70102606e-02f, 1.97226336e-02f, - 2.22581567e-02f, 2.45957058e-02f, 2.67158611e-02f, 2.86011679e-02f, - 3.02362198e-02f, 3.16077891e-02f, 3.27049359e-02f, 3.35191077e-02f, - 3.40442066e-02f, 3.42766034e-02f, 3.42151913e-02f, 3.38613937e-02f, - 3.32191151e-02f, 3.22947056e-02f, 3.10969473e-02f, 2.96368987e-02f, - 2.79278426e-02f, 2.59851513e-02f, 2.38261551e-02f, 2.14699783e-02f, - 1.89373695e-02f, 1.62505430e-02f, 1.34329466e-02f, 1.05090853e-02f, - 7.50431274e-03f, 4.44457496e-03f, 1.35623378e-03f, -1.73420401e-03f, - -4.80027676e-03f, -7.81581580e-03f, -1.07551646e-02f, -1.35933944e-02f, - -1.63065125e-02f, -1.88716732e-02f, -2.12673829e-02f, -2.34736329e-02f, - -2.54721072e-02f, -2.72463321e-02f, -2.87818120e-02f, -3.00661165e-02f, - -3.10890008e-02f, -3.18425307e-02f, -3.23210635e-02f, -3.25213258e-02f, - -3.24424720e-02f, -3.20860357e-02f, -3.14558962e-02f, -3.05582934e-02f, - -2.94017600e-02f, -2.79969684e-02f, -2.63567172e-02f, -2.44957911e-02f, - -2.24308005e-02f, -2.01800752e-02f, -1.77634589e-02f, -1.52021705e-02f, - -1.25186056e-02f, -9.73612725e-03f, -6.87889048e-03f, -3.97162201e-03f, - -1.03936748e-03f, 1.89264412e-03f, 4.79928751e-03f, 7.65573326e-03f, - 1.04376485e-02f, 1.31214182e-02f, 1.56843167e-02f, 1.81047816e-02f, - 2.03624722e-02f, 2.24385491e-02f, 2.43157747e-02f, 2.59786613e-02f, - 2.74136059e-02f, 2.86090270e-02f, 2.95554085e-02f, 3.02453933e-02f, - 3.06738695e-02f, 3.08379709e-02f, 3.07371080e-02f, 3.03729625e-02f, - 2.97494799e-02f, 2.88728239e-02f, 2.77512742e-02f, 2.63952326e-02f, - 2.48170551e-02f, 2.30309672e-02f, 2.10529469e-02f, 1.89005531e-02f, - 1.65928079e-02f, 1.41499753e-02f, 1.15934547e-02f, 8.94553439e-03f, - 6.22921565e-03f, 3.46802456e-03f, 6.85807660e-04f, -2.09351762e-03f, - -4.84607801e-03f, -7.54834950e-03f, -1.01772976e-02f, -1.27105702e-02f, - -1.51267312e-02f, -1.74054170e-02f, -1.95274752e-02f, -2.14752102e-02f, - -2.32324132e-02f, -2.47846112e-02f, -2.61191248e-02f, -2.72251852e-02f, - -2.80939857e-02f, -2.87188307e-02f, -2.90951296e-02f, -2.92204205e-02f, - -2.90944185e-02f, -2.87189899e-02f, -2.80981757e-02f, -2.72380366e-02f, - -2.61467465e-02f, -2.48343900e-02f, -2.33129547e-02f, -2.15961399e-02f, - -1.96993447e-02f, -1.76394103e-02f, -1.54345417e-02f, -1.31041691e-02f, - -1.06686731e-02f, -8.14934589e-03f, -5.56806233e-03f, -2.94722846e-03f, - -3.09480282e-04f, 2.32248018e-03f, 4.92603655e-03f, 7.47890755e-03f, - 9.95933342e-03f, 1.23462104e-02f, 1.46193259e-02f, 1.67595114e-02f, - 1.87488002e-02f, 2.05705588e-02f, 2.22096708e-02f, 2.36526409e-02f, - 2.48876669e-02f, 2.59048208e-02f, 2.66960464e-02f, 2.72552501e-02f, - 2.75783775e-02f, 2.76634150e-02f, 2.75103717e-02f, 2.71213402e-02f, - 2.65004170e-02f, 2.56536729e-02f, 2.45891352e-02f, 2.33166362e-02f, - 2.18477931e-02f, 2.01958648e-02f, 1.83756573e-02f, 1.64033346e-02f, - 1.42963876e-02f, 1.20733364e-02f, 9.75368948e-03f, 7.35772256e-03f, - 4.90625681e-03f, 2.42058549e-03f, -7.78123459e-05f, -2.56740681e-03f, - -5.02679577e-03f, -7.43492567e-03f, -9.77127015e-03f, -1.20159192e-02f, - -1.41498747e-02f, -1.61551576e-02f, -1.80148982e-02f, -1.97135784e-02f, - -2.12371093e-02f, -2.25729462e-02f, -2.37102058e-02f, -2.46397233e-02f, - -2.53542230e-02f, -2.58482111e-02f, -2.61181554e-02f, -2.61624917e-02f, - -2.59815513e-02f, -2.55776241e-02f, -2.49549139e-02f, -2.41194964e-02f, - -2.30792716e-02f, -2.18438342e-02f, -2.04244587e-02f, -1.88339919e-02f, - -1.70866610e-02f, -1.51980228e-02f, -1.31848099e-02f, -1.10647537e-02f, - -8.85646007e-03f, -6.57922197e-03f, -4.25289625e-03f, -1.89763580e-03f, - 4.66153419e-04f, 2.81808458e-03f, 5.13797202e-03f, 7.40592877e-03f, - 9.60259119e-03f, 1.17092799e-02f, 1.37081595e-02f, 1.55823451e-02f, - 1.73161398e-02f, 1.88950442e-02f, 2.03059901e-02f, 2.15373720e-02f, - 2.25791706e-02f, 2.34230473e-02f, 2.40623953e-02f, 2.44923608e-02f, - 2.47099481e-02f, 2.47139948e-02f, 2.45051449e-02f, 2.40859346e-02f, - 2.34606570e-02f, 2.26353881e-02f, 2.16179088e-02f, 2.04175981e-02f, - 1.90454439e-02f, 1.75138196e-02f, 1.58364356e-02f, 1.40282761e-02f, - 1.21053168e-02f, 1.00845358e-02f, 7.98365780e-03f, 5.82106543e-03f, - 3.61560367e-03f, 1.38641929e-03f, -8.47218784e-04f, -3.06599466e-03f, - -5.25087774e-03f, -7.38313991e-03f, -9.44455890e-03f, -1.14176395e-02f, - -1.32856363e-02f, -1.50328238e-02f, -1.66445337e-02f, -1.81073376e-02f, - -1.94091057e-02f, -2.05391909e-02f, -2.14883924e-02f, -2.22491632e-02f, - -2.28155723e-02f, -2.31833780e-02f, -2.33501014e-02f, -2.33149599e-02f, - -2.30789680e-02f, -2.26448166e-02f, -2.20169170e-02f, -2.12013570e-02f, - -2.02057946e-02f, -1.90394186e-02f, -1.77128837e-02f, -1.62381452e-02f, - -1.46284655e-02f, -1.28981349e-02f, -1.10625252e-02f, -9.13782192e-03f, - -7.14089458e-03f, -5.08925289e-03f, -3.00076224e-03f, -8.93563929e-04f, - 1.21412493e-03f, 3.30408095e-03f, 5.35835840e-03f, 7.35936068e-03f, - 9.29000657e-03f, 1.11338879e-02f, 1.28753714e-02f, 1.44998123e-02f, - 1.59935874e-02f, 1.73442409e-02f, 1.85406270e-02f, 1.95729684e-02f, - 2.04328853e-02f, 2.11135770e-02f, 2.16097716e-02f, 2.19178390e-02f, - 2.20357483e-02f, 2.19631551e-02f, 2.17013288e-02f, 2.12531940e-02f, - 2.06232436e-02f, 1.98175530e-02f, 1.88436810e-02f, 1.77106134e-02f, - 1.64286792e-02f, 1.50094525e-02f, 1.34656661e-02f, 1.18110672e-02f, - 1.00603222e-02f, 8.22887936e-03f, 6.33283241e-03f, 4.38874672e-03f, - 2.41360251e-03f, 4.24522718e-04f, -1.56124462e-03f, -3.52657933e-03f, - -5.45459389e-03f, -7.32876872e-03f, -9.13310880e-03f, -1.08522951e-02f, - -1.24717730e-02f, -1.39778955e-02f, -1.53580868e-02f, -1.66008540e-02f, - -1.76959435e-02f, -1.86344131e-02f, -1.94087423e-02f, -2.00127825e-02f, - -2.04419305e-02f, -2.06931003e-02f, -2.07647439e-02f, -2.06568662e-02f, - -2.03710410e-02f, -1.99103562e-02f, -1.92794106e-02f, -1.84842504e-02f, - -1.75323167e-02f, -1.64323977e-02f, -1.51944972e-02f, -1.38297917e-02f, - -1.23505473e-02f, -1.07699177e-02f, -9.10190890e-03f, -7.36125336e-03f, - -5.56322740e-03f, -3.72358380e-03f, -1.85834629e-03f, 1.62797268e-05f, - 1.88406706e-03f, 3.72890929e-03f, 5.53494881e-03f, 7.28672421e-03f, - 8.96929998e-03f, 1.05683732e-02f, 1.20704430e-02f, 1.34628666e-02f, - 1.47340226e-02f, 1.58733625e-02f, 1.68714835e-02f, 1.77202945e-02f, - 1.84129995e-02f, 1.89441562e-02f, 1.93097640e-02f, 1.95072568e-02f, - 1.95355280e-02f, 1.93949451e-02f, 1.90873200e-02f, 1.86159281e-02f, - 1.79854186e-02f, 1.72018134e-02f, 1.62724351e-02f, 1.52058602e-02f, - 1.40117601e-02f, 1.27009429e-02f, 1.12851595e-02f, 9.77701852e-03f, - 8.18987013e-03f, 6.53774227e-03f, 4.83514325e-03f, 3.09695569e-03f, - 1.33834551e-03f, -4.25417736e-04f, -2.17907719e-03f, -3.90750635e-03f, - -5.59584405e-03f, -7.22967588e-03f, -8.79504135e-03f, -1.02786946e-02f, - -1.16680841e-02f, -1.29515500e-02f, -1.41183852e-02f, -1.51589335e-02f, - -1.60646521e-02f, -1.68282128e-02f, -1.74435295e-02f, -1.79058268e-02f, - -1.82116669e-02f, -1.83589783e-02f, -1.83470786e-02f, -1.81766429e-02f, - -1.78497359e-02f, -1.73697470e-02f, -1.67414052e-02f, -1.59706714e-02f, - -1.50647456e-02f, -1.40319474e-02f, -1.28817007e-02f, -1.16243487e-02f, - -1.02711700e-02f, -8.83423668e-03f, -7.32626993e-03f, -5.76055872e-03f, - -4.15088347e-03f, -2.51130684e-03f, -8.56121139e-04f, 8.00308080e-04f, - 2.44364656e-03f, 4.05971160e-03f, 5.63466432e-03f, 7.15502326e-03f, - 8.60784346e-03f, 9.98079276e-03f, 1.12623356e-02f, 1.24416991e-02f, - 1.35090584e-02f, 1.44556378e-02f, 1.52736545e-02f, 1.59565533e-02f, - 1.64989049e-02f, 1.68965640e-02f, 1.71466261e-02f, 1.72474699e-02f, - 1.71988001e-02f, 1.70016009e-02f, 1.66581092e-02f, 1.61718740e-02f, - 1.55476166e-02f, 1.47912749e-02f, 1.39098794e-02f, 1.29115146e-02f, - 1.18052872e-02f, 1.06011344e-02f, 9.30987887e-03f, 7.94299705e-03f, - 6.51261294e-03f, 5.03132373e-03f, 3.51216505e-03f, 1.96838522e-03f, - 4.13468447e-04f, -1.13910857e-03f, -2.67593573e-03f, -4.18373241e-03f, - -5.64960835e-03f, -7.06102465e-03f, -8.40597413e-03f, -9.67306954e-03f, - -1.08516566e-02f, -1.19318601e-02f, -1.29047339e-02f, -1.37622291e-02f, - -1.44974073e-02f, -1.51043649e-02f, -1.55783142e-02f, -1.59157002e-02f, - -1.61141039e-02f, -1.61723481e-02f, -1.60904670e-02f, -1.58696900e-02f, - -1.55124969e-02f, -1.50224681e-02f, -1.44043694e-02f, -1.36640534e-02f, - -1.28083790e-02f, -1.18451989e-02f, -1.07832734e-02f, -9.63216695e-03f, - -8.40217979e-03f, -7.10427993e-03f, -5.74993358e-03f, -4.35111118e-03f, - -2.92006835e-03f, -1.46932453e-03f, -1.15175859e-05f, 1.44071303e-03f, - 2.87483552e-03f, 4.27848214e-03f, 5.63962178e-03f, 6.94667140e-03f, - 8.18849175e-03f, 9.35461319e-03f, 1.04352259e-02f, 1.14213169e-02f, - 1.23047082e-02f, 1.30781646e-02f, 1.37354286e-02f, 1.42712333e-02f, - 1.46814453e-02f, 1.49630117e-02f, 1.51139756e-02f, 1.51335483e-02f, - 1.50220835e-02f, 1.47810620e-02f, 1.44130827e-02f, 1.39218007e-02f, - 1.33119894e-02f, 1.25893957e-02f, 1.17606823e-02f, 1.08334768e-02f, - 9.81618503e-03f, 8.71796809e-03f, 7.54864594e-03f, 6.31863400e-03f, - 5.03883063e-03f, 3.72048341e-03f, 2.37517536e-03f, 1.01468348e-03f, - -3.49196130e-04f, -1.70461937e-03f, -3.03987673e-03f, -4.34350332e-03f, - -5.60433339e-03f, -6.81160996e-03f, -7.95512104e-03f, -9.02519096e-03f, - -1.00128542e-02f, -1.09099085e-02f, -1.17089124e-02f, -1.24033803e-02f, - -1.29876865e-02f, -1.34572282e-02f, -1.38083913e-02f, -1.40386233e-02f, - -1.41463921e-02f, -1.41312813e-02f, -1.39939047e-02f, -1.37359379e-02f, - -1.33601191e-02f, -1.28701544e-02f, -1.22707533e-02f, -1.15675607e-02f, - -1.07670757e-02f, -9.87660975e-03f, -8.90426422e-03f, -7.85876691e-03f, - -6.74947754e-03f, -5.58627059e-03f, -4.37942163e-03f, -3.13956447e-03f, - -1.87757286e-03f, -6.04471683e-04f, 6.68687153e-04f, 1.93086297e-03f, - 3.17116571e-03f, 4.37896308e-03f, 5.54391133e-03f, 6.65608640e-03f, - 7.70610236e-03f, 8.68508463e-03f, 9.58487893e-03f, 1.03979809e-02f, - 1.11177300e-02f, 1.17382727e-02f, 1.22546165e-02f, 1.26627517e-02f, - 1.29595607e-02f, 1.31429469e-02f, 1.32117696e-02f, 1.31659380e-02f, - 1.30062790e-02f, 1.27346679e-02f, 1.23539183e-02f, 1.18677784e-02f, - 1.12808819e-02f, 1.05987296e-02f, 9.82765005e-03f, 8.97466282e-03f, - 8.04750011e-03f, 7.05450097e-03f, 6.00455814e-03f, 4.90697069e-03f, - 3.77147007e-03f, 2.60803032e-03f, 1.42685205e-03f, 2.38252620e-04f, - -9.47456984e-04f, -2.12001557e-03f, -3.26930604e-03f, -4.38549571e-03f, - -5.45903303e-03f, -6.48080118e-03f, -7.44217435e-03f, -8.33506789e-03f, - -9.15202991e-03f, -9.88632138e-03f, -1.05319082e-02f, -1.10835810e-02f, - -1.15369475e-02f, -1.18884995e-02f, -1.21356183e-02f, -1.22765935e-02f, - -1.23106741e-02f, -1.22379903e-02f, -1.20596309e-02f, -1.17775907e-02f, - -1.13947504e-02f, -1.09148490e-02f, -1.03424524e-02f, -9.68292516e-03f, - -8.94233597e-03f, -8.12744125e-03f, -7.24562722e-03f, -6.30481777e-03f, - -5.31340458e-03f, -4.28019210e-03f, -3.21432388e-03f, -2.12514582e-03f, - -1.02222518e-03f, 8.48402655e-05f, 1.18644055e-03f, 2.27305998e-03f, - 3.33534080e-03f, 4.36417880e-03f, 5.35082505e-03f, 6.28688841e-03f, - 7.16448437e-03f, 7.97626556e-03f, 8.71547416e-03f, 9.37602127e-03f, - 9.95252480e-03f, 1.04403423e-02f, 1.08356349e-02f, 1.11353709e-02f, - 1.13373727e-02f, 1.14403200e-02f, 1.14437206e-02f, 1.13479929e-02f, - 1.11544081e-02f, 1.08650423e-02f, 1.04828191e-02f, 1.00114740e-02f, - 9.45546226e-03f, 8.81995782e-03f, 8.11083408e-03f, 7.33454940e-03f, - 6.49810508e-03f, 5.60902655e-03f, 4.67524423e-03f, 3.70504943e-03f, - 2.70698716e-03f, 1.68984103e-03f, 6.62511289e-04f, -3.66063830e-04f, - -1.38696193e-03f, -2.39137620e-03f, -3.37067114e-03f, -4.31647732e-03f, - -5.22074273e-03f, -6.07581700e-03f, -6.87449892e-03f, -7.61012299e-03f, - -8.27659902e-03f, -8.86843891e-03f, -9.38086115e-03f, -9.80976718e-03f, - -1.01518071e-02f, -1.04044062e-02f, -1.05657705e-02f, -1.06349047e-02f, - -1.06116070e-02f, -1.04965065e-02f, -1.02910004e-02f, -9.99726062e-03f, - -9.61823783e-03f, -9.15759359e-03f, -8.61966847e-03f, -8.00948048e-03f, - -7.33263803e-03f, -6.59530070e-03f, -5.80411820e-03f, -4.96619474e-03f, - -4.08901524e-03f, -3.18032417e-03f, -2.24815040e-03f, -1.30069573e-03f, - -3.46202014e-04f, 6.07005678e-04f, 1.55067386e-03f, 2.47666319e-03f, - 3.37705799e-03f, 4.24414413e-03f, 5.07055312e-03f, 5.84933606e-03f, - 6.57394125e-03f, 7.23833180e-03f, 7.83701102e-03f, 8.36512776e-03f, - 8.81838829e-03f, 9.19320845e-03f, 9.48670950e-03f, 9.69671225e-03f, - 9.82176780e-03f, 9.86117239e-03f, 9.81500060e-03f, 9.68401019e-03f, - 9.46973376e-03f, 9.17440866e-03f, 8.80096882e-03f, 8.35299798e-03f, - 7.83470675e-03f, 7.25092919e-03f, 6.60700863e-03f, 5.90880044e-03f, - 5.16260232e-03f, 4.37511499e-03f, 3.55333719e-03f, 2.70453571e-03f, - 1.83621654e-03f, 9.55971892e-04f, 7.14941081e-05f, -8.09546859e-04f, - -1.67949997e-03f, -2.53091770e-03f, -3.35648519e-03f, -4.14916820e-03f, - -4.90226718e-03f, -5.60941116e-03f, -6.26471868e-03f, -6.86271228e-03f, - -7.39850829e-03f, -7.86773441e-03f, -8.26664253e-03f, -8.59209655e-03f, - -8.84162491e-03f, -9.01340035e-03f, -9.10631046e-03f, -9.11992414e-03f, - -9.05447739e-03f, -8.91090648e-03f, -8.69081572e-03f, -8.39650138e-03f, - -8.03081656e-03f, -7.59728914e-03f, -7.09998516e-03f, -6.54351866e-03f, - -5.93297162e-03f, -5.27391890e-03f, -4.57225558e-03f, -3.83429395e-03f, - -3.06659425e-03f, -2.27593987e-03f, -1.46929411e-03f, -6.53717718e-04f, - 1.63676664e-04f, 9.75799545e-04f, 1.77563501e-03f, 2.55633177e-03f, - 3.31116336e-03f, 4.03378434e-03f, 4.71802893e-03f, 5.35816666e-03f, - 5.94887641e-03f, 6.48526029e-03f, 6.96292147e-03f, 7.37801172e-03f, - 7.72723246e-03f, 8.00785932e-03f, 8.21780315e-03f, 8.35557459e-03f, - 8.42032149e-03f, 8.41183281e-03f, 8.33054181e-03f, 8.17747564e-03f, - 7.95433786e-03f, 7.66336298e-03f, 7.30743051e-03f, 6.88990453e-03f, - 6.41471602e-03f, 5.88625192e-03f, 5.30935851e-03f, 4.68925554e-03f, - 4.03151761e-03f, 3.34203719e-03f, 2.62693694e-03f, 1.89253809e-03f, - 1.14528552e-03f, 3.91735329e-04f, -3.61576948e-04f, -1.10810138e-03f, - -1.84142652e-03f, -2.55525216e-03f, -3.24348047e-03f, -3.90028979e-03f, - -4.52012845e-03f, -5.09781875e-03f, -5.62855343e-03f, -6.10796080e-03f, - -6.53213670e-03f, -6.89768993e-03f, -7.20172730e-03f, -7.44190362e-03f, - -7.61648070e-03f, -7.72424208e-03f, -7.76459042e-03f, -7.73750201e-03f, - -7.64355603e-03f, -7.48387832e-03f, -7.26015909e-03f, -6.97470458e-03f, - -6.63023185e-03f, -6.23005432e-03f, -5.77789750e-03f, -5.27794195e-03f, - -4.73473676e-03f, -4.15323407e-03f, -3.53862105e-03f, -2.89641236e-03f, - -2.23229690e-03f, -1.55213945e-03f, -8.61924039e-04f, -1.67646019e-04f, - 5.24634726e-04f, 1.20893687e-03f, 1.87936876e-03f, 2.53020242e-03f, - 3.15589017e-03f, 3.75112921e-03f, 4.31093227e-03f, 4.83063434e-03f, - 5.30589437e-03f, 5.73285894e-03f, 6.10805341e-03f, 6.42847724e-03f, - 6.69165721e-03f, 6.89558077e-03f, 7.03876861e-03f, 7.12031596e-03f, - 7.13979497e-03f, 7.09737416e-03f, 6.99372292e-03f, 6.83002861e-03f, - 6.60805053e-03f, 6.32997518e-03f, 5.99849919e-03f, 5.61676889e-03f, - 5.18833988e-03f, 4.71716487e-03f, 4.20753440e-03f, 3.66405499e-03f, - 3.09162548e-03f, 2.49532169e-03f, 1.88042982e-03f, 1.25240798e-03f, - 6.16721933e-04f, -2.10678481e-05f, -6.55427849e-04f, -1.28088666e-03f, - -1.89206206e-03f, -2.48374616e-03f, -3.05092627e-03f, -3.58876931e-03f, - -4.09281312e-03f, -4.55886002e-03f, -4.98304465e-03f, -5.36192844e-03f, - -5.69246645e-03f, -5.97202847e-03f, -6.19845814e-03f, -6.37007698e-03f, - -6.48568424e-03f, -6.54455208e-03f, -6.54646884e-03f, -6.49172972e-03f, - -6.38107878e-03f, -6.21576754e-03f, -5.99751984e-03f, -5.72848662e-03f, - -5.41128232e-03f, -5.04886935e-03f, -4.64464895e-03f, -4.20232731e-03f, - -3.72592908e-03f, -3.21976323e-03f, -2.68837079e-03f, -2.13647137e-03f, - -1.56895819e-03f, -9.90826814e-04f, -4.07137101e-04f, 1.77034128e-04f, - 7.56614045e-04f, 1.32662280e-03f, 1.88216158e-03f, 2.41851689e-03f, - 2.93111653e-03f, 3.41567923e-03f, 3.86812645e-03f, 4.28473921e-03f, - 4.66208309e-03f, 4.99709627e-03f, 5.28710944e-03f, 5.52983700e-03f, - 5.72344791e-03f, 5.86650915e-03f, 5.95806206e-03f, 5.99756713e-03f, - 5.98498044e-03f, 5.92067495e-03f, 5.80547493e-03f, 5.64067393e-03f, - 5.42792578e-03f, 5.16935371e-03f, 4.86743833e-03f, 4.52499899e-03f, - 4.14524387e-03f, 3.73165445e-03f, 3.28798006e-03f, 2.81822731e-03f, - 2.32660442e-03f, 1.81747333e-03f, 1.29535738e-03f, 7.64811514e-04f, - 2.30499750e-04f, -3.02941233e-04f, -8.30904036e-04f, -1.34884935e-03f, - -1.85232914e-03f, -2.33709184e-03f, -2.79902951e-03f, -3.23425712e-03f, - -3.63917195e-03f, -4.01042034e-03f, -4.34498121e-03f, -4.64014771e-03f, - -4.89357867e-03f, -5.10333096e-03f, -5.26778390e-03f, -5.38579823e-03f, - -5.45658844e-03f, -5.47979294e-03f, -5.45548594e-03f, -5.38411670e-03f, - -5.26658130e-03f, -5.10413250e-03f, -4.89843368e-03f, -4.65147045e-03f, - -4.36565944e-03f, -4.04362854e-03f, -3.68839513e-03f, -3.30322131e-03f, - -2.89157726e-03f, -2.45718506e-03f, -2.00393056e-03f, -1.53582784e-03f, - -1.05702424e-03f, -5.71697826e-04f, -8.41072148e-05f, 4.01536814e-04f, - 8.81019836e-04f, 1.35024965e-03f, 1.80521520e-03f, 2.24205417e-03f, - 2.65710363e-03f, 3.04687963e-03f, 3.40815186e-03f, 3.73795216e-03f, - 4.03361599e-03f, 4.29276556e-03f, 4.51335779e-03f, 4.69370830e-03f, - 4.83246574e-03f, 4.92869877e-03f, 4.98175710e-03f, 4.99146876e-03f, - 4.95797374e-03f, 4.88180168e-03f, 4.76385107e-03f, 4.60539108e-03f, - 4.40799947e-03f, 4.17361084e-03f, 3.90446439e-03f, 3.60306851e-03f, - 3.27223243e-03f, 2.91495118e-03f, 2.53450126e-03f, 2.13428258e-03f, - 1.71785917e-03f, 1.28894440e-03f, 8.51307681e-04f, 4.08783855e-04f, - -3.47765574e-05f, -4.75529382e-04f, -9.09669659e-04f, -1.33349225e-03f, - -1.74339270e-03f, -2.13590830e-03f, -2.50772263e-03f, -2.85576594e-03f, - -3.17714507e-03f, -3.46924367e-03f, -3.72969621e-03f, -3.95645033e-03f, - -4.14770845e-03f, -4.30203167e-03f, -4.41831180e-03f, -4.49574815e-03f, - -4.53388522e-03f, -4.53264318e-03f, -4.49224360e-03f, -4.41325534e-03f, - -4.29659437e-03f, -4.14348253e-03f, -3.95545723e-03f, -3.73433539e-03f, - -3.48222611e-03f, -3.20148621e-03f, -2.89470832e-03f, -2.56468722e-03f, - -2.21442685e-03f, -1.84705410e-03f, -1.46585762e-03f, -1.07420242e-03f, - -6.75541964e-04f, -2.73362726e-04f, 1.28857758e-04f, 5.27625747e-04f, - 9.19505126e-04f, 1.30117518e-03f, 1.66938725e-03f, 2.02102017e-03f, - 2.35316570e-03f, 2.66303554e-03f, 2.94810484e-03f, 3.20604525e-03f, - 3.43477820e-03f, 3.63252281e-03f, 3.79773367e-03f, 3.92918244e-03f, - 4.02592670e-03f, 4.08733570e-03f, 4.11309343e-03f, 4.10318081e-03f, - 4.05789653e-03f, 3.97783502e-03f, 3.86391767e-03f, 3.71728516e-03f, - 3.53944503e-03f, 3.33208164e-03f, 3.09717361e-03f, 2.83693803e-03f, - 2.55373764e-03f, 2.25016474e-03f, 1.92898540e-03f, 1.59304145e-03f, - 1.24535047e-03f, 8.88983788e-04f, 5.27054162e-04f, 1.62739254e-04f, - -2.00809162e-04f, -5.60458110e-04f, -9.13122723e-04f, -1.25578987e-03f, - -1.58556683e-03f, -1.89968711e-03f, -2.19553534e-03f, -2.47065402e-03f, - -2.72279518e-03f, -2.94992092e-03f, -3.15023183e-03f, -3.32214990e-03f, - -3.46435451e-03f, -3.57581566e-03f, -3.65574132e-03f, -3.70363730e-03f, - -3.71929064e-03f, -3.70275212e-03f, -3.65435442e-03f, -3.57474125e-03f, - -3.46473848e-03f, -3.32552683e-03f, -3.15846353e-03f, -2.96515654e-03f, - -2.74746347e-03f, -2.50737272e-03f, -2.24712016e-03f, -1.96906951e-03f, - -1.67571539e-03f, -1.36971428e-03f, -1.05376024e-03f, -7.30644072e-04f, - -4.03206799e-04f, -7.42995897e-05f, 2.53245135e-04f, 5.76588008e-04f, - 8.92968397e-04f, 1.19970438e-03f, 1.49420457e-03f, 1.77402045e-03f, - 2.03680128e-03f, 2.28039302e-03f, 2.50282909e-03f, 2.70229005e-03f, - 2.87722528e-03f, 3.02626345e-03f, 3.14829228e-03f, 3.24241372e-03f, - 3.30798804e-03f, 3.34463320e-03f, 3.35222123e-03f, 3.33084720e-03f, - 3.28088558e-03f, 3.20297004e-03f, 3.09790204e-03f, 2.96678891e-03f, - 2.81092150e-03f, 2.63179675e-03f, 2.43109407e-03f, 2.21069486e-03f, - 1.97261876e-03f, 1.71903709e-03f, 1.45221718e-03f, 1.17455819e-03f, - 8.88520565e-04f, 5.96615677e-04f, 3.01413631e-04f, 5.46569794e-06f, - -2.88648335e-04f, -5.78440455e-04f, -8.61415446e-04f, -1.13516540e-03f, - -1.39741163e-03f, -1.64595773e-03f, -1.87874945e-03f, -2.09387292e-03f, - -2.28959324e-03f, -2.46433942e-03f, -2.61675008e-03f, -2.74564089e-03f, - -2.85005436e-03f, -2.92925341e-03f, -2.98270791e-03f, -3.01012152e-03f, - -3.01144172e-03f, -2.98680121e-03f, -2.93658715e-03f, -2.86141187e-03f, - -2.76205629e-03f, -2.63955281e-03f, -2.49510204e-03f, -2.33009109e-03f, - -2.14606014e-03f, -1.94473859e-03f, -1.72796009e-03f, -1.49770401e-03f, - -1.25602813e-03f, -1.00509543e-03f, -7.47131162e-04f, -4.84384241e-04f, - -2.19190438e-04f, 4.61883980e-05f, 3.09442481e-04f, 5.68335816e-04f, - 8.20650412e-04f, 1.06426195e-03f, 1.29712378e-03f, 1.51732041e-03f, - 1.72299209e-03f, 1.91251660e-03f, 2.08433504e-03f, 2.23708584e-03f, - 2.36958407e-03f, 2.48081558e-03f, 2.56998499e-03f, 2.63642962e-03f, - 2.67976001e-03f, 2.69973545e-03f, 2.69635545e-03f, 2.66978646e-03f, - 2.62041612e-03f, 2.54882653e-03f, 2.45577849e-03f, 2.34219502e-03f, - 2.20921910e-03f, 2.05811735e-03f, 1.89029043e-03f, 1.70732601e-03f, - 1.51087190e-03f, 1.30273238e-03f, 1.08474708e-03f, 8.58888422e-04f, - 6.27141283e-04f, 3.91541843e-04f, 1.54125345e-04f, -8.30221557e-05f, - -3.17874320e-04f, -5.48434822e-04f, -7.72741386e-04f, -9.88897922e-04f, - -1.19510300e-03f, -1.38966446e-03f, -1.57096128e-03f, -1.73754052e-03f, - -1.88804822e-03f, -2.02133530e-03f, -2.13633027e-03f, -2.23217932e-03f, - -2.30820740e-03f, -2.36387261e-03f, -2.39883936e-03f, -2.41294460e-03f, - -2.40621942e-03f, -2.37884693e-03f, -2.33121609e-03f, -2.26385904e-03f, - -2.17751835e-03f, -2.07302810e-03f, -1.95142870e-03f, -1.81390123e-03f, - -1.66170287e-03f, -1.49626799e-03f, -1.31909211e-03f, -1.13178731e-03f, - -9.36049438e-04f, -7.33582746e-04f, -5.26210187e-04f, -3.15727877e-04f, - -1.03964727e-04f, 1.07230526e-04f, 3.16062176e-04f, 5.20749304e-04f, - 7.19556957e-04f, 9.10822165e-04f, 1.09293790e-03f, 1.26442573e-03f, - 1.42387037e-03f, 1.56997858e-03f, 1.70160712e-03f, 1.81770393e-03f, - 1.91739676e-03f, 1.99993376e-03f, 2.06472082e-03f, 2.11132364e-03f, - 2.13948959e-03f, 2.14907506e-03f, 2.14015415e-03f, 2.11291404e-03f, - 2.06772062e-03f, 2.00509948e-03f, 1.92568205e-03f, 1.83030593e-03f, - 1.71988793e-03f, 1.59546278e-03f, 1.45820958e-03f, 1.30941455e-03f, - 1.15041754e-03f, 9.82655662e-04f, 8.07633897e-04f, 6.26913392e-04f, - 4.42086406e-04f, 2.54752525e-04f, 6.65589815e-05f, -1.20878459e-04f, - -3.05964268e-04f, -4.87118900e-04f, -6.62815511e-04f, -8.31597430e-04f, - -9.92029288e-04f, -1.14283171e-03f, -1.28276198e-03f, -1.41069924e-03f, - -1.52563857e-03f, -1.62665705e-03f, -1.71301571e-03f, -1.78407215e-03f, - -1.83932486e-03f, -1.87840593e-03f, -1.90110668e-03f, -1.90733981e-03f, - -1.89718173e-03f, -1.87081315e-03f, -1.82861635e-03f, -1.77102421e-03f, - -1.69867585e-03f, -1.61227322e-03f, -1.51267564e-03f, -1.40081900e-03f, - -1.27776296e-03f, -1.14464853e-03f, -1.00266848e-03f, -8.53125745e-04f, - -6.97343266e-04f, -5.36715682e-04f, -3.72641111e-04f, -2.06566113e-04f, - -3.99112362e-05f, 1.25872577e-04f, 2.89379042e-04f, 4.49221338e-04f, - 6.04060361e-04f, 7.52597124e-04f, 8.93603965e-04f, 1.02593625e-03f, - 1.14851696e-03f, 1.26037089e-03f, 1.36060965e-03f, 1.44847187e-03f, - 1.52327677e-03f, 1.58450468e-03f, 1.63170530e-03f, 1.66459341e-03f, - 1.68299465e-03f, 1.68684492e-03f, 1.67622052e-03f, 1.65133740e-03f, - 1.61248307e-03f, 1.56013086e-03f, 1.49479356e-03f, 1.41715103e-03f, - 1.32795375e-03f, 1.22803948e-03f, 1.11836065e-03f, 9.99922127e-04f, - 8.73805232e-04f, 7.41131956e-04f, 6.03105296e-04f, 4.60940344e-04f, - 3.15890241e-04f, 1.69211261e-04f, 2.21743638e-05f, -1.23952607e-04f, - -2.67936295e-04f, -4.08555731e-04f, -5.44640381e-04f, -6.75036718e-04f, - -7.98694471e-04f, -9.14605111e-04f, -1.02181314e-03f, -1.11948762e-03f, - -1.20686651e-03f, -1.28326662e-03f, -1.34811836e-03f, -1.40096877e-03f, - -1.44142571e-03f, -1.46927100e-03f, -1.48434461e-03f, -1.48661782e-03f, - -1.47616538e-03f, -1.45317443e-03f, -1.41795006e-03f, -1.37085590e-03f, - -1.31241749e-03f, -1.24319769e-03f, -1.16388507e-03f, -1.07522322e-03f, - -9.78051491e-04f, -8.73267846e-04f, -7.61808745e-04f, -6.44689116e-04f, - -5.22962242e-04f, -3.97698841e-04f, -2.69974946e-04f, -1.40936535e-04f, - -1.16687372e-05f, 1.16707935e-04f, 2.43105542e-04f, 3.66459900e-04f, - 4.85746921e-04f, 5.99973582e-04f, 7.08198423e-04f, 8.09540404e-04f, - 9.03214311e-04f, 9.88445526e-04f, 1.06458286e-03f, 1.13105319e-03f, - 1.18736348e-03f, 1.23309711e-03f, 1.26795195e-03f, 1.29173611e-03f, - 1.30429959e-03f, 1.30564635e-03f, 1.29584484e-03f, 1.27505558e-03f, - 1.24356178e-03f, 1.20169119e-03f, 1.14990793e-03f, 1.08871802e-03f, - 1.01870853e-03f, 9.40552377e-04f, 8.55007232e-04f, 7.62816811e-04f, - 6.64843157e-04f, 5.61968820e-04f, 4.55113887e-04f, 3.45206551e-04f, - 2.33202943e-04f, 1.20101771e-04f, 6.86134614e-06f, -1.05554304e-04f, - -2.16195795e-04f, -3.24112900e-04f, -4.28434961e-04f, -5.28276628e-04f, - -6.22838378e-04f, -7.11351653e-04f, -7.93096664e-04f, -8.67456763e-04f, - -9.33831175e-04f, -9.91726329e-04f, -1.04072669e-03f, -1.08046986e-03f, - -1.11070332e-03f, -1.13122472e-03f, -1.14196584e-03f, -1.14289978e-03f, - -1.13408364e-03f, -1.11569239e-03f, -1.08794173e-03f, -1.05115450e-03f, - -1.00570315e-03f, -9.52066366e-04f, -8.90742618e-04f, -8.22333877e-04f, - -7.47460323e-04f, -6.66834457e-04f, -5.81183573e-04f, -4.91248607e-04f, - -3.97865398e-04f, -3.01852004e-04f, -2.04027600e-04f, -1.05245839e-04f, - -6.36777404e-06f, 9.17617553e-05f, 1.88340766e-04f, 2.82546451e-04f, - 3.73570554e-04f, 4.60711065e-04f, 5.43222306e-04f, 6.20446879e-04f, - 6.91790312e-04f, 7.56668845e-04f, 8.14584433e-04f, 8.65136460e-04f, - 9.07903855e-04f, 9.42617554e-04f, 9.69039487e-04f, 9.87009408e-04f, - 9.96454207e-04f, 9.97361571e-04f, 9.89773454e-04f, 9.73845965e-04f, - 9.49773639e-04f, 9.17830053e-04f, 8.78358737e-04f, 8.31731409e-04f, - 7.78436246e-04f, 7.18952835e-04f, 6.53849580e-04f, 5.83726405e-04f, - 5.09215445e-04f, 4.30986185e-04f, 3.49740961e-04f, 2.66183564e-04f, - 1.81053864e-04f, 9.50814188e-05f, 9.00101989e-06f, -7.64476466e-05f, - -1.60555268e-04f, -2.42615492e-04f, -3.21939784e-04f, -3.97891256e-04f, - -4.69842651e-04f, -5.37218972e-04f, -5.99490711e-04f, -6.56157341e-04f, - -7.06805747e-04f, -7.51040629e-04f, -7.88542406e-04f, -8.19057673e-04f, - -8.42365219e-04f, -8.58354952e-04f, -8.66924438e-04f, -8.68087147e-04f, - -8.61872021e-04f, -8.48416980e-04f, -8.27864530e-04f, -8.00485643e-04f, - -7.66529791e-04f, -7.26368966e-04f, -6.80390151e-04f, -6.29012607e-04f, - -5.72740364e-04f, -5.12074275e-04f, -4.47577967e-04f, -3.79824551e-04f, - -3.09404546e-04f, -2.36955845e-04f, -1.63096797e-04f, -8.84666689e-05f, - -1.36918345e-05f, 6.05605282e-05f, 1.33706352e-04f, 2.05104934e-04f, - 2.74177775e-04f, 3.40364816e-04f, 4.03127428e-04f, 4.61941353e-04f, - 5.16384122e-04f, 5.65987864e-04f, 6.10406960e-04f, 6.49289729e-04f, - 6.82362251e-04f, 7.09386220e-04f, 7.30190172e-04f, 7.44646203e-04f, - 7.52685452e-04f, 7.54304020e-04f, 7.49522579e-04f, 7.38444760e-04f, - 7.21207483e-04f, 6.98036730e-04f, 6.69144529e-04f, 6.34844810e-04f, - 5.95456793e-04f, 5.51362735e-04f, 5.02982434e-04f, 4.50740873e-04f, - 3.95141218e-04f, 3.36652582e-04f, 2.75799155e-04f, 2.13134574e-04f, - 1.49173607e-04f, 8.44743226e-05f, 1.96125552e-05f, -4.48903541e-05f, - -1.08473952e-04f, -1.70630967e-04f, -2.30822756e-04f, -2.88566894e-04f, - -3.43417761e-04f, -3.94905833e-04f, -4.42640702e-04f, -4.86249354e-04f, - -5.25408193e-04f, -5.59793704e-04f, -5.89180289e-04f, -6.13368302e-04f, - -6.32178408e-04f, -6.45490725e-04f, -6.53274170e-04f, -6.55472793e-04f, - -6.52128126e-04f, -6.43307606e-04f, -6.29155845e-04f, -6.09800934e-04f, - -5.85463412e-04f, -5.56409754e-04f, -5.22887459e-04f, -4.85231865e-04f, - -4.43797790e-04f, -3.98970397e-04f, -3.51128444e-04f, -3.00728426e-04f, - -2.48188000e-04f, -1.94019174e-04f, -1.38619235e-04f, -8.25046619e-05f, - -2.61663235e-05f, 2.99583710e-05f, 8.53795123e-05f, 1.39627331e-04f, - 1.92270974e-04f, 2.42876498e-04f, 2.91041116e-04f, 3.36365129e-04f, - 3.78493606e-04f, 4.17129701e-04f, 4.51931766e-04f, 4.82649784e-04f, - 5.09080493e-04f, 5.31030056e-04f, 5.48333695e-04f, 5.60878484e-04f, - 5.68641598e-04f, 5.71552610e-04f, 5.69629306e-04f, 5.62942331e-04f, - 5.51586568e-04f, 5.35680325e-04f, 5.15392595e-04f, 4.90931349e-04f, - 4.62558320e-04f, 4.30500957e-04f, 3.95081151e-04f, 3.56619808e-04f, - 3.15458928e-04f, 2.71954945e-04f, 2.26519129e-04f, 1.79516051e-04f, - 1.31357899e-04f, 8.24572190e-05f, 3.32391965e-05f, -1.58850698e-05f, - -6.45330169e-05f, -1.12264134e-04f, -1.58698628e-04f, -2.03467556e-04f, - -2.46210106e-04f, -2.86570115e-04f, -3.24236629e-04f, -3.58912356e-04f, - -3.90340467e-04f, -4.18264108e-04f, -4.42494467e-04f, -4.62859927e-04f, - -4.79201468e-04f, -4.91408046e-04f, -4.99443631e-04f, -5.03240684e-04f, - -5.02785830e-04f, -4.98157139e-04f, -4.89378704e-04f, -4.76587119e-04f, - -4.59890738e-04f, -4.39477548e-04f, -4.15523795e-04f, -3.88271943e-04f, - -3.57945858e-04f, -3.24835243e-04f, -2.89240231e-04f, -2.51465546e-04f, - -2.11835672e-04f, -1.70699525e-04f, -1.28404932e-04f, -8.52894686e-05f, - -4.17402676e-05f, 1.86488651e-06f, 4.51992598e-05f, 8.79090103e-05f, - 1.29605134e-04f, 1.69965889e-04f, 2.08696862e-04f, 2.45456482e-04f, - 2.79955019e-04f, 3.11933102e-04f, 3.41137711e-04f, 3.67347210e-04f, - 3.90354753e-04f, 4.10008585e-04f, 4.26152950e-04f, 4.38673341e-04f, - 4.47495015e-04f, 4.52569681e-04f, 4.53871341e-04f, 4.51401451e-04f, - 4.45215411e-04f, 4.35379482e-04f, 4.22000265e-04f, 4.05191759e-04f, - 3.85119408e-04f, 3.61974661e-04f, 3.35944547e-04f, 3.07269134e-04f, - 2.76178449e-04f, 2.42964749e-04f, 2.07892199e-04f, 1.71254500e-04f, - 1.33357821e-04f, 9.45229736e-05f, 5.50557762e-05f, 1.52985188e-05f, - -2.44379242e-05f, -6.38226873e-05f, -1.02547166e-04f, -1.40299443e-04f, - -1.76768344e-04f, -2.11670093e-04f, -2.44743896e-04f, -2.75718512e-04f, - -3.04347022e-04f, -3.30418232e-04f, -3.53736841e-04f, -3.74109356e-04f, - -3.91381845e-04f, -4.05451309e-04f, -4.16191977e-04f, -4.23521256e-04f, - -4.27426658e-04f, -4.27869490e-04f, -4.24853386e-04f, -4.18413250e-04f, - -4.08611901e-04f, -3.95543058e-04f, -3.79292301e-04f, -3.60028810e-04f, - -3.37902489e-04f, -3.13061225e-04f, -2.85740223e-04f, -2.56168414e-04f, - -2.24556060e-04f, -1.91176716e-04f, -1.56272512e-04f, -1.20139890e-04f, - -8.30537602e-05f, -4.52873563e-05f, -7.15972468e-06f, 3.10302032e-05f, - 6.90244932e-05f, 1.06496284e-04f, 1.43158198e-04f, 1.78736255e-04f, - 2.12976701e-04f, 2.45591364e-04f, 2.76326944e-04f, 3.04976111e-04f, - 3.31299044e-04f, 3.55114336e-04f, 3.76239459e-04f, 3.94517329e-04f, - 4.09785367e-04f, 4.21936769e-04f, 4.30916344e-04f, 4.36588118e-04f, - 4.38939238e-04f, 4.37964002e-04f, 4.33639999e-04f, 4.26009328e-04f, - 4.15120198e-04f, 4.01062409e-04f, 3.83911295e-04f, 3.63806383e-04f, - 3.40873899e-04f, 3.15295099e-04f, 2.87229071e-04f, 2.56841831e-04f, - 2.24418352e-04f, 1.90132129e-04f, 1.54208573e-04f, 1.16966099e-04f, - 7.85931871e-05f, 3.94169348e-05f, -3.38391999e-07f, -4.03917338e-05f, - -8.04238542e-05f, -1.20226558e-04f, -1.59470944e-04f, -1.97919204e-04f, - -2.35294348e-04f, -2.71326824e-04f, -3.05778818e-04f, -3.38410918e-04f, - -3.69010644e-04f, -3.97338842e-04f, -4.23216844e-04f, -4.46466259e-04f, - -4.66912010e-04f, -4.84405189e-04f, -4.98827952e-04f, -5.10071530e-04f, - -5.18032256e-04f, -5.22640255e-04f, -5.23881412e-04f, -5.21676098e-04f, - -5.16039363e-04f, -5.06992324e-04f, -4.94563784e-04f, -4.78787143e-04f, - -4.59750953e-04f, -4.37555727e-04f, -4.12275557e-04f, -3.84054035e-04f, - -3.53037193e-04f, -3.19340180e-04f, -2.83173899e-04f, -2.44687364e-04f, - -2.04094207e-04f, -1.61583244e-04f, -1.17361228e-04f, -7.16348417e-05f, - -2.46348477e-05f, 2.33922164e-05f, 7.22418000e-05f, 1.21684779e-04f, - 1.71499457e-04f, 2.21438013e-04f, 2.71280140e-04f, 3.20801296e-04f, - 3.69771761e-04f, 4.18013477e-04f, 4.65284057e-04f, 5.11428788e-04f, - 5.56212641e-04f, 5.99478253e-04f, 6.41075472e-04f, 6.80832949e-04f, - 7.18609100e-04f, 7.54279009e-04f, 7.87742278e-04f, 8.18871508e-04f, - 8.47567713e-04f, 8.73805352e-04f, 8.97474148e-04f, 9.18529976e-04f, - 9.36959592e-04f, 9.52736827e-04f, 9.65854080e-04f, 9.76300491e-04f, - 9.84128104e-04f, 9.89353301e-04f, 9.92012052e-04f, 9.92188548e-04f, - 9.89909273e-04f, 9.85280796e-04f, 9.78335267e-04f, 9.69211001e-04f, - 9.58007113e-04f, 9.44804463e-04f, 9.29714666e-04f, 9.12874477e-04f, - 8.94419257e-04f, 8.74438162e-04f, 8.53087607e-04f, 8.30499113e-04f, - 8.06785316e-04f, 7.82049426e-04f, 7.56478510e-04f, 7.30171081e-04f, - 7.03266226e-04f, 6.75883876e-04f, 6.48150302e-04f, 6.20186681e-04f, - 5.92085453e-04f, 5.63985129e-04f, 5.35999572e-04f, 5.08203069e-04f, - 4.80689533e-04f, 4.53557971e-04f, 4.26884566e-04f, 4.00740812e-04f, - 3.75236819e-04f, 3.50353190e-04f, 3.26243777e-04f, 3.02893629e-04f, - 2.80331434e-04f, 2.58652176e-04f, 2.37852072e-04f, 2.17971034e-04f, - 1.99026311e-04f, 1.81011783e-04f, 1.63957631e-04f, 1.47844848e-04f, - 1.32718875e-04f, 1.18546172e-04f, 1.05256179e-04f, 9.29127235e-05f, - 8.14745498e-05f, 7.08726925e-05f, 6.11573299e-05f, 5.22357210e-05f, - 4.41150988e-05f, 3.67197275e-05f, 3.01315396e-05f, 2.41484723e-05f, - 1.88541281e-05f, 1.41686879e-05f, 1.00524473e-05f, 6.50288873e-06f, - 3.38594106e-06f, 7.66607975e-07f, -1.44302379e-06f, -3.26787635e-06f, - -4.78247665e-06f, -5.93452415e-06f, -6.76352831e-06f, -7.34758715e-06f, - -7.79331112e-06f, -7.91311421e-06f, -7.86244933e-06f, -7.86157694e-06f, - -7.64215127e-06f, -7.28290836e-06f, -6.81887853e-06f, -6.49158597e-06f, - -6.02222593e-06f, -5.26738395e-06f, -5.09005650e-06f, -1.73022428e-05f, + 0.00000000e+00f, 2.57738229e-05f, 2.28312635e-05f, 3.29301720e-05f, + 4.62792766e-05f, 6.28392813e-05f, 8.34298526e-05f, 1.09345366e-04f, + 1.40765613e-04f, 1.79085823e-04f, 2.25515927e-04f, 2.80686140e-04f, + 3.46440184e-04f, 4.24213060e-04f, 5.15679758e-04f, 6.22703072e-04f, + 7.47248606e-04f, 8.91648830e-04f, 1.05819097e-03f, 1.24970360e-03f, + 1.46896184e-03f, 1.71909900e-03f, 2.00352112e-03f, 2.32585325e-03f, + 2.69004589e-03f, 3.10009770e-03f, 3.56066709e-03f, 4.07650766e-03f, + 4.65266984e-03f, 5.29443820e-03f, 6.00761218e-03f, 6.79817892e-03f, + 7.67241666e-03f, 8.63705786e-03f, 9.69902462e-03f, 1.08655464e-02f, + 1.21441926e-02f, 1.35431147e-02f, 1.50703831e-02f, 1.67342248e-02f, + 1.85434518e-02f, 2.05073885e-02f, 2.26351995e-02f, 2.49359480e-02f, + 2.74195152e-02f, 3.00957201e-02f, 3.29746797e-02f, 3.60660148e-02f, + 3.93800865e-02f, 4.29269667e-02f, 4.67167355e-02f, 5.07595322e-02f, + 5.50652027e-02f, 5.96435730e-02f, 6.45041914e-02f, 6.96563995e-02f, + 7.51092741e-02f, 8.08713050e-02f, 8.69506086e-02f, 9.33550143e-02f, + 1.00091521e-01f, 1.07166524e-01f, 1.14585856e-01f, 1.22354427e-01f, + 1.30476332e-01f, 1.38954781e-01f, 1.47791858e-01f, 1.56988836e-01f, + 1.66545712e-01f, 1.76461162e-01f, 1.86732857e-01f, 1.97356887e-01f, + 2.08328235e-01f, 2.19640042e-01f, 2.31284315e-01f, 2.43251224e-01f, + 2.55529580e-01f, 2.68106380e-01f, 2.80966931e-01f, 2.94094913e-01f, + 3.07472157e-01f, 3.21078662e-01f, 3.34892936e-01f, 3.48891418e-01f, + 3.63048947e-01f, 3.77338339e-01f, 3.91731016e-01f, 4.06196375e-01f, + 4.20702327e-01f, 4.35214921e-01f, 4.49698829e-01f, 4.64117192e-01f, + 4.78431501e-01f, 4.92602156e-01f, 5.06588058e-01f, 5.20347244e-01f, + 5.33836291e-01f, 5.47011324e-01f, 5.59827304e-01f, 5.72238858e-01f, + 5.84199812e-01f, 5.95663881e-01f, 6.06584530e-01f, 6.16915479e-01f, + 6.26610227e-01f, 6.35623009e-01f, 6.43908415e-01f, 6.51422044e-01f, + 6.58120303e-01f, 6.63960857e-01f, 6.68902787e-01f, 6.72906783e-01f, + 6.75935360e-01f, 6.77953091e-01f, 6.78926764e-01f, 6.78825652e-01f, + 6.77621638e-01f, 6.75289589e-01f, 6.71807256e-01f, 6.67155714e-01f, + 6.61319462e-01f, 6.54286514e-01f, 6.46048652e-01f, 6.36601561e-01f, + 6.25944910e-01f, 6.14082478e-01f, 6.01022343e-01f, 5.86776774e-01f, + 5.71362584e-01f, 5.54800895e-01f, 5.37117377e-01f, 5.18342181e-01f, + 4.98509921e-01f, 4.77659701e-01f, 4.55834999e-01f, 4.33083714e-01f, + 4.09457911e-01f, 3.85013844e-01f, 3.59811788e-01f, 3.33915906e-01f, + 3.07393970e-01f, 2.80317366e-01f, 2.52760583e-01f, 2.24801315e-01f, + 1.96519969e-01f, 1.67999385e-01f, 1.39324761e-01f, 1.10583043e-01f, + 8.18628436e-02f, 5.32540056e-02f, 2.48471803e-02f, -3.26636649e-03f, + -3.09952967e-02f, -5.82485562e-02f, -8.49355930e-02f, -1.10967034e-01f, + -1.36254903e-01f, -1.60712966e-01f, -1.84257387e-01f, -2.06806776e-01f, + -2.28282907e-01f, -2.48610910e-01f, -2.67719669e-01f, -2.85542260e-01f, + -3.02016257e-01f, -3.17084071e-01f, -3.30693194e-01f, -3.42796661e-01f, + -3.53353212e-01f, -3.62327576e-01f, -3.69690653e-01f, -3.75419813e-01f, + -3.79499075e-01f, -3.81919103e-01f, -3.82677491e-01f, -3.81778733e-01f, + -3.79234384e-01f, -3.75063017e-01f, -3.69290152e-01f, -3.61948304e-01f, + -3.53076910e-01f, -3.42722096e-01f, -3.30936620e-01f, -3.17779633e-01f, + -3.03316514e-01f, -2.87618587e-01f, -2.70762756e-01f, -2.52831320e-01f, + -2.33911539e-01f, -2.14095259e-01f, -1.93478520e-01f, -1.72161169e-01f, + -1.50246285e-01f, -1.27839839e-01f, -1.05050082e-01f, -8.19871599e-02f, + -5.87624360e-02f, -3.54880619e-02f, -1.22764049e-02f, 1.07605263e-02f, + 3.35116028e-02f, 5.58671830e-02f, 7.77196474e-02f, 9.89638589e-02f, + 1.19497902e-01f, 1.39223354e-01f, 1.58046039e-01f, 1.75876344e-01f, + 1.92629792e-01f, 2.08227445e-01f, 2.22596376e-01f, 2.35669935e-01f, + 2.47388321e-01f, 2.57698757e-01f, 2.66555789e-01f, 2.73921619e-01f, + 2.79766215e-01f, 2.84067669e-01f, 2.86812042e-01f, 2.87993788e-01f, + 2.87615536e-01f, 2.85688209e-01f, 2.82230951e-01f, 2.77271050e-01f, + 2.70843807e-01f, 2.62992310e-01f, 2.53767274e-01f, 2.43226727e-01f, + 2.31435732e-01f, 2.18465973e-01f, 2.04395453e-01f, 1.89308050e-01f, + 1.73292986e-01f, 1.56444403e-01f, 1.38860793e-01f, 1.20644581e-01f, + 1.01901339e-01f, 8.27393357e-02f, 6.32689072e-02f, 4.36018454e-02f, + 2.38507071e-02f, 4.12822827e-03f, -1.54533490e-02f, -3.47829031e-02f, + -5.37510504e-02f, -7.22508561e-02f, -9.01782817e-02f, -1.07433017e-01f, + -1.23918854e-01f, -1.39544366e-01f, -1.54223358e-01f, -1.67875437e-01f, + -1.80426472e-01f, -1.91808926e-01f, -2.01962423e-01f, -2.10833914e-01f, + -2.18378136e-01f, -2.24557777e-01f, -2.29343716e-01f, -2.32715250e-01f, + -2.34660116e-01f, -2.35174592e-01f, -2.34263552e-01f, -2.31940408e-01f, + -2.28226909e-01f, -2.23153240e-01f, -2.16757610e-01f, -2.09086092e-01f, + -2.00192330e-01f, -1.90137237e-01f, -1.78988601e-01f, -1.66820607e-01f, + -1.53713424e-01f, -1.39752706e-01f, -1.25029052e-01f, -1.09637434e-01f, + -9.36766468e-02f, -7.72485626e-02f, -6.04577178e-02f, -4.34104123e-02f, + -2.62142727e-02f, -8.97736641e-03f, 8.19224333e-03f, 2.51873938e-02f, + 4.19023475e-02f, 5.82335223e-02f, 7.40800352e-02f, 8.93445817e-02f, + 1.03933643e-01f, 1.17758514e-01f, 1.30735488e-01f, 1.42786597e-01f, + 1.53840027e-01f, 1.63830572e-01f, 1.72700012e-01f, 1.80397490e-01f, + 1.86879846e-01f, 1.92111822e-01f, 1.96066362e-01f, 1.98724630e-01f, + 2.00076290e-01f, 2.00119400e-01f, 1.98860492e-01f, 1.96314478e-01f, + 1.92504587e-01f, 1.87462157e-01f, 1.81226343e-01f, 1.73844017e-01f, + 1.65369327e-01f, 1.55863248e-01f, 1.45393459e-01f, 1.34033511e-01f, + 1.21862692e-01f, 1.08965144e-01f, 9.54296309e-02f, 8.13487144e-02f, + 6.68182119e-02f, 5.19365830e-02f, 3.68042391e-02f, 2.15228293e-02f, + 6.19473181e-03f, -9.07786386e-03f, -2.41934219e-02f, -3.90518897e-02f, + -5.35552556e-02f, -6.76082701e-02f, -8.11190181e-02f, -9.39995712e-02f, + -1.06166560e-01f, -1.17541673e-01f, -1.28052194e-01f, -1.37631580e-01f, + -1.46219738e-01f, -1.53763473e-01f, -1.60216884e-01f, -1.65541564e-01f, + -1.69706914e-01f, -1.72690307e-01f, -1.74477196e-01f, -1.75061256e-01f, + -1.74444314e-01f, -1.72636437e-01f, -1.69655694e-01f, -1.65528167e-01f, + -1.60287634e-01f, -1.53975364e-01f, -1.46639831e-01f, -1.38336334e-01f, + -1.29126633e-01f, -1.19078480e-01f, -1.08265109e-01f, -9.67648277e-02f, + -8.46603321e-02f, -7.20381983e-02f, -5.89882902e-02f, -4.56030440e-02f, + -3.19769405e-02f, -1.82057654e-02f, -4.38589399e-03f, 9.38618442e-03f, + 2.30147873e-02f, 3.64055026e-02f, 4.94659847e-02f, 6.21064640e-02f, + 7.42404823e-02f, 8.57854026e-02f, 9.66630881e-02f, 1.06800214e-01f, + 1.16129062e-01f, 1.24587714e-01f, 1.32120704e-01f, 1.38679150e-01f, + 1.44221352e-01f, 1.48712781e-01f, 1.52126551e-01f, 1.54443364e-01f, + 1.55651872e-01f, 1.55748483e-01f, 1.54737594e-01f, 1.52631324e-01f, + 1.49449613e-01f, 1.45219867e-01f, 1.39976951e-01f, 1.33762695e-01f, + 1.26625732e-01f, 1.18621127e-01f, 1.09809835e-01f, 1.00258461e-01f, + 9.00385966e-02f, 7.92263112e-02f, 6.79017514e-02f, 5.61483242e-02f, + 4.40522753e-02f, 3.17019988e-02f, 1.91873411e-02f, 6.59909466e-03f, + -5.97180657e-03f, -1.84349074e-02f, -3.07007132e-02f, -4.26816417e-02f, + -5.42923896e-02f, -6.54506079e-02f, -7.60775609e-02f, -8.60985825e-02f, + -9.54437408e-02f, -1.04048142e-01f, -1.11852553e-01f, -1.18803711e-01f, + -1.24854813e-01f, -1.29965653e-01f, -1.34103041e-01f, -1.37241039e-01f, + -1.39360993e-01f, -1.40451792e-01f, -1.40509793e-01f, -1.39538986e-01f, + -1.37550804e-01f, -1.34564089e-01f, -1.30604905e-01f, -1.25706354e-01f, + -1.19908276e-01f, -1.13257015e-01f, -1.05804921e-01f, -9.76101298e-02f, + -8.87359691e-02f, -7.92505483e-02f, -6.92262319e-02f, -5.87391125e-02f, + -4.78684160e-02f, -3.66959289e-02f, -2.53053541e-02f, -1.37817249e-02f, + -2.21079428e-03f, 9.32167747e-03f, 2.07305160e-02f, 3.19316883e-02f, + 4.28430566e-02f, 5.33848786e-02f, 6.34804441e-02f, 7.30565856e-02f, + 8.20442897e-02f, 9.03790776e-02f, 9.80016231e-02f, 1.04858052e-01f, + 1.10900371e-01f, 1.16086840e-01f, 1.20382198e-01f, 1.23757969e-01f, + 1.26192651e-01f, 1.27671770e-01f, 1.28188137e-01f, 1.27741675e-01f, + 1.26339596e-01f, 1.23996183e-01f, 1.20732705e-01f, 1.16577307e-01f, + 1.11564669e-01f, 1.05735813e-01f, 9.91377187e-02f, 9.18230275e-02f, + 8.38495092e-02f, 7.52797839e-02f, 6.61806838e-02f, 5.66228090e-02f, + 4.66799931e-02f, 3.64286945e-02f, 2.59474847e-02f, 1.53163689e-02f, + 4.61622490e-03f, -6.07182197e-03f, -1.66669799e-02f, -2.70894704e-02f, + -3.72610297e-02f, -4.71056290e-02f, -5.65498931e-02f, -6.55237625e-02f, + -7.39609699e-02f, -8.17995235e-02f, -8.89821548e-02f, -9.54568508e-02f, + -1.01177041e-01f, -1.06102108e-01f, -1.10197566e-01f, -1.13435384e-01f, + -1.15794171e-01f, -1.17259227e-01f, -1.17822757e-01f, -1.17483887e-01f, + -1.16248548e-01f, -1.14129572e-01f, -1.11146430e-01f, -1.07325180e-01f, + -1.02698169e-01f, -9.73037750e-02f, -9.11861728e-02f, -8.43949139e-02f, + -7.69845152e-02f, -6.90140891e-02f, -6.05468325e-02f, -5.16495384e-02f, + -4.23921120e-02f, -3.28470026e-02f, -2.30885659e-02f, -1.31926321e-02f, + -3.23579390e-03f, 6.70506976e-03f, 1.65535306e-02f, 2.62340587e-02f, + 3.56726723e-02f, 4.47975155e-02f, 5.35393359e-02f, 6.18320891e-02f, + 6.96133711e-02f, 7.68249575e-02f, 8.34132047e-02f, 8.93294053e-02f, + 9.45302615e-02f, 9.89780776e-02f, 1.02641187e-01f, 1.05494003e-01f, + 1.07517350e-01f, 1.08698506e-01f, 1.09031319e-01f, 1.08516239e-01f, + 1.07160277e-01f, 1.04976947e-01f, 1.01986161e-01f, 9.82139821e-02f, + 9.36925268e-02f, 8.84595879e-02f, 8.25583806e-02f, 7.60371894e-02f, + 6.89489402e-02f, 6.13508561e-02f, 5.33039225e-02f, 4.48724192e-02f, + 3.61234643e-02f, 2.71263730e-02f, 1.79522593e-02f, 8.67332814e-03f, + -6.37560549e-04f, -9.90757623e-03f, -1.90643762e-02f, -2.80367915e-02f, + -3.67552453e-02f, -4.51524120e-02f, -5.31636754e-02f, -6.07276053e-02f, + -6.77865076e-02f, -7.42868063e-02f, -8.01794199e-02f, -8.54202472e-02f, + -8.99703936e-02f, -9.37965226e-02f, -9.68710320e-02f, -9.91723636e-02f, + -1.00685055e-01f, -1.01399924e-01f, -1.01314027e-01f, -1.00430752e-01f, + -9.87597996e-02f, -9.63169323e-02f, -9.31240228e-02f, -8.92087380e-02f, + -8.46043840e-02f, -7.93495604e-02f, -7.34879129e-02f, -6.70676790e-02f, + -6.01414310e-02f, -5.27655243e-02f, -4.49997042e-02f, -3.69066235e-02f, + -2.85513234e-02f, -2.00007483e-02f, -1.13231172e-02f, -2.58753819e-03f, + 6.13671174e-03f, 1.47805601e-02f, 2.32758739e-02f, 3.15558319e-02f, + 3.95555353e-02f, 4.72125037e-02f, 5.44671546e-02f, 6.12632712e-02f, + 6.75484439e-02f, 7.32744268e-02f, 7.83976489e-02f, 8.28793328e-02f, + 8.66860430e-02f, 8.97896293e-02f, 9.21678120e-02f, 9.38039664e-02f, + 9.46875290e-02f, 9.48139007e-02f, 9.41845125e-02f, 9.28068505e-02f, + 9.06942851e-02f, 8.78660470e-02f, 8.43469508e-02f, 8.01672994e-02f, + 7.53625613e-02f, 6.99730444e-02f, 6.40436219e-02f, 5.76234074e-02f, + 5.07651752e-02f, 4.35251559e-02f, 3.59623612e-02f, 2.81382801e-02f, + 2.01163116e-02f, 1.19611978e-02f, 3.73860852e-03f, -4.48544815e-03f, + -1.26451529e-02f, -2.06753686e-02f, -2.85122123e-02f, -3.60935203e-02f, + -4.33593322e-02f, -5.02524350e-02f, -5.67187066e-02f, -6.27076379e-02f, + -6.81726899e-02f, -7.30716527e-02f, -7.73669645e-02f, -8.10260558e-02f, + -8.40214980e-02f, -8.63313064e-02f, -8.79390729e-02f, -8.88340404e-02f, + -8.90112500e-02f, -8.84714905e-02f, -8.72213317e-02f, -8.52730391e-02f, + -8.26444808e-02f, -7.93588812e-02f, -7.54447661e-02f, -7.09355706e-02f, + -6.58695010e-02f, -6.02890903e-02f, -5.42408964e-02f, -4.77751478e-02f, + -4.09452872e-02f, -3.38075119e-02f, -2.64204001e-02f, -1.88443271e-02f, + -1.11410841e-02f, -3.37322640e-03f, 4.39625224e-03f, 1.21045621e-02f, + 1.96895966e-02f, 2.70903872e-02f, 3.42476479e-02f, 4.11042063e-02f, + 4.76055071e-02f, 5.36999666e-02f, 5.93395286e-02f, 6.44798549e-02f, + 6.90808373e-02f, 7.31067984e-02f, 7.65268692e-02f, 7.93151321e-02f, + 8.14508652e-02f, 8.29187318e-02f, 8.37087874e-02f, 8.38166939e-02f, + 8.32435667e-02f, 8.19961230e-02f, 8.00865044e-02f, 7.75321668e-02f, + 7.43557925e-02f, 7.05850463e-02f, 6.62523340e-02f, 6.13945573e-02f, + 5.60527587e-02f, 5.02718197e-02f, 4.41000820e-02f, 3.75889077e-02f, + 3.07923072e-02f, 2.37664500e-02f, 1.65692144e-02f, 9.25974471e-03f, + 1.89789131e-03f, -5.45616102e-03f, -1.27425262e-02f, -1.99019879e-02f, + -2.68765329e-02f, -3.36098353e-02f, -4.00476211e-02f, -4.61382887e-02f, + -5.18330845e-02f, -5.70866854e-02f, -6.18574768e-02f, -6.61078954e-02f, + -6.98047314e-02f, -7.29193713e-02f, -7.54280733e-02f, -7.73120518e-02f, + -7.85577193e-02f, -7.91567321e-02f, -7.91060672e-02f, -7.84079778e-02f, + -7.70700917e-02f, -7.51051477e-02f, -7.25310474e-02f, -6.93706386e-02f, + -6.56514718e-02f, -6.14056206e-02f, -5.66693718e-02f, -5.14829473e-02f, + -4.58901735e-02f, -3.99380331e-02f, -3.36763702e-02f, -2.71574389e-02f, + -2.04354475e-02f, -1.35661532e-02f, -6.60630727e-03f, 3.86646824e-04f, + 7.35520973e-03f, 1.42422280e-02f, 2.09913641e-02f, 2.75475284e-02f, + 3.38574221e-02f, 3.98698307e-02f, 4.55362181e-02f, 5.08109216e-02f, + 5.56516759e-02f, 6.00198762e-02f, 6.38809158e-02f, 6.72044395e-02f, + 6.99645791e-02f, 7.21402152e-02f, 7.37150597e-02f, 7.46778107e-02f, + 7.50222360e-02f, 7.47472565e-02f, 7.38568363e-02f, 7.23600417e-02f, + 7.02708761e-02f, 6.76082238e-02f, 6.43956576e-02f, 6.06611745e-02f, + 5.64370176e-02f, 5.17594495e-02f, 4.66682739e-02f, 4.12066978e-02f, + 3.54208167e-02f, 2.93593188e-02f, 2.30730217e-02f, 1.66144782e-02f, + 1.00375317e-02f, 3.39684147e-03f, -3.25251124e-03f, -9.85551584e-03f, + -1.63577490e-02f, -2.27056595e-02f, -2.88471998e-02f, -3.47320840e-02f, + -4.03123540e-02f, -4.55426353e-02f, -5.03805924e-02f, -5.47872529e-02f, + -5.87272646e-02f, -6.21693020e-02f, -6.50861771e-02f, -6.74551714e-02f, + -6.92581408e-02f, -7.04816306e-02f, -7.11171304e-02f, -7.11609040e-02f, + -7.06142341e-02f, -6.94832169e-02f, -6.77788399e-02f, -6.55167566e-02f, + -6.27172654e-02f, -5.94050389e-02f, -5.56089420e-02f, -5.13618140e-02f, + -4.67001077e-02f, -4.16636667e-02f, -3.62953408e-02f, -3.06405937e-02f, + -2.47472033e-02f, -1.86647681e-02f, -1.24443758e-02f, -6.13808338e-03f, + 2.01411978e-04f, 6.52133427e-03f, 1.27691320e-02f, 1.88930538e-02f, + 2.48424465e-02f, 3.05682613e-02f, 3.60234752e-02f, 4.11633315e-02f, + 4.59459248e-02f, 5.03323304e-02f, 5.42870835e-02f, 5.77784019e-02f, + 6.07783651e-02f, 6.32633288e-02f, 6.52139121e-02f, 6.66152616e-02f, + 6.74571587e-02f, 6.77340253e-02f, 6.74450634e-02f, 6.65941743e-02f, + 6.51898969e-02f, 6.32454230e-02f, 6.07783949e-02f, 5.78107648e-02f, + 5.43686030e-02f, 5.04819132e-02f, 4.61842717e-02f, 4.15126988e-02f, + 3.65071237e-02f, 3.12102820e-02f, 2.56671547e-02f, 1.99247189e-02f, + 1.40314897e-02f, 8.03710981e-03f, 1.99194113e-03f, -4.05328806e-03f, + -1.00480950e-02f, -1.59424027e-02f, -2.16871978e-02f, -2.72348278e-02f, + -3.25393096e-02f, -3.75569641e-02f, -4.22464745e-02f, -4.65694440e-02f, + -5.04906287e-02f, -5.39782262e-02f, -5.70041442e-02f, -5.95442285e-02f, + -6.15784134e-02f, -6.30910059e-02f, -6.40706329e-02f, -6.45104738e-02f, + -6.44081853e-02f, -6.37660587e-02f, -6.25908310e-02f, -6.08937512e-02f, + -5.86903214e-02f, -5.60003749e-02f, -5.28477193e-02f, -4.92599533e-02f, + -4.52683132e-02f, -4.09073183e-02f, -3.62145264e-02f, -3.12301828e-02f, + -2.59968465e-02f, -2.05591356e-02f, -1.49632303e-02f, -9.25651553e-03f, + -3.48724314e-03f, 2.29598571e-03f, 8.04450724e-03f, 1.37101015e-02f, + 1.92453689e-02f, 2.46040942e-02f, 2.97416606e-02f, 3.46154341e-02f, + 3.91850779e-02f, 4.34129264e-02f, 4.72642978e-02f, 5.07076760e-02f, + 5.37151661e-02f, 5.62625282e-02f, 5.83294584e-02f, 5.98998097e-02f, + 6.09615551e-02f, 6.15070547e-02f, 6.15329990e-02f, 6.10404716e-02f, + 6.00349135e-02f, 5.85260610e-02f, 5.65279401e-02f, 5.40585286e-02f, + 5.11398898e-02f, 4.77976785e-02f, 4.40611761e-02f, 3.99628438e-02f, + 3.55380989e-02f, 3.08251587e-02f, 2.58644088e-02f, 2.06983747e-02f, + 1.53710739e-02f, 9.92792727e-03f, 4.41510532e-03f, -1.12066218e-03f, + -6.63261302e-03f, -1.20742595e-02f, -1.73998395e-02f, -2.25646758e-02f, + -2.75255332e-02f, -3.22410401e-02f, -3.66719333e-02f, -4.07815241e-02f, + -4.45358322e-02f, -4.79040656e-02f, -5.08586430e-02f, -5.33756630e-02f, + -5.54348806e-02f, -5.70200545e-02f, -5.81188903e-02f, -5.87233379e-02f, + -5.88294685e-02f, -5.84376077e-02f, -5.75522891e-02f, -5.61822154e-02f, + -5.43401522e-02f, -5.20428669e-02f, -4.93108603e-02f, -4.61683834e-02f, + -4.26429808e-02f, -3.87654608e-02f, -3.45695151e-02f, -3.00914369e-02f, + -2.53698514e-02f, -2.04453289e-02f, -1.53600900e-02f, -1.01575989e-02f, + -4.88223250e-03f, 4.21140683e-04f, 5.70749346e-03f, 1.09320714e-02f, + 1.60507423e-02f, 2.10203384e-02f, 2.57991258e-02f, 3.03469891e-02f, + 3.46259681e-02f, 3.86003807e-02f, 4.22372656e-02f, 4.55065977e-02f, + 4.83814916e-02f, 5.08385122e-02f, 5.28578492e-02f, 5.44233532e-02f, + 5.55228686e-02f, 5.61481799e-02f, 5.62951181e-02f, 5.59635641e-02f, + 5.51575059e-02f, 5.38848971e-02f, 5.21577067e-02f, 4.99916630e-02f, + 4.74062222e-02f, 4.44243762e-02f, 4.10723813e-02f, 3.73796334e-02f, + 3.33783371e-02f, 2.91031994e-02f, 2.45912699e-02f, 1.98814471e-02f, + 1.50142456e-02f, 1.00314492e-02f, 4.97569860e-03f, -1.09829558e-04f, + -5.18180429e-03f, -1.01971104e-02f, -1.51132009e-02f, -1.98884909e-02f, + -2.44826770e-02f, -2.88570632e-02f, -3.29749774e-02f, -3.68019407e-02f, + -4.03060834e-02f, -4.34583201e-02f, -4.62326394e-02f, -4.86063256e-02f, + -5.05600942e-02f, -5.20783017e-02f, -5.31490320e-02f, -5.37642601e-02f, + -5.39197625e-02f, -5.36153455e-02f, -5.28546569e-02f, -5.16452744e-02f, + -4.99985519e-02f, -4.79295687e-02f, -4.54569365e-02f, -4.26027299e-02f, + -3.93921382e-02f, -3.58534069e-02f, -3.20174780e-02f, -2.79177659e-02f, + -2.35898897e-02f, -1.90712489e-02f, -1.44009108e-02f, -9.61905551e-03f, + -4.76675190e-03f, 1.14429175e-04f, 4.98275197e-03f, 9.79667060e-03f, + 1.45152450e-02f, 1.90984000e-02f, 2.35073004e-02f, 2.77047166e-02f, + 3.16552879e-02f, 3.53258717e-02f, 3.86857329e-02f, 4.17069009e-02f, + 4.43643859e-02f, 4.66362841e-02f, 4.85040977e-02f, 4.99528118e-02f, + 5.09710058e-02f, 5.15509899e-02f, 5.16888143e-02f, 5.13843182e-02f, + 5.06411841e-02f, 4.94667221e-02f, 4.78719944e-02f, 4.58716454e-02f, + 4.34836680e-02f, 4.07294565e-02f, 3.76333478e-02f, 3.42226856e-02f, + 3.05273460e-02f, 2.65796084e-02f, 2.24138588e-02f, 1.80662406e-02f, + 1.35743725e-02f, 8.97708082e-03f, 4.31392223e-03f, -3.74997817e-04f, + -5.04949488e-03f, -9.66954442e-03f, -1.41957488e-02f, -1.85895102e-02f, + -2.28135517e-02f, -2.68320150e-02f, -3.06110148e-02f, -3.41186844e-02f, + -3.73255948e-02f, -4.02049849e-02f, -4.27328870e-02f, -4.48884826e-02f, + -4.66541442e-02f, -4.80155982e-02f, -4.89621376e-02f, -4.94865911e-02f, + -4.95854066e-02f, -4.92587543e-02f, -4.85103568e-02f, -4.73476557e-02f, + -4.57815259e-02f, -4.38263403e-02f, -4.14997758e-02f, -3.88226280e-02f, + -3.58186618e-02f, -3.25144195e-02f, -2.89389208e-02f, -2.51234952e-02f, + -2.11014173e-02f, -1.69076786e-02f, -1.25787051e-02f, -8.15193430e-03f, + -3.66561199e-03f, 8.41612730e-04f, 5.33096998e-03f, 9.76396982e-03f, + 1.41026633e-02f, 1.83099747e-02f, 2.23500907e-02f, 2.61886650e-02f, + 2.97931947e-02f, 3.31332165e-02f, 3.61806686e-02f, 3.89099840e-02f, + 4.12984711e-02f, 4.33263397e-02f, 4.49769909e-02f, 4.62370906e-02f, + 4.70967034e-02f, 4.75493101e-02f, 4.75919973e-02f, 4.72252655e-02f, + 4.64532495e-02f, 4.52834651e-02f, 4.37268913e-02f, 4.17977928e-02f, + 3.95136051e-02f, 3.68947746e-02f, 3.39645897e-02f, 3.07489848e-02f, + 2.72762475e-02f, 2.35768654e-02f, 1.96831666e-02f, 1.56291178e-02f, + 1.14499678e-02f, 7.18199244e-03f, 2.86208797e-03f, -1.47237323e-03f, + -5.78408986e-03f, -1.00359644e-02f, -1.41915102e-02f, -1.82151627e-02f, + -2.20725868e-02f, -2.57308876e-02f, -2.91590343e-02f, -3.23279893e-02f, + -3.52110559e-02f, -3.77840233e-02f, -4.00254088e-02f, -4.19166660e-02f, + -4.34422559e-02f, -4.45898688e-02f, -4.53504693e-02f, -4.57183342e-02f, + -4.56912080e-02f, -4.52701881e-02f, -4.44598082e-02f, -4.32679041e-02f, + -4.17056221e-02f, -3.97872721e-02f, -3.75301757e-02f, -3.49545539e-02f, + -3.20833642e-02f, -2.89420199e-02f, -2.55582198e-02f, -2.19616927e-02f, + -1.81839563e-02f, -1.42580179e-02f, -1.02180663e-02f, -6.09921811e-03f, + -1.93723037e-03f, 2.23190874e-03f, 6.37220013e-03f, 1.04479531e-02f, + 1.44241290e-02f, 1.82666389e-02f, 2.19425802e-02f, 2.54206248e-02f, + 2.86711138e-02f, 3.16665395e-02f, 3.43815887e-02f, 3.67934228e-02f, + 3.88819070e-02f, 4.06296963e-02f, 4.20224632e-02f, 4.30489649e-02f, + 4.37011440e-02f, 4.39741987e-02f, 4.38666105e-02f, 4.33801492e-02f, + 4.25198834e-02f, 4.12940568e-02f, 3.97140893e-02f, 3.77944134e-02f, + 3.55523815e-02f, 3.30080847e-02f, 3.01842181e-02f, 2.71057696e-02f, + 2.37999951e-02f, 2.02958932e-02f, 1.66242890e-02f, 1.28172569e-02f, + 8.90804027e-03f, 4.93068765e-03f, 9.19732115e-04f, -3.09003828e-03f, + -7.06392168e-03f, -1.09676549e-02f, -1.47675919e-02f, -1.84310619e-02f, + -2.19267124e-02f, -2.52246331e-02f, -2.82967223e-02f, -3.11168905e-02f, + -3.36612820e-02f, -3.59084899e-02f, -3.78397204e-02f, -3.94389199e-02f, + -4.06930177e-02f, -4.15918929e-02f, -4.21285586e-02f, -4.22991498e-02f, + -4.21030212e-02f, -4.15426637e-02f, -4.06237373e-02f, -3.93549944e-02f, + -3.77482044e-02f, -3.58180208e-02f, -3.35818746e-02f, -3.10598205e-02f, + -2.82743551e-02f, -2.52501753e-02f, -2.20140228e-02f, -1.85944012e-02f, + -1.50213539e-02f, -1.13261978e-02f, -7.54120713e-03f, -3.69942331e-03f, + 1.65761761e-04f, 4.02077827e-03f, 7.83220364e-03f, 1.15671267e-02f, + 1.51932941e-02f, 1.86795257e-02f, 2.19958878e-02f, 2.51139844e-02f, + 2.80072326e-02f, 3.06510161e-02f, 3.30229521e-02f, 3.51030581e-02f, + 3.68739519e-02f, 3.83209200e-02f, 3.94321479e-02f, 4.01987038e-02f, + 4.06147205e-02f, 4.06773379e-02f, 4.03868010e-02f, 3.97463905e-02f, + 3.87624502e-02f, 3.74442613e-02f, 3.58040217e-02f, 3.38566274e-02f, + 3.16197104e-02f, 2.91132913e-02f, 2.63597535e-02f, 2.33834951e-02f, + 2.02109048e-02f, 1.68699197e-02f, 1.33899369e-02f, 9.80149436e-03f, + 6.13596978e-03f, 2.42540672e-03f, -1.29791566e-03f, -5.00159520e-03f, + -8.65350380e-03f, -1.22220366e-02f, -1.56763640e-02f, -1.89867381e-02f, + -2.21247040e-02f, -2.50633577e-02f, -2.77776392e-02f, -3.02443926e-02f, + -3.24427741e-02f, -3.43542347e-02f, -3.59627985e-02f, -3.72551425e-02f, + -3.82207294e-02f, -3.88518854e-02f, -3.91438407e-02f, -3.90947976e-02f, + -3.87059356e-02f, -3.79813919e-02f, -3.69281514e-02f, -3.55561727e-02f, + -3.38780082e-02f, -3.19089538e-02f, -2.96667755e-02f, -2.71715529e-02f, + -2.44455713e-02f, -2.15130067e-02f, -1.83998712e-02f, -1.51336016e-02f, + -1.17429607e-02f, -8.25773631e-03f, -4.70843717e-03f, -1.12611141e-03f, + 2.45802887e-03f, 6.01275162e-03f, 9.50720826e-03f, 1.29111118e-02f, + 1.61950221e-02f, 1.93306375e-02f, 2.22909758e-02f, 2.50506401e-02f, + 2.75860615e-02f, 2.98756376e-02f, 3.18999914e-02f, 3.36420581e-02f, + 3.50872846e-02f, 3.62237568e-02f, 3.70422138e-02f, 3.75362558e-02f, + 3.77022507e-02f, 3.75395252e-02f, 3.70501413e-02f, 3.62391403e-02f, + 3.51142523e-02f, 3.36859539e-02f, 3.19673834e-02f, 2.99741015e-02f, + 2.77241155e-02f, 2.52375586e-02f, 2.25366390e-02f, 1.96453479e-02f, + 1.65892764e-02f, 1.33954509e-02f, 1.00919807e-02f, 6.70792095e-03f, + 3.27291818e-03f, -1.82973294e-04f, -3.62955472e-03f, -7.03682264e-03f, + -1.03751742e-02f, -1.36156254e-02f, -1.67301419e-02f, -1.96918703e-02f, + -2.24752801e-02f, -2.50565117e-02f, -2.74134928e-02f, -2.95261426e-02f, + -3.13765953e-02f, -3.29492690e-02f, -3.42310321e-02f, -3.52113800e-02f, + -3.58823726e-02f, -3.62388683e-02f, -3.62784198e-02f, -3.60013804e-02f, + -3.54108451e-02f, -3.45126668e-02f, -3.33153418e-02f, -3.18299584e-02f, + -3.00701260e-02f, -2.80517892e-02f, -2.57931374e-02f, -2.33143863e-02f, + -2.06376424e-02f, -1.77867508e-02f, -1.47868963e-02f, -1.16646556e-02f, + -8.44750940e-03f, -5.16378838e-03f, -1.84226820e-03f, 1.48794674e-03f, + 4.79778745e-03f, 8.05836223e-03f, 1.12413416e-02f, 1.43190753e-02f, + 1.72649338e-02f, 2.00534760e-02f, 2.26606759e-02f, 2.50641770e-02f, + 2.72434093e-02f, 2.91798405e-02f, 3.08570869e-02f, 3.22610538e-02f, + 3.33800775e-02f, 3.42050023e-02f, 3.47292410e-02f, 3.49489094e-02f, + 3.48627120e-02f, 3.44720896e-02f, 3.37811019e-02f, 3.27964643e-02f, + 3.15273982e-02f, 2.99856201e-02f, 2.81852138e-02f, 2.61424498e-02f, + 2.38757347e-02f, 2.14053475e-02f, 1.87533476e-02f, 1.59433058e-02f, + 1.30001119e-02f, 9.94981546e-03f, 6.81929403e-03f, 3.63610142e-03f, + 4.28194737e-04f, -2.77634627e-03f, -5.94948890e-03f, -9.06356635e-03f, + -1.20914729e-02f, -1.50068986e-02f, -1.77846186e-02f, -2.04006305e-02f, + -2.28323995e-02f, -2.50590554e-02f, -2.70615857e-02f, -2.88229378e-02f, + -3.03282395e-02f, -3.15648981e-02f, -3.25226545e-02f, -3.31937480e-02f, + -3.35729235e-02f, -3.36575141e-02f, -3.34474213e-02f, -3.29451007e-02f, + -3.21556137e-02f, -3.10864769e-02f, -2.97476700e-02f, -2.81514985e-02f, + -2.63124688e-02f, -2.42472323e-02f, -2.19743519e-02f, -1.95141654e-02f, + -1.68886307e-02f, -1.41210735e-02f, -1.12360633e-02f, -8.25907766e-03f, + -5.21640280e-03f, -2.13480364e-03f, 9.58636625e-04f, 4.03679668e-03f, + 7.07279449e-03f, 1.00401133e-02f, 1.29129091e-02f, 1.56662400e-02f, + 1.82762701e-02f, 2.07204296e-02f, 2.29776718e-02f, 2.50286615e-02f, + 2.68558574e-02f, 2.84437511e-02f, 2.97789600e-02f, 3.08503332e-02f, + 3.16490674e-02f, 3.21687669e-02f, 3.24054635e-02f, 3.23577221e-02f, + 3.20265687e-02f, 3.14155519e-02f, 3.05305894e-02f, 2.93800783e-02f, + 2.79746552e-02f, 2.63272236e-02f, 2.44527049e-02f, 2.23680618e-02f, + 2.00919975e-02f, 1.76449192e-02f, 1.50485988e-02f, 1.23261499e-02f, + 9.50171139e-03f, 6.60026650e-03f, 3.64740367e-03f, 6.69130719e-04f, + -2.30836384e-03f, -5.25901398e-03f, -8.15698453e-03f, -1.09769803e-02f, + -1.36944604e-02f, -1.62858144e-02f, -1.87285884e-02f, -2.10016919e-02f, + -2.30855512e-02f, -2.49623180e-02f, -2.66159983e-02f, -2.80325244e-02f, + -2.92000656e-02f, -3.01088544e-02f, -3.07515454e-02f, -3.11230425e-02f, + -3.12207134e-02f, -3.10442642e-02f, -3.05958739e-02f, -2.98800667e-02f, + -2.89037056e-02f, -2.76759017e-02f, -2.62080378e-02f, -2.45134730e-02f, + -2.26075902e-02f, -2.05075661e-02f, -1.82322785e-02f, -1.58020520e-02f, + -1.32385765e-02f, -1.05646147e-02f, -7.80391393e-03f, -4.98087115e-03f, + -2.12039991e-03f, 7.52293928e-04f, 3.61198449e-03f, 6.43358111e-03f, + 9.19241842e-03f, 1.18644039e-02f, 1.44262640e-02f, 1.68557443e-02f, + 1.91318358e-02f, 2.12348654e-02f, 2.31467521e-02f, 2.48511451e-02f, + 2.63335090e-02f, 2.75813428e-02f, 2.85841744e-02f, 2.93337504e-02f, + 2.98240644e-02f, 3.00513466e-02f, 3.00142022e-02f, 2.97135260e-02f, + 2.91525594e-02f, 2.83367887e-02f, 2.72739123e-02f, 2.59738760e-02f, + 2.44485621e-02f, 2.27119106e-02f, 2.07795919e-02f, 1.86690808e-02f, + 1.63992592e-02f, 1.39904260e-02f, 1.14640690e-02f, 8.84264758e-03f, + 6.14939507e-03f, 3.40812526e-03f, 6.43054198e-04f, -2.12146966e-03f, + -4.86117838e-03f, -7.55201827e-03f, -1.01704721e-02f, -1.26936743e-02f, + -1.50996598e-02f, -1.73675327e-02f, -1.94776873e-02f, -2.14119119e-02f, + -2.31535849e-02f, -2.46878274e-02f, -2.60016123e-02f, -2.70838807e-02f, + -2.79256152e-02f, -2.85199737e-02f, -2.88622578e-02f, -2.89500170e-02f, + -2.87830391e-02f, -2.83633782e-02f, -2.76952574e-02f, -2.67851241e-02f, + -2.56414895e-02f, -2.42749591e-02f, -2.26980211e-02f, -2.09250117e-02f, + -1.89719767e-02f, -1.68564622e-02f, -1.45974790e-02f, -1.22151551e-02f, + -9.73077489e-03f, -7.16636971e-03f, -4.54471089e-03f, -1.88895556e-03f, + 7.77421152e-04f, 3.43097465e-03f, 6.04836823e-03f, 8.60665740e-03f, + 1.10834931e-02f, 1.34572307e-02f, 1.57072091e-02f, 1.78139437e-02f, + 1.97591848e-02f, 2.15261896e-02f, 2.30998051e-02f, 2.44666019e-02f, + 2.56150329e-02f, 2.65354493e-02f, 2.72202868e-02f, 2.76640272e-02f, + 2.78632920e-02f, 2.78168878e-02f, 2.75257711e-02f, 2.69930474e-02f, + 2.62239398e-02f, 2.52257420e-02f, 2.40077502e-02f, 2.25811821e-02f, + 2.09590651e-02f, 1.91560968e-02f, 1.71885831e-02f, 1.50741718e-02f, + 1.28318640e-02f, 1.04816157e-02f, 8.04442313e-03f, 5.54190051e-03f, + 2.99625439e-03f, 4.29964517e-04f, -2.13427934e-03f, -4.67394770e-03f, + -7.16668999e-03f, -9.59070046e-03f, -1.19247527e-02f, -1.41485167e-02f, + -1.62426221e-02f, -1.81889386e-02f, -1.99706517e-02f, -2.15724419e-02f, + -2.29805917e-02f, -2.41831321e-02f, -2.51699460e-02f, -2.59328014e-02f, + -2.64654988e-02f, -2.67638473e-02f, -2.68257457e-02f, -2.66511933e-02f, + -2.62422396e-02f, -2.56030378e-02f, -2.47397026e-02f, -2.36603803e-02f, + -2.23750178e-02f, -2.08954572e-02f, -1.92351291e-02f, -1.74091057e-02f, + -1.54338252e-02f, -1.33270371e-02f, -1.11076012e-02f, -8.79532116e-03f, + -6.41081337e-03f, -3.97522070e-03f, -1.51016205e-03f, 9.62627810e-04f, + 3.42126524e-03f, 5.84416900e-03f, 8.21003607e-03f, 1.04981418e-02f, + 1.26884979e-02f, 1.47620168e-02f, 1.67006743e-02f, 1.84877071e-02f, + 2.01076631e-02f, 2.15466723e-02f, 2.27924559e-02f, 2.38344391e-02f, + 2.46639202e-02f, 2.52740508e-02f, 2.56599603e-02f, 2.58187383e-02f, + 2.57495061e-02f, 2.54533889e-02f, 2.49335063e-02f, 2.41949679e-02f, + 2.32447443e-02f, 2.20917330e-02f, 2.07465369e-02f, 1.92214548e-02f, + 1.75303581e-02f, 1.56885248e-02f, 1.37125582e-02f, 1.16201731e-02f, + 9.43010247e-03f, 7.16186261e-03f, 4.83567361e-03f, 2.47215188e-03f, + 9.22473038e-05f, -2.28300123e-03f, -4.63266587e-03f, -6.93607706e-03f, + -9.17298978e-03f, -1.13238544e-02f, -1.33698760e-02f, -1.52932430e-02f, + -1.70772680e-02f, -1.87065128e-02f, -2.01669821e-02f, -2.14461452e-02f, + -2.25331284e-02f, -2.34188021e-02f, -2.40957864e-02f, -2.45585844e-02f, + -2.48036252e-02f, -2.48292044e-02f, -2.46356329e-02f, -2.42250842e-02f, + -2.36017013e-02f, -2.27714652e-02f, -2.17421665e-02f, -2.05233690e-02f, + -1.91262271e-02f, -1.75635217e-02f, -1.58493869e-02f, -1.39993165e-02f, + -1.20299498e-02f, -9.95894696e-03f, -7.80483551e-03f, -5.58678574e-03f, + -3.32457334e-03f, -1.03821621e-03f, 1.25199524e-03f, 3.52587224e-03f, + 5.76334794e-03f, 7.94474634e-03f, 1.00509704e-02f, 1.20635647e-02f, + 1.39649989e-02f, 1.57386972e-02f, 1.73693364e-02f, 1.88428192e-02f, + 2.01465043e-02f, 2.12692517e-02f, 2.22015935e-02f, 2.29357192e-02f, + 2.34655795e-02f, 2.37870006e-02f, 2.38975984e-02f, 2.37969007e-02f, + 2.34862414e-02f, 2.29689158e-02f, 2.22499000e-02f, 2.13360637e-02f, + 2.02358982e-02f, 1.89595527e-02f, 1.75187187e-02f, 1.59264857e-02f, + 1.41973055e-02f, 1.23467171e-02f, 1.03913879e-02f, 8.34881663e-03f, + 6.23727188e-03f, 4.07554898e-03f, 1.88285221e-03f, -3.21348688e-04f, + -2.51757106e-03f, -4.68641464e-03f, -6.80879709e-03f, -8.86604146e-03f, + -1.08401587e-02f, -1.27138656e-02f, -1.44708646e-02f, -1.60958743e-02f, + -1.75748613e-02f, -1.88950773e-02f, -2.00452523e-02f, -2.10155784e-02f, + -2.17979173e-02f, -2.23857954e-02f, -2.27744512e-02f, -2.29609363e-02f, + -2.29440496e-02f, -2.27244286e-02f, -2.23044963e-02f, -2.16883864e-02f, + -2.08820436e-02f, -1.98930467e-02f, -1.87304962e-02f, -1.74051536e-02f, + -1.59290505e-02f, -1.43156476e-02f, -1.25794710e-02f, -1.07361870e-02f, + -8.80234289e-03f, -6.79523707e-03f, -4.73276287e-03f, -2.63333832e-03f, + -5.15560833e-04f, 1.60177115e-03f, 3.69994169e-03f, 5.76046713e-03f, + 7.76518011e-03f, 9.69651950e-03f, 1.15375588e-02f, 1.32722403e-02f, + 1.48854641e-02f, 1.63632477e-02f, 1.76928345e-02f, 1.88628539e-02f, + 1.98632836e-02f, 2.06857078e-02f, 2.13232370e-02f, 2.17707035e-02f, + 2.20245710e-02f, 2.20830689e-02f, 2.19461035e-02f, 2.16153999e-02f, + 2.10942875e-02f, 2.03878531e-02f, 1.95027602e-02f, 1.84472879e-02f, + 1.72311466e-02f, 1.58654896e-02f, 1.43627402e-02f, 1.27365006e-02f, + 1.10014956e-02f, 9.17326248e-03f, 7.26826777e-03f, 5.30349659e-03f, + 3.29647574e-03f, 1.26509823e-03f, -7.72666021e-04f, -2.79870526e-03f, + -4.79517508e-03f, -6.74446397e-03f, -8.62941284e-03f, -1.04335350e-02f, + -1.21410266e-02f, -1.37370298e-02f, -1.52076630e-02f, -1.65402345e-02f, + -1.77232647e-02f, -1.87466345e-02f, -1.96016877e-02f, -2.02812328e-02f, + -2.07796995e-02f, -2.10930662e-02f, -2.12190342e-02f, -2.11569310e-02f, + -2.09077424e-02f, -2.04741102e-02f, -1.98603315e-02f, -1.90722574e-02f, + -1.81172824e-02f, -1.70042570e-02f, -1.57433882e-02f, -1.43462276e-02f, + -1.28254403e-02f, -1.11947772e-02f, -9.46895912e-03f, -7.66347070e-03f, + -5.79450864e-03f, -3.87873939e-03f, -1.93323810e-03f, 2.46644329e-05f, + 1.97765804e-03f, 3.90841509e-03f, 5.79993335e-03f, 7.63553849e-03f, + 9.39912349e-03f, 1.10752347e-02f, 1.26492655e-02f, 1.41074757e-02f, + 1.54373004e-02f, 1.66272192e-02f, 1.76670438e-02f, 1.85479358e-02f, + 1.92624646e-02f, 1.98047255e-02f, 2.01703008e-02f, 2.03564026e-02f, + 2.03618038e-02f, 2.01868907e-02f, 1.98336400e-02f, 1.93056297e-02f, + 1.86079226e-02f, 1.77471409e-02f, 1.67312987e-02f, 1.55697372e-02f, + 1.42731251e-02f, 1.28532513e-02f, 1.13230121e-02f, 9.69619751e-03f, + 7.98747524e-03f, 6.21217610e-03f, 4.38617553e-03f, 2.52576378e-03f, + 6.47513831e-04f, -1.23187933e-03f, -3.09578918e-03f, -4.92771934e-03f, + -6.71155785e-03f, -8.43156989e-03f, -1.00726980e-02f, -1.16206095e-02f, + -1.30617878e-02f, -1.43837508e-02f, -1.55750228e-02f, -1.66254535e-02f, + -1.75260165e-02f, -1.82691095e-02f, -1.88485262e-02f, -1.92595629e-02f, + -1.94989485e-02f, -1.95649969e-02f, -1.94575478e-02f, -1.91779692e-02f, + -1.87291588e-02f, -1.81155240e-02f, -1.73428622e-02f, -1.64184497e-02f, + -1.53508419e-02f, -1.41498176e-02f, -1.28263924e-02f, -1.13925480e-02f, + -9.86129571e-03f, -8.24642962e-03f, -6.56245985e-03f, -4.82449803e-03f, + -3.04804665e-03f, -1.24899186e-03f, 5.56712448e-04f, 2.35302268e-03f, + 4.12404102e-03f, 5.85413883e-03f, 7.52808495e-03f, 9.13118114e-03f, + 1.06493773e-02f, 1.20694285e-02f, 1.33790131e-02f, 1.45667599e-02f, + 1.56224667e-02f, 1.65370727e-02f, 1.73028687e-02f, 1.79133659e-02f, + 1.83636049e-02f, 1.86499152e-02f, 1.87702262e-02f, 1.87238401e-02f, + 1.85116123e-02f, 1.81358025e-02f, 1.76001712e-02f, 1.69098379e-02f, + 1.60713269e-02f, 1.50924404e-02f, 1.39821625e-02f, 1.27507290e-02f, + 1.14093145e-02f, 9.97008458e-03f, 8.44606386e-03f, 6.85095093e-03f, + 5.19909294e-03f, 3.50524621e-03f, 1.78455190e-03f, 5.23496112e-05f, + -1.67599378e-03f, -3.38516088e-03f, -5.06003030e-03f, -6.68585576e-03f, + -8.24832082e-03f, -9.73374187e-03f, -1.11291228e-02f, -1.24223220e-02f, + -1.36021312e-02f, -1.46583598e-02f, -1.55819659e-02f, -1.63650578e-02f, + -1.70010772e-02f, -1.74847152e-02f, -1.78120952e-02f, -1.79806821e-02f, + -1.79893879e-02f, -1.78385292e-02f, -1.75298522e-02f, -1.70664857e-02f, + -1.64529222e-02f, -1.56949858e-02f, -1.47997517e-02f, -1.37755159e-02f, + -1.26316843e-02f, -1.13787184e-02f, -1.00279612e-02f, -8.59169598e-03f, + -7.08284107e-03f, -5.51497784e-03f, -3.90215899e-03f, -2.25882049e-03f, + -5.99578638e-04f, 1.06075048e-03f, 2.70749024e-03f, 4.32601367e-03f, + 5.90209087e-03f, 7.42179142e-03f, 8.87180147e-03f, 1.02394295e-02f, + 1.15127513e-02f, 1.26806976e-02f, 1.37331768e-02f, 1.46611542e-02f, + 1.54567137e-02f, 1.61130908e-02f, 1.66248644e-02f, 1.69878247e-02f, + 1.71991520e-02f, 1.72573319e-02f, 1.71622504e-02f, 1.69151533e-02f, + 1.65185918e-02f, 1.59764761e-02f, 1.52940069e-02f, 1.44775476e-02f, + 1.35347439e-02f, 1.24742225e-02f, 1.13057588e-02f, 1.00399437e-02f, + 8.68828286e-03f, 7.26299632e-03f, 5.77689293e-03f, 4.24335301e-03f, + 2.67608005e-03f, 1.08909445e-03f, -5.03504096e-04f, -2.08753398e-03f, + -3.64897310e-03f, -5.17400648e-03f, -6.64921573e-03f, -8.06161447e-03f, + -9.39882273e-03f, -1.06491934e-02f, -1.18018231e-02f, -1.28467447e-02f, + -1.37749479e-02f, -1.45785068e-02f, -1.52505941e-02f, -1.57855954e-02f, + -1.61790941e-02f, -1.64279942e-02f, -1.65304126e-02f, -1.64858700e-02f, + -1.62950933e-02f, -1.59601657e-02f, -1.54844596e-02f, -1.48725186e-02f, + -1.41301516e-02f, -1.32642739e-02f, -1.22829116e-02f, -1.11950413e-02f, + -1.00106329e-02f, -8.74042472e-03f, -7.39593243e-03f, -5.98926755e-03f, + -4.53309924e-03f, -3.04046635e-03f, -1.52472960e-03f, 6.00928259e-07f, + 1.52194755e-03f, 3.02583848e-03f, 4.49892816e-03f, 5.92824223e-03f, + 7.30119202e-03f, 8.60573140e-03f, 9.83048696e-03f, 1.09647391e-02f, + 1.19986831e-02f, 1.29234016e-02f, 1.37309351e-02f, 1.44144670e-02f, + 1.49682002e-02f, 1.53875953e-02f, 1.56692643e-02f, 1.58110475e-02f, + 1.58120917e-02f, 1.56726915e-02f, 1.53945244e-02f, 1.49803712e-02f, + 1.44342905e-02f, 1.37614814e-02f, 1.29682165e-02f, 1.20618964e-02f, + 1.10508386e-02f, 9.94431276e-03f, 8.75238815e-03f, 7.48587143e-03f, + 6.15624020e-03f, 4.77541534e-03f, 3.35583886e-03f, 1.91020436e-03f, + 4.51421254e-04f, -1.00751978e-03f, -2.45365701e-03f, -3.87418456e-03f, + -5.25654068e-03f, -6.58854736e-03f, -7.85852969e-03f, -9.05533440e-03f, + -1.01685578e-02f, -1.11885037e-02f, -1.21063745e-02f, -1.29142921e-02f, + -1.36053355e-02f, -1.41737244e-02f, -1.46146963e-02f, -1.49246914e-02f, + -1.51012942e-02f, -1.51432786e-02f, -1.50506566e-02f, -1.48245834e-02f, + -1.44674302e-02f, -1.39827154e-02f, -1.33750864e-02f, -1.26502768e-02f, + -1.18150098e-02f, -1.08770275e-02f, -9.84492957e-03f, -8.72812658e-03f, + -7.53678810e-03f, -6.28165584e-03f, -4.97410178e-03f, -3.62585109e-03f, + -2.24899664e-03f, -8.55861568e-04f, 5.41158727e-04f, 1.92960471e-03f, + 3.29720069e-03f, 4.63182847e-03f, 5.92171512e-03f, 7.15552772e-03f, + 8.32245104e-03f, 9.41229766e-03f, 1.04155873e-02f, 1.13236403e-02f, + 1.21286239e-02f, 1.28237067e-02f, 1.34029530e-02f, 1.38615561e-02f, + 1.41957640e-02f, 1.44029303e-02f, 1.44815754e-02f, 1.44313144e-02f, + 1.42529830e-02f, 1.39484805e-02f, 1.35208513e-02f, 1.29742567e-02f, + 1.23138195e-02f, 1.15457887e-02f, 1.06772196e-02f, 9.71614290e-03f, + 8.67133403e-03f, 7.55228769e-03f, 6.36919353e-03f, 5.13268639e-03f, + 3.85394864e-03f, 2.54443240e-03f, 1.21585789e-03f, -1.19878996e-04f, + -1.45093919e-03f, -2.76547718e-03f, -4.05185149e-03f, -5.29871877e-03f, + -6.49507635e-03f, -7.63046174e-03f, -8.69492807e-03f, -9.67921631e-03f, + -1.05747852e-02f, -1.13739326e-02f, -1.20697971e-02f, -1.26564969e-02f, + -1.31290964e-02f, -1.34837229e-02f, -1.37175190e-02f, -1.38287732e-02f, + -1.38167986e-02f, -1.36820574e-02f, -1.34260714e-02f, -1.30514744e-02f, + -1.25618820e-02f, -1.19619814e-02f, -1.12574303e-02f, -1.04547257e-02f, + -9.56133206e-03f, -8.58538654e-03f, -7.53583412e-03f, -6.42217872e-03f, + -5.25452181e-03f, -4.04336901e-03f, -2.79962633e-03f, -1.53443002e-03f, + -2.59098789e-04f, 1.01502741e-03f, 2.27659752e-03f, 3.51443317e-03f, + 4.71761329e-03f, 5.87551504e-03f, 6.97798012e-03f, 8.01534309e-03f, + 8.97859709e-03f, 9.85934513e-03f, 1.06500123e-02f, 1.13437857e-02f, + 1.19348028e-02f, 1.24180671e-02f, 1.27895845e-02f, 1.30463879e-02f, + 1.31864572e-02f, 1.32089360e-02f, 1.31138884e-02f, 1.29025483e-02f, + 1.25770694e-02f, 1.21406815e-02f, 1.15975855e-02f, 1.09528847e-02f, + 1.02125997e-02f, 9.38359994e-03f, 8.47347906e-03f, 7.49058151e-03f, + 6.44381432e-03f, 5.34271678e-03f, 4.19719189e-03f, 3.01755365e-03f, + 1.81439519e-03f, 5.98487206e-04f, -6.19328778e-04f, -1.82821924e-03f, + -3.01745871e-03f, -4.17650106e-03f, -5.29517520e-03f, -6.36359283e-03f, + -7.37244199e-03f, -8.31286411e-03f, -9.17674098e-03f, -9.95657112e-03f, + -1.06456657e-02f, -1.12381503e-02f, -1.17290432e-02f, -1.21142223e-02f, + -1.23906200e-02f, -1.25560393e-02f, -1.26093357e-02f, -1.25503481e-02f, + -1.23799279e-02f, -1.20999009e-02f, -1.17130697e-02f, -1.12231760e-02f, + -1.06348586e-02f, -9.95364282e-03f, -9.18584158e-03f, -8.33854107e-03f, + -7.41947999e-03f, -6.43706907e-03f, -5.40019213e-03f, -4.31826103e-03f, + -3.20099965e-03f, -2.05845707e-03f, -9.00865349e-04f, 2.61441292e-04f, + 1.41809108e-03f, 2.55883321e-03f, 3.67354889e-03f, 4.75241753e-03f, + 5.78592427e-03f, 6.76501988e-03f, 7.68115315e-03f, 8.52633033e-03f, + 9.29324902e-03f, 9.97532856e-03f, 1.05667189e-02f, 1.10624375e-02f, + 1.14583325e-02f, 1.17511845e-02f, 1.19386891e-02f, 1.20194307e-02f, + 1.19930645e-02f, 1.18600946e-02f, 1.16220073e-02f, 1.12812470e-02f, + 1.08411159e-02f, 1.03058419e-02f, 9.68042693e-03f, 8.97072807e-03f, + 8.18325916e-03f, 7.32531818e-03f, 6.40466057e-03f, 5.42973409e-03f, + 4.40930059e-03f, 3.35261692e-03f, 2.26916087e-03f, 1.16867964e-03f, + 6.09764122e-05f, -1.04405158e-03f, -2.13657269e-03f, -3.20691908e-03f, + -4.24563108e-03f, -5.24355860e-03f, -6.19193381e-03f, -7.08246398e-03f, + -7.90741664e-03f, -8.65959818e-03f, -9.33257195e-03f, -9.92055171e-03f, + -1.04185689e-02f, -1.08224536e-02f, -1.11288669e-02f, -1.13354045e-02f, + -1.14404744e-02f, -1.14434825e-02f, -1.13446735e-02f, -1.11452105e-02f, + -1.08472074e-02f, -1.04535649e-02f, -9.96807850e-03f, -9.39533832e-03f, + -8.74069245e-03f, -8.01020524e-03f, -7.21061702e-03f, -6.34920826e-03f, + -5.43385500e-03f, -4.47285238e-03f, -3.47490761e-03f, -2.44893717e-03f, + -1.40424120e-03f, -3.50117524e-04f, 7.03996367e-04f, 1.74874948e-03f, + 2.77486316e-03f, 3.77323529e-03f, 4.73510696e-03f, 5.65199516e-03f, + 6.51589535e-03f, 7.31925060e-03f, 8.05510111e-03f, 8.71711178e-03f, + 9.29960810e-03f, 9.79762661e-03f, 1.02069851e-02f, 1.05243267e-02f, + 1.07470734e-02f, 1.08735440e-02f, 1.09028797e-02f, 1.08351360e-02f, + 1.06711793e-02f, 1.04127447e-02f, 1.00624520e-02f, 9.62365700e-03f, + 9.10054672e-03f, 8.49802459e-03f, 7.82168998e-03f, 7.07776632e-03f, + 6.27310545e-03f, 5.41501721e-03f, 4.51131894e-03f, 3.57017926e-03f, + 2.60009355e-03f, 1.60979968e-03f, 6.08111937e-04f, -3.95951416e-04f, + -1.39349410e-03f, -2.37563261e-03f, -3.33368651e-03f, -4.25918973e-03f, + -5.14402447e-03f, -5.98039909e-03f, -6.76106610e-03f, -7.47919350e-03f, + -8.12860686e-03f, -8.70374621e-03f, -9.19968280e-03f, -9.61226566e-03f, + -9.93805155e-03f, -1.01744272e-02f, -1.03195556e-02f, -1.03723935e-02f, + -1.03327824e-02f, -1.02013418e-02f, -9.97951235e-03f, -9.66955161e-03f, + -9.27446962e-03f, -8.79806255e-03f, -8.24481164e-03f, -7.61986353e-03f, + -6.92898907e-03f, -6.17857782e-03f, -5.37544863e-03f, -4.52693679e-03f, + -3.64070939e-03f, -2.72478903e-03f, -1.78741480e-03f, -8.36980429e-04f, + 1.17989664e-04f, 1.06900538e-03f, 2.00758934e-03f, 2.92546046e-03f, + 3.81446851e-03f, 4.66681294e-03f, 5.47503707e-03f, 6.23202642e-03f, + 6.93126676e-03f, 7.56665434e-03f, 8.13275794e-03f, 8.62476397e-03f, + 9.03846441e-03f, 9.37048808e-03f, 9.61806678e-03f, 9.77929762e-03f, + 9.85300343e-03f, 9.83878137e-03f, 9.73705126e-03f, 9.54895686e-03f, + 9.27646789e-03f, 8.92224731e-03f, 8.48973525e-03f, 7.98296668e-03f, + 7.40673818e-03f, 6.76635688e-03f, 6.06774523e-03f, 5.31727151e-03f, + 4.52181520e-03f, 3.68856414e-03f, 2.82508906e-03f, 1.93911771e-03f, + 1.03864165e-03f, 1.31724000e-04f, -7.73548104e-04f, -1.66912498e-03f, + -2.54705639e-03f, -3.39960239e-03f, -4.21921926e-03f, -4.99874677e-03f, + -5.73135156e-03f, -6.41064787e-03f, -7.03078893e-03f, -7.58638515e-03f, + -8.07274239e-03f, -8.48573382e-03f, -8.82189842e-03f, -9.07847965e-03f, + -9.25348106e-03f, -9.34553774e-03f, -9.35415328e-03f, -9.27944192e-03f, + -9.12241341e-03f, -8.88466411e-03f, -8.56856089e-03f, -8.17721225e-03f, + -7.71429404e-03f, -7.18416104e-03f, -6.59175983e-03f, -5.94253368e-03f, + -5.24247041e-03f, -4.49794039e-03f, -3.71571847e-03f, -2.90290602e-03f, + -2.06680152e-03f, -1.21494821e-03f, -3.54960536e-04f, 5.05456753e-04f, + 1.35866818e-03f, 2.19707251e-03f, 3.01328571e-03f, 3.80007591e-03f, + 4.55055509e-03f, 5.25814438e-03f, 5.91669192e-03f, 6.52047200e-03f, + 7.06430640e-03f, 7.54353794e-03f, 7.95408283e-03f, 8.29252663e-03f, + 8.55606304e-03f, 8.74262824e-03f, 8.85075236e-03f, 8.87975906e-03f, + 8.82962202e-03f, 8.70105451e-03f, 8.49545293e-03f, 8.21486139e-03f, + 7.86207328e-03f, 7.44038974e-03f, 6.95385180e-03f, 6.40695971e-03f, + 5.80479502e-03f, 5.15286870e-03f, 4.45718246e-03f, 3.72404945e-03f, + 2.96010221e-03f, 2.17226938e-03f, 1.36762733e-03f, 5.53405381e-04f, + -2.63146484e-04f, -1.07470419e-03f, -1.87409443e-03f, -2.65421924e-03f, + -3.40822243e-03f, -4.12944508e-03f, -4.81156866e-03f, -5.44867726e-03f, + -6.03520323e-03f, -6.56613683e-03f, -7.03688002e-03f, -7.44347988e-03f, + -7.78249135e-03f, -8.05112205e-03f, -8.24721385e-03f, -8.36925002e-03f, + -8.41640683e-03f, -8.38846392e-03f, -8.28595912e-03f, -8.11001020e-03f, + -7.86244307e-03f, -7.54569095e-03f, -7.16278595e-03f, -6.71738300e-03f, + -6.21361469e-03f, -5.65619404e-03f, -5.05022152e-03f, -4.40127236e-03f, + -3.71526815e-03f, -2.99841577e-03f, -2.25722112e-03f, -1.49836763e-03f, + -7.28642071e-04f, 4.50434477e-05f, 8.15794780e-04f, 1.57675983e-03f, + 2.32119303e-03f, 3.04253373e-03f, 3.73440374e-03f, 4.39076109e-03f, + 5.00588458e-03f, 5.57441293e-03f, 6.09144353e-03f, 6.55256260e-03f, + 6.95381103e-03f, 7.29184996e-03f, 7.56385409e-03f, 7.76763312e-03f, + 7.90157987e-03f, 7.96474929e-03f, 7.95679715e-03f, 7.87804369e-03f, + 7.72942509e-03f, 7.51247337e-03f, 7.22938137e-03f, 6.88285636e-03f, + 6.47620933e-03f, 6.01325566e-03f, 5.49830484e-03f, 4.93612206e-03f, + 4.33185515e-03f, 3.69104049e-03f, 3.01948602e-03f, 2.32330330e-03f, + 1.60874544e-03f, 8.82231714e-04f, 1.50308827e-04f, -5.80528317e-04f, + -1.30375322e-03f, -2.01296135e-03f, -2.70188199e-03f, -3.36444701e-03f, + -3.99483762e-03f, -4.58758013e-03f, -5.13746453e-03f, -5.63980390e-03f, + -6.09023428e-03f, -6.48495361e-03f, -6.82059867e-03f, -7.09440613e-03f, + -7.30414154e-03f, -7.44814383e-03f, -7.52537074e-03f, -7.53532231e-03f, + -7.47817928e-03f, -7.35462729e-03f, -7.16604653e-03f, -6.91428861e-03f, + -6.60184485e-03f, -6.23166749e-03f, -5.80727376e-03f, -5.33260451e-03f, + -4.81206874e-03f, -4.25046030e-03f, -3.65290532e-03f, -3.02486716e-03f, + -2.37201464e-03f, -1.70026266e-03f, -1.01565670e-03f, -3.24326012e-04f, + 3.67549442e-04f, 1.05380850e-03f, 1.72833677e-03f, 2.38519657e-03f, + 3.01857597e-03f, 3.62294858e-03f, 4.19299252e-03f, 4.72378606e-03f, + 5.21069555e-03f, 5.64957232e-03f, 6.03664967e-03f, 6.36867744e-03f, + 6.64284990e-03f, 6.85698418e-03f, 7.00931443e-03f, 7.09874441e-03f, + 7.12465840e-03f, 7.08706888e-03f, 6.98651094e-03f, 6.82410178e-03f, + 6.60150306e-03f, 6.32090521e-03f, 5.98500306e-03f, 5.59697420e-03f, + 5.16044998e-03f, 4.67947995e-03f, 4.15852195e-03f, 3.60234011e-03f, + 3.01599436e-03f, 2.40483676e-03f, 1.77436911e-03f, 1.13028995e-03f, + 4.78371791e-04f, -1.75557439e-04f, -8.25650621e-04f, -1.46616696e-03f, + -2.09139965e-03f, -2.69583389e-03f, -3.27419516e-03f, -3.82140068e-03f, + -4.33268241e-03f, -4.80363658e-03f, -5.23017010e-03f, -5.60867917e-03f, + -5.93592714e-03f, -6.20921825e-03f, -6.42625503e-03f, -6.58536384e-03f, + -6.68526899e-03f, -6.72533025e-03f, -6.70537034e-03f, -6.62579421e-03f, + -6.48752153e-03f, -6.29196633e-03f, -6.04110074e-03f, -5.73732501e-03f, + -5.38355191e-03f, -4.98307960e-03f, -4.53965639e-03f, -4.05738219e-03f, + -3.54068617e-03f, -2.99427320e-03f, -2.42313171e-03f, -1.83242705e-03f, + -1.22748267e-03f, -6.13742401e-04f, 3.29459964e-06f, 6.18128643e-04f, + 1.22530705e-03f, 1.81942982e-03f, 2.39525993e-03f, 2.94773242e-03f, + 3.47202200e-03f, 3.96353949e-03f, 4.41805557e-03f, 4.83162874e-03f, + 5.20072081e-03f, 5.52222100e-03f, 5.79342983e-03f, 6.01209834e-03f, + 6.17646715e-03f, 6.28528285e-03f, 6.33778155e-03f, 6.33364321e-03f, + 6.27317175e-03f, 6.15705477e-03f, 5.98657179e-03f, 5.76336894e-03f, + 5.48968624e-03f, 5.16811657e-03f, 4.80167923e-03f, 4.39381474e-03f, + 3.94828981e-03f, 3.46922951e-03f, 2.96098404e-03f, 2.42819955e-03f, + 1.87572404e-03f, 1.30851770e-03f, 7.31708692e-04f, 1.50445075e-04f, + -4.30070196e-04f, -1.00466524e-03f, -1.56827393e-03f, -2.11588940e-03f, + -2.64269440e-03f, -3.14408074e-03f, -3.61568802e-03f, -4.05340575e-03f, + -4.45347457e-03f, -4.81245326e-03f, -5.12730726e-03f, -5.39539697e-03f, + -5.61447256e-03f, -5.78280084e-03f, -5.89903878e-03f, -5.96236515e-03f, + -5.97237627e-03f, -5.92920270e-03f, -5.83340844e-03f, -5.68605666e-03f, + -5.48859720e-03f, -5.24302551e-03f, -4.95168147e-03f, -4.61731774e-03f, + -4.24306545e-03f, -3.83242787e-03f, -3.38915992e-03f, -2.91735676e-03f, + -2.42128273e-03f, -1.90549963e-03f, -1.37460030e-03f, -8.33438466e-04f, + -2.86823004e-04f, 2.60354880e-04f, 8.03207784e-04f, 1.33692447e-03f, + 1.85678689e-03f, 2.35821923e-03f, 2.83683539e-03f, 3.28844174e-03f, + 3.70911927e-03f, 4.09525832e-03f, 4.44351520e-03f, 4.75094096e-03f, + 5.01496724e-03f, 5.23338423e-03f, 5.40441842e-03f, 5.52671520e-03f, + 5.59940305e-03f, 5.62196231e-03f, 5.59441894e-03f, 5.51717260e-03f, + 5.39113480e-03f, 5.21756593e-03f, 4.99821309e-03f, 4.73517171e-03f, + 4.43098991e-03f, 4.08848316e-03f, 3.71086536e-03f, 3.30161941e-03f, + 2.86452112e-03f, 2.40355058e-03f, 1.92289472e-03f, 1.42691829e-03f, + 9.20082667e-04f, 4.06947402e-04f, -1.07889236e-04f, -6.19866009e-04f, + -1.12440714e-03f, -1.61705134e-03f, -2.09347995e-03f, -2.54950186e-03f, + -2.98112018e-03f, -3.38459298e-03f, -3.75646023e-03f, -4.09349417e-03f, + -4.39283025e-03f, -4.65196264e-03f, -4.86872185e-03f, -5.04135627e-03f, + -5.16846121e-03f, -5.24911038e-03f, -5.28273546e-03f, -5.26923483e-03f, + -5.20889346e-03f, -5.10241936e-03f, -4.95095616e-03f, -4.75600604e-03f, + -4.51946213e-03f, -4.24360018e-03f, -3.93100678e-03f, -3.58463172e-03f, + -3.20766423e-03f, -2.80356963e-03f, -2.37606413e-03f, -1.92903332e-03f, + -1.46654246e-03f, -9.92737520e-04f, -5.11899001e-04f, -2.83386680e-05f, + 4.53646722e-04f, 9.29762308e-04f, 1.39578833e-03f, 1.84762733e-03f, + 2.28131825e-03f, 2.69304631e-03f, 3.07924546e-03f, 3.43657876e-03f, + 3.76196637e-03f, 4.05264982e-03f, 4.30615625e-03f, 4.52037923e-03f, + 4.69357270e-03f, 4.82432688e-03f, 4.91167463e-03f, 4.95498330e-03f, + 4.95403020e-03f, 4.90901290e-03f, 4.82048355e-03f, 4.68941823e-03f, + 4.51712335e-03f, 4.30530103e-03f, 4.05599901e-03f, 3.77155674e-03f, + 3.45466234e-03f, 3.10824592e-03f, 2.73552763e-03f, 2.33988646e-03f, + 1.92496688e-03f, 1.49452387e-03f, 1.05244323e-03f, 6.02711670e-04f, + 1.49351638e-04f, -3.03587769e-04f, -7.52061949e-04f, -1.19211664e-03f, + -1.61985030e-03f, -2.03151752e-03f, -2.42352388e-03f, -2.79243508e-03f, + -3.13508754e-03f, -3.44849890e-03f, -3.73002846e-03f, -3.97727137e-03f, + -4.18818648e-03f, -4.36099952e-03f, -4.49435996e-03f, -4.58719167e-03f, + -4.63889710e-03f, -4.64910091e-03f, -4.61791828e-03f, -4.54579073e-03f, + -4.43349692e-03f, -4.28220742e-03f, -4.09340522e-03f, -3.86894313e-03f, + -3.61094458e-03f, -3.32182985e-03f, -3.00430811e-03f, -2.66130866e-03f, + -2.29599013e-03f, -1.91166816e-03f, -1.51187551e-03f, -1.10018574e-03f, + -6.80346354e-04f, -2.56113849e-04f, 1.68739284e-04f, 5.90407275e-04f, + 1.00515855e-03f, 1.40933774e-03f, 1.79936566e-03f, 2.17186782e-03f, + 2.52357035e-03f, 2.85142336e-03f, 3.15262986e-03f, 3.42461005e-03f, + 3.66503089e-03f, 3.87190507e-03f, 4.04353460e-03f, 4.17850434e-03f, + 4.27578294e-03f, 4.33462985e-03f, 4.35470557e-03f, 4.33597602e-03f, + 4.27874072e-03f, 4.18369422e-03f, 4.05179951e-03f, 3.88441838e-03f, + 3.68314007e-03f, 3.44992044e-03f, 3.18696299e-03f, 2.89668435e-03f, + 2.58184280e-03f, 2.24526719e-03f, 1.89009368e-03f, 1.51951951e-03f, + 1.13694082e-03f, 7.45769400e-04f, 3.49541752e-04f, -4.81936756e-05f, + -4.43913653e-04f, -8.34083461e-04f, -1.21527427e-03f, -1.58411419e-03f, + -1.93737804e-03f, -2.27200055e-03f, -2.58505953e-03f, -2.87386789e-03f, + -3.13596261e-03f, -3.36909158e-03f, -3.57133916e-03f, -3.74099847e-03f, + -3.87671687e-03f, -3.97740006e-03f, -4.04231029e-03f, -4.07103135e-03f, + -4.06340767e-03f, -4.01970877e-03f, -3.94042889e-03f, -3.82644559e-03f, + -3.67889403e-03f, -3.49922796e-03f, -3.28921214e-03f, -3.05076130e-03f, + -2.78618777e-03f, -2.49791205e-03f, -2.18859644e-03f, -1.86109553e-03f, + -1.51839073e-03f, -1.16358358e-03f, -7.99877062e-04f, -4.30553251e-04f, + -5.89097879e-05f, 3.11723769e-04f, 6.78086247e-04f, 1.03688551e-03f, + 1.38501625e-03f, 1.71939231e-03f, 2.03710542e-03f, 2.33541156e-03f, + 2.61171872e-03f, 2.86367519e-03f, 3.08912751e-03f, 3.28618123e-03f, + 3.45323225e-03f, 3.58887288e-03f, 3.69206164e-03f, 3.76199260e-03f, + 3.79818934e-03f, 3.80048902e-03f, 3.76896561e-03f, 3.70409972e-03f, + 3.60655769e-03f, 3.47737266e-03f, 3.31778682e-03f, 3.12940296e-03f, + 2.91396911e-03f, 2.67353140e-03f, 2.41032316e-03f, 2.12679252e-03f, + 1.82553429e-03f, 1.50931293e-03f, 1.18097998e-03f, 8.43534445e-04f, + 4.99989066e-04f, 1.53447611e-04f, -1.93022636e-04f, -5.36321933e-04f, + -8.73416474e-04f, -1.20133088e-03f, -1.51720912e-03f, -1.81826798e-03f, + -2.10190341e-03f, -2.36567388e-03f, -2.60729607e-03f, -2.82474796e-03f, + -3.01616460e-03f, -3.17997100e-03f, -3.31484550e-03f, -3.41966897e-03f, + -3.49367342e-03f, -3.53630851e-03f, -3.54733556e-03f, -3.52679670e-03f, + -3.47499990e-03f, -3.39254703e-03f, -3.28028198e-03f, -3.13934064e-03f, + -2.97110336e-03f, -2.77718173e-03f, -2.55939692e-03f, -2.31979112e-03f, + -2.06058943e-03f, -1.78418388e-03f, -1.49308601e-03f, -1.18996918e-03f, + -8.77562261e-04f, -5.58694884e-04f, -2.36201878e-04f, 8.69917292e-05f, + 4.08040872e-04f, 7.24100044e-04f, 1.03237391e-03f, 1.33015630e-03f, + 1.61484341e-03f, 1.88395774e-03f, 2.13520669e-03f, 2.36638885e-03f, + 2.57554947e-03f, 2.76095026e-03f, 2.92101166e-03f, 3.05442850e-03f, + 3.16014757e-03f, 3.23732893e-03f, 3.28541223e-03f, 3.30412034e-03f, + 3.29335572e-03f, 3.25342718e-03f, 3.18472643e-03f, 3.08805315e-03f, + 2.96434808e-03f, 2.81484617e-03f, 2.64098428e-03f, 2.44441964e-03f, + 2.22698580e-03f, 1.99071671e-03f, 1.73777144e-03f, 1.47049147e-03f, + 1.19131101e-03f, 9.02757052e-04f, 6.07440484e-04f, 3.08007319e-04f, + 7.13829517e-06f, -2.92474511e-04f, -5.88197665e-04f, -8.77399733e-04f, + -1.15755209e-03f, -1.42622267e-03f, -1.68101236e-03f, -1.91978505e-03f, + -2.14045006e-03f, -2.34115054e-03f, -2.52017578e-03f, -2.67602742e-03f, + -2.80743844e-03f, -2.91334720e-03f, -2.99289427e-03f, -3.04551982e-03f, + -3.07087625e-03f, -3.06883997e-03f, -3.03955162e-03f, -2.98340720e-03f, + -2.90101796e-03f, -2.79322818e-03f, -2.66108957e-03f, -2.50591158e-03f, + -2.32917402e-03f, -2.13252284e-03f, -1.91779817e-03f, -1.68700449e-03f, + -1.44223748e-03f, -1.18575491e-03f, -9.19861684e-04f, -6.47001914e-04f, + -3.69593810e-04f, -9.01276712e-05f, 1.88886030e-04f, 4.64997876e-04f, + 7.35743579e-04f, 9.98752535e-04f, 1.25173547e-03f, 1.49246727e-03f, + 1.71888893e-03f, 1.92901822e-03f, 2.12110128e-03f, 2.29349620e-03f, + 2.44475112e-03f, 2.57362114e-03f, 2.67908845e-03f, 2.76026445e-03f, + 2.81660118e-03f, 2.84766807e-03f, 2.85331403e-03f, 2.83360095e-03f, + 2.78882988e-03f, 2.71949307e-03f, 2.62633897e-03f, 2.51027194e-03f, + 2.37244667e-03f, 2.21418592e-03f, 2.03698052e-03f, 1.84248796e-03f, + 1.63251333e-03f, 1.40901166e-03f, 1.17398423e-03f, 9.29629511e-04f, + 6.78093274e-04f, 4.21697134e-04f, 1.62708629e-04f, -9.65532846e-05f, + -3.53786150e-04f, -6.06698280e-04f, -8.53097342e-04f, -1.09078811e-03f, + -1.31773933e-03f, -1.53194961e-03f, -1.73160040e-03f, -1.91496699e-03f, + -2.08051810e-03f, -2.22683099e-03f, -2.35272161e-03f, -2.45716809e-03f, + -2.53932263e-03f, -2.59857031e-03f, -2.63448385e-03f, -2.64686900e-03f, + -2.63568789e-03f, -2.60117512e-03f, -2.54374911e-03f, -2.46399878e-03f, + -2.36276068e-03f, -2.24101080e-03f, -2.09994110e-03f, -1.94088932e-03f, + -1.76533620e-03f, -1.57492545e-03f, -1.37143402e-03f, -1.15668344e-03f, + -9.32686687e-04f, -7.01423675e-04f, -4.65019034e-04f, -2.25575437e-04f, + 1.47791969e-05f, 2.53869081e-04f, 4.89608281e-04f, 7.19930839e-04f, + 9.42775487e-04f, 1.15624415e-03f, 1.35846050e-03f, 1.54767351e-03f, + 1.72231203e-03f, 1.88082576e-03f, 2.02194509e-03f, 2.14444684e-03f, + 2.24738140e-03f, 2.32988384e-03f, 2.39134751e-03f, 2.43130004e-03f, + 2.44952080e-03f, 2.44590293e-03f, 2.42061909e-03f, 2.37398935e-03f, + 2.30650850e-03f, 2.21889198e-03f, 2.11201559e-03f, 1.98687753e-03f, + 1.84474091e-03f, 1.68687111e-03f, 1.51481261e-03f, 1.33010545e-03f, + 1.13447001e-03f, 9.29723262e-04f, 7.17657881e-04f, 5.00235175e-04f, + 2.79406846e-04f, 5.71322995e-05f, -1.64594545e-04f, -3.83823629e-04f, + -5.98614360e-04f, -8.07065822e-04f, -1.00739680e-03f, -1.19784818e-03f, + -1.37675697e-03f, -1.54263099e-03f, -1.69403924e-03f, -1.82971049e-03f, + -1.94850212e-03f, -2.04945536e-03f, -2.13177374e-03f, -2.19478544e-03f, + -2.23804061e-03f, -2.26125546e-03f, -2.26431492e-03f, -2.24728584e-03f, + -2.21038704e-03f, -2.15409950e-03f, -2.07896974e-03f, -1.98576777e-03f, + -1.87541310e-03f, -1.74895112e-03f, -1.60761319e-03f, -1.45267246e-03f, + -1.28561050e-03f, -1.10794701e-03f, -9.21319188e-04f, -7.27412747e-04f, + -5.28001974e-04f, -3.24849174e-04f, -1.19822475e-04f, 8.53071910e-05f, + 2.88686843e-04f, 4.88528049e-04f, 6.83070989e-04f, 8.70634797e-04f, + 1.04957014e-03f, 1.21833395e-03f, 1.37549860e-03f, 1.51969890e-03f, + 1.64975621e-03f, 1.76454677e-03f, 1.86314685e-03f, 1.94476478e-03f, + 2.00874558e-03f, 2.05462014e-03f, 2.08206459e-03f, 2.09092884e-03f, + 2.08122572e-03f, 2.05311829e-03f, 2.00696285e-03f, 1.94325570e-03f, + 1.86261554e-03f, 1.76589405e-03f, 1.65398613e-03f, 1.52796111e-03f, + 1.38902998e-03f, 1.23845862e-03f, 1.07765357e-03f, 9.08101811e-04f, + 7.31312372e-04f, 5.48925579e-04f, 3.62559457e-04f, 1.73908753e-04f, + -1.53640653e-05f, -2.03576240e-04f, -3.89073746e-04f, -5.70178373e-04f, + -7.45367890e-04f, -9.13091163e-04f, -1.07188249e-03f, -1.22041361e-03f, + -1.35740687e-03f, -1.48168528e-03f, -1.59225154e-03f, -1.68814122e-03f, + -1.76862655e-03f, -1.83303385e-03f, -1.88089694e-03f, -1.91186980e-03f, + -1.92575160e-03f, -1.92251421e-03f, -1.90226422e-03f, -1.86526958e-03f, + -1.81193893e-03f, -1.74283351e-03f, -1.65863573e-03f, -1.56017209e-03f, + -1.44838006e-03f, -1.32431143e-03f, -1.18916080e-03f, -1.04412159e-03f, + -8.90592600e-04f, -7.29926142e-04f, -5.63607604e-04f, -3.93123732e-04f, + -2.20015398e-04f, -4.58265535e-05f, 1.27888591e-04f, 2.99622014e-04f, + 4.67812693e-04f, 6.31045212e-04f, 7.87850352e-04f, 9.36918188e-04f, + 1.07691014e-03f, 1.20669763e-03f, 1.32512406e-03f, 1.43124633e-03f, + 1.52414726e-03f, 1.60310171e-03f, 1.66746035e-03f, 1.71674068e-03f, + 1.75057975e-03f, 1.76874501e-03f, 1.77117438e-03f, 1.75790455e-03f, + 1.72916148e-03f, 1.68523050e-03f, 1.62665041e-03f, 1.55393961e-03f, + 1.46788028e-03f, 1.36921441e-03f, 1.25897234e-03f, 1.13815311e-03f, + 1.00785213e-03f, 8.69331636e-04f, 7.23787860e-04f, 5.72621147e-04f, + 4.17123806e-04f, 2.58743469e-04f, 9.88837891e-05f, -6.10350755e-05f, + -2.19596167e-04f, -3.75409622e-04f, -5.27082337e-04f, -6.73344125e-04f, + -8.12882243e-04f, -9.44509077e-04f, -1.06712919e-03f, -1.17964492e-03f, + -1.28116892e-03f, -1.37084083e-03f, -1.44791745e-03f, -1.51179651e-03f, + -1.56196842e-03f, -1.59806342e-03f, -1.61984890e-03f, -1.62719129e-03f, + -1.62009091e-03f, -1.59871731e-03f, -1.56330213e-03f, -1.51425912e-03f, + -1.45207397e-03f, -1.37735224e-03f, -1.29085967e-03f, -1.19339166e-03f, + -1.08587214e-03f, -9.69313282e-04f, -8.44784217e-04f, -7.13452738e-04f, + -5.76480508e-04f, -4.35102909e-04f, -2.90654344e-04f, -1.44369163e-04f, + 2.42120883e-06f, 1.48447430e-04f, 2.92370866e-04f, 4.32973514e-04f, + 5.68999279e-04f, 6.99308513e-04f, 8.22732435e-04f, 9.38238883e-04f, + 1.04484914e-03f, 1.14166499e-03f, 1.22788622e-03f, 1.30279910e-03f, + 1.36578274e-03f, 1.41640462e-03f, 1.45420013e-03f, 1.47896611e-03f, + 1.49053972e-03f, 1.48884571e-03f, 1.47404145e-03f, 1.44625672e-03f, + 1.40585004e-03f, 1.35323924e-03f, 1.28894075e-03f, 1.21358722e-03f, + 1.12792237e-03f, 1.03272450e-03f, 9.28938538e-04f, 8.17461092e-04f, + 6.99372886e-04f, 5.75717626e-04f, 4.47641236e-04f, 3.16292734e-04f, + 1.82841969e-04f, 4.84802779e-05f, -8.55739910e-05f, -2.18169080e-04f, + -3.48142521e-04f, -4.74327437e-04f, -5.95661172e-04f, -7.11080885e-04f, + -8.19593186e-04f, -9.20314282e-04f, -1.01233129e-03f, -1.09493574e-03f, + -1.16743147e-03f, -1.22920752e-03f, -1.27981998e-03f, -1.31882788e-03f, + -1.34600511e-03f, -1.36114508e-03f, -1.36417035e-03f, -1.35514680e-03f, + -1.33419233e-03f, -1.30158504e-03f, -1.25764422e-03f, -1.20284876e-03f, + -1.13771466e-03f, -1.06289654e-03f, -9.79088444e-04f, -8.87105266e-04f, + -7.87774055e-04f, -6.82024906e-04f, -5.70838905e-04f, -4.55202296e-04f, + -3.36181145e-04f, -2.14839486e-04f, -9.22503227e-05f, 3.04698838e-05f, + 1.52270414e-04f, 2.72050892e-04f, 3.88799677e-04f, 5.01453830e-04f, + 6.09100748e-04f, 7.10751675e-04f, 8.05583159e-04f, 8.92801542e-04f, + 9.71649359e-04f, 1.04151118e-03f, 1.10177598e-03f, 1.15200217e-03f, + 1.19178629e-03f, 1.22083114e-03f, 1.23895814e-03f, 1.24603381e-03f, + 1.24209314e-03f, 1.22718978e-03f, 1.20157123e-03f, 1.16547661e-03f, + 1.11929301e-03f, 1.06350264e-03f, 9.98630850e-04f, 9.25308342e-04f, + 8.44227193e-04f, 7.56141297e-04f, 6.61877038e-04f, 5.62284265e-04f, + 4.58306144e-04f, 3.50831771e-04f, 2.40873321e-04f, 1.29410208e-04f, + 1.74046420e-05f, -9.41372015e-05f, -2.04212769e-04f, -3.11884379e-04f, + -4.16198692e-04f, -5.16283854e-04f, -6.11247752e-04f, -7.00285651e-04f, + -7.82656708e-04f, -8.57682384e-04f, -9.24710993e-04f, -9.83216422e-04f, + -1.03273072e-03f, -1.07286281e-03f, -1.10331228e-03f, -1.12385081e-03f, + -1.13439398e-03f, -1.13485267e-03f, -1.12532108e-03f, -1.10590886e-03f, + -1.07686330e-03f, -1.03849711e-03f, -9.91189076e-04f, -9.35388727e-04f, + -8.71690018e-04f, -8.00651347e-04f, -7.22956626e-04f, -6.39322842e-04f, + -5.50528037e-04f, -4.57398663e-04f, -3.60742819e-04f, -2.61456827e-04f, + -1.60433252e-04f, -5.85672438e-05f, 4.32388885e-05f, 1.44099976e-04f, + 2.43118717e-04f, 3.39409985e-04f, 4.32194068e-04f, 5.20625265e-04f, + 6.03978397e-04f, 6.81539342e-04f, 7.52654738e-04f, 8.16724875e-04f, + 8.73252328e-04f, 9.21758980e-04f, 9.61871797e-04f, 9.93285392e-04f, + 1.01577077e-03f, 1.02918084e-03f, 1.03343231e-03f, 1.02859389e-03f, + 1.01469668e-03f, 9.91956413e-04f, 9.60609395e-04f, 9.20977714e-04f, + 8.73477570e-04f, 8.18534765e-04f, 7.56717188e-04f, 6.88582017e-04f, + 6.14781378e-04f, 5.35992969e-04f, 4.52932280e-04f, 3.66367367e-04f, + 2.77067953e-04f, 1.85846068e-04f, 9.35202666e-05f, 8.95800378e-07f, + -9.12013687e-05f, -1.81968879e-04f, -2.70612753e-04f, -3.56346876e-04f, + -4.38458579e-04f, -5.16248792e-04f, -5.89021803e-04f, -6.56215711e-04f, + -7.17226849e-04f, -7.71587683e-04f, -8.18834525e-04f, -8.58598909e-04f, + -8.90593018e-04f, -9.14550231e-04f, -9.30330737e-04f, -9.37818621e-04f, + -9.37042342e-04f, -9.28000423e-04f, -9.10856704e-04f, -8.85795548e-04f, + -8.53094084e-04f, -8.13072428e-04f, -7.66143829e-04f, -7.12744419e-04f, + -6.53375169e-04f, -5.88633731e-04f, -5.19070071e-04f, -4.45359640e-04f, + -3.68152539e-04f, -2.88173584e-04f, -2.06121828e-04f, -1.22733383e-04f, + -3.87658539e-05f, 4.50501871e-05f, 1.27995593e-04f, 2.09307286e-04f, + 2.88316517e-04f, 3.64302877e-04f, 4.36661583e-04f, 5.04755088e-04f, + 5.68007944e-04f, 6.25887844e-04f, 6.77933133e-04f, 7.23703420e-04f, + 7.62852083e-04f, 7.95064070e-04f, 8.20080681e-04f, 8.37764175e-04f, + 8.47969128e-04f, 8.50664693e-04f, 8.45873351e-04f, 8.33674258e-04f, + 8.14207332e-04f, 7.87719338e-04f, 7.54456412e-04f, 7.14767940e-04f, + 6.69041313e-04f, 6.17697028e-04f, 5.61235426e-04f, 5.00173623e-04f, + 4.35079832e-04f, 3.66567870e-04f, 2.95227094e-04f, 2.21715344e-04f, + 1.46718993e-04f, 7.08530739e-05f, -5.15577364e-06f, -8.07004736e-05f, + -1.55066524e-04f, -2.27613536e-04f, -2.97740553e-04f, -3.64841340e-04f, + -4.28317869e-04f, -4.87670380e-04f, -5.42375883e-04f, -5.92004229e-04f, + -6.36123948e-04f, -6.74394124e-04f, -7.06529242e-04f, -7.32237235e-04f, + -7.51412988e-04f, -7.63820757e-04f, -7.69492035e-04f, -7.68344591e-04f, + -7.60492235e-04f, -7.45985791e-04f, -7.25024598e-04f, -6.97830116e-04f, + -6.64676731e-04f, -6.25892548e-04f, -5.81842478e-04f, -5.32951282e-04f, + -4.79696107e-04f, -4.22525795e-04f, -3.62014271e-04f, -2.98672472e-04f, + -2.33120510e-04f, -1.65900121e-04f, -9.76362200e-05f, -2.89275243e-05f, + 3.96176674e-05f, 1.07395474e-04f, 1.73835271e-04f, 2.38325404e-04f, + 3.00347969e-04f, 3.59354345e-04f, 4.14862825e-04f, 4.66386521e-04f, + 5.13515409e-04f, 5.55829029e-04f, 5.93042831e-04f, 6.24819812e-04f, + 6.50905442e-04f, 6.71146467e-04f, 6.85375692e-04f, 6.93516598e-04f, + 6.95509528e-04f, 6.91405413e-04f, 6.81262048e-04f, 6.65220278e-04f, + 6.43420145e-04f, 6.16125905e-04f, 5.83606917e-04f, 5.46156953e-04f, + 5.04157409e-04f, 4.58002510e-04f, 4.08099631e-04f, 3.54942755e-04f, + 2.98984828e-04f, 2.40759234e-04f, 1.80768051e-04f, 1.19575974e-04f, + 5.76957438e-05f, -4.28880367e-06f, -6.58721960e-05f, -1.26493174e-04f, + -1.85639859e-04f, -2.42807383e-04f, -2.97481187e-04f, -3.49221122e-04f, + -3.97578847e-04f, -4.42159263e-04f, -4.82605142e-04f, -5.18557036e-04f, + -5.49760019e-04f, -5.75954068e-04f, -5.96944106e-04f, -6.12574988e-04f, + -6.22745551e-04f, -6.27396115e-04f, -6.26534322e-04f, -6.20176804e-04f, + -6.08460174e-04f, -5.91464522e-04f, -5.69410030e-04f, -5.42484457e-04f, + -5.10992484e-04f, -4.75225134e-04f, -4.35505918e-04f, -3.92225972e-04f, + -3.45771246e-04f, -2.96593648e-04f, -2.45116844e-04f, -1.91808621e-04f, + -1.37165970e-04f, -8.16356339e-05f, -2.57445926e-05f, 3.00322984e-05f, + 8.51783200e-05f, 1.39260137e-04f, 1.91796337e-04f, 2.42317102e-04f, + 2.90405255e-04f, 3.35659286e-04f, 3.77691701e-04f, 4.16174018e-04f, + 4.50785161e-04f, 4.81219250e-04f, 5.07259738e-04f, 5.28714262e-04f, + 5.45399813e-04f, 5.57228370e-04f, 5.64092051e-04f, 5.66000580e-04f, + 5.62944333e-04f, 5.54966288e-04f, 5.42209838e-04f, 5.24790563e-04f, + 5.02873830e-04f, 4.76708610e-04f, 4.46521001e-04f, 4.12650276e-04f, + 3.75358170e-04f, 3.35013907e-04f, 2.91997251e-04f, 2.46695035e-04f, + 1.99507857e-04f, 1.50889553e-04f, 1.01231956e-04f, 5.10047602e-05f, + 6.33587302e-07f, -4.94259002e-05f, -9.87418814e-05f, -1.46889490e-04f, + -1.93470217e-04f, -2.38065458e-04f, -2.80305060e-04f, -3.19836003e-04f, + -3.56327452e-04f, -3.89502317e-04f, -4.19041782e-04f, -4.44759447e-04f, + -4.66423158e-04f, -4.83886447e-04f, -4.97011387e-04f, -5.05730787e-04f, + -5.09963720e-04f, -5.09717728e-04f, -5.05043322e-04f, -4.95963142e-04f, + -4.82639446e-04f, -4.65152875e-04f, -4.43733866e-04f, -4.18567153e-04f, + -3.89890304e-04f, -3.57996097e-04f, -3.23170997e-04f, -2.85736681e-04f, + -2.46055002e-04f, -2.04445682e-04f, -1.61320963e-04f, -1.17042529e-04f, + -7.20188676e-05f, -2.66396241e-05f, 1.86912290e-05f, 6.36017948e-05f, + 1.07665176e-04f, 1.50514890e-04f, 1.91812013e-04f, 2.31174729e-04f, + 2.68275281e-04f, 3.02820173e-04f, 3.34517215e-04f, 3.63107193e-04f, + 3.88350390e-04f, 4.10051248e-04f, 4.28044624e-04f, 4.42214263e-04f, + 4.52417167e-04f, 4.58637799e-04f, 4.60811960e-04f, 4.58959019e-04f, + 4.53110813e-04f, 4.43353615e-04f, 4.29788076e-04f, 4.12573604e-04f, + 3.91837458e-04f, 3.67827680e-04f, 3.40742776e-04f, 3.10878990e-04f, + 2.78464465e-04f, 2.43800376e-04f, 2.07253019e-04f, 1.69099894e-04f, + 1.29702525e-04f, 8.93909104e-05f, 4.85743353e-05f, 7.55415142e-06f, + -3.32831786e-05f, -7.36072164e-05f, -1.13043176e-04f, -1.51234384e-04f, + -1.87933888e-04f, -2.22751408e-04f, -2.55424048e-04f, -2.85707115e-04f, + -3.13301151e-04f, -3.38018882e-04f, -3.59650405e-04f, -3.78009094e-04f, + -3.93005971e-04f, -4.04467563e-04f, -4.12352296e-04f, -4.16625213e-04f, + -4.17213487e-04f, -4.14188932e-04f, -4.07570766e-04f, -3.97428285e-04f, + -3.83928030e-04f, -3.67135876e-04f, -3.47267227e-04f, -3.24463119e-04f, + -2.98994961e-04f, -2.71043298e-04f, -2.40926487e-04f, -2.08843709e-04f, + -1.75152575e-04f, -1.40107049e-04f, -1.04080472e-04f, -6.73135829e-05f, + -3.01948541e-05f, 7.00871665e-06f, 4.39240599e-05f, 8.02943349e-05f, + 1.15726815e-04f, 1.49966848e-04f, 1.82710879e-04f, 2.13703334e-04f, + 2.42653106e-04f, 2.69351388e-04f, 2.93549873e-04f, 3.15102924e-04f, + 3.33768751e-04f, 3.49447907e-04f, 3.62019988e-04f, 3.71363176e-04f, + 3.77445082e-04f, 3.80209433e-04f, 3.79656986e-04f, 3.75829456e-04f, + 3.68740564e-04f, 3.58494796e-04f, 3.45203622e-04f, 3.28976671e-04f, + 3.09977680e-04f, 2.88380075e-04f, 2.64390378e-04f, 2.38244599e-04f, + 2.10143730e-04f, 1.80382945e-04f, 1.49191334e-04f, 1.16874433e-04f, + 8.36939641e-05f, 4.99545687e-05f, 1.59419682e-05f, -1.80487956e-05f, + -5.17116932e-05f, -8.47731419e-05f, -1.16937183e-04f, -1.47911960e-04f, + -1.77490349e-04f, -2.05400357e-04f, -2.31387402e-04f, -2.55252058e-04f, + -2.76804796e-04f, -2.95856747e-04f, -3.12270197e-04f, -3.25902725e-04f, + -3.36672987e-04f, -3.44466047e-04f, -3.49280604e-04f, -3.51026811e-04f, + -3.49738846e-04f, -3.45444097e-04f, -3.38175670e-04f, -3.28025304e-04f, + -3.15107780e-04f, -2.99483932e-04f, -2.81389775e-04f, -2.60907140e-04f, + -2.38309091e-04f, -2.13697965e-04f, -1.87369701e-04f, -1.59547138e-04f, + -1.30435290e-04f, -1.00336342e-04f, -6.95075165e-05f, -3.81891347e-05f, + -6.66316342e-06f, 2.48005967e-05f, 5.59093608e-05f, 8.64381970e-05f, + 1.16084478e-04f, 1.44629782e-04f, 1.71817136e-04f, 1.97430379e-04f, + 2.21249248e-04f, 2.43067483e-04f, 2.62714805e-04f, 2.80037512e-04f, + 2.94912411e-04f, 3.07154450e-04f, 3.16770812e-04f, 3.23565757e-04f, + 3.27598190e-04f, 3.28781884e-04f, 3.27137017e-04f, 3.22671504e-04f, + 3.15478482e-04f, 3.05561179e-04f, 2.93071243e-04f, 2.78072214e-04f, + 2.60752421e-04f, 2.41204026e-04f, 2.19657587e-04f, 1.96274763e-04f, + 1.71259618e-04f, 1.44829587e-04f, 1.17207736e-04f, 8.86396900e-05f, + 5.93848595e-05f, 2.96756971e-05f, -2.55974213e-07f, -3.01364040e-05f, + -5.97095986e-05f, -8.87016361e-05f, -1.16938981e-04f, -1.44121097e-04f, + -1.70042060e-04f, -1.94504049e-04f, -2.17285275e-04f, -2.38190653e-04f, + -2.57074514e-04f, -2.73769854e-04f, -2.88131929e-04f, -3.00067778e-04f, + -3.09445066e-04f, -3.16238251e-04f, -3.20350572e-04f, -3.21779258e-04f, + -3.20521292e-04f, -3.16575228e-04f, -3.09991670e-04f, -3.00802605e-04f, + -2.89147820e-04f, -2.75068336e-04f, -2.58722607e-04f, -2.40234254e-04f, + -2.19762234e-04f, -1.97484144e-04f, -1.73586378e-04f, -1.48283187e-04f, + -1.21767035e-04f, -9.42546438e-05f, -6.59849449e-05f, -3.72382910e-05f, + -8.18708029e-06f, 2.09018928e-05f, 4.97856427e-05f, 7.82043260e-05f, + 1.05945996e-04f, 1.32796055e-04f, 1.58498048e-04f, 1.82852505e-04f, + 2.05652703e-04f, 2.26725402e-04f, 2.45875363e-04f, 2.62957031e-04f, + 2.77828957e-04f, 2.90361412e-04f, 3.00450626e-04f, 3.07979930e-04f, + 3.12961709e-04f, 3.15260735e-04f, 3.14925301e-04f, 3.11915939e-04f, + 3.06269493e-04f, 2.97998031e-04f, 2.87169055e-04f, 2.73915388e-04f, + 2.58278031e-04f, 2.40399328e-04f, 2.20409753e-04f, 1.98478187e-04f, + 1.74739784e-04f, 1.49434544e-04f, 1.22685308e-04f, 9.47715675e-05f, + 6.58537103e-05f, 3.61749072e-05f, 5.97746474e-06f, -2.45162183e-05f, + -5.50707960e-05f, -8.54546076e-05f, -1.15437977e-04f, -1.44752181e-04f, + -1.73217230e-04f, -2.00586450e-04f, -2.26666528e-04f, -2.51224767e-04f, + -2.74099533e-04f, -2.95109231e-04f, -3.14079824e-04f, -3.30869553e-04f, + -3.45369337e-04f, -3.57422987e-04f, -3.66953155e-04f, -3.73911775e-04f, + -3.78220230e-04f, -3.79850587e-04f, -3.78754989e-04f, -3.74987358e-04f, + -3.68568872e-04f, -3.59511330e-04f, -3.47888205e-04f, -3.33821666e-04f, + -3.17386918e-04f, -2.98721551e-04f, -2.77916847e-04f, -2.55178573e-04f, + -2.30688519e-04f, -2.04596855e-04f, -1.77101686e-04f, -1.48416144e-04f, + -1.18791903e-04f, -8.83978597e-05f, -5.74821966e-05f, -2.62868833e-05f, + 4.92815189e-06f, 3.59504591e-05f, 6.65434569e-05f, 9.64155022e-05f, + 1.25420450e-04f, 1.53244448e-04f, 1.79726500e-04f, 2.04644719e-04f, + 2.27732573e-04f, 2.48876343e-04f, 2.67856021e-04f, 2.84480504e-04f, + 2.98654254e-04f, 3.10195638e-04f, 3.18997467e-04f, 3.24986682e-04f, + 3.28010014e-04f, 3.28075276e-04f, 3.25081644e-04f, 3.18989006e-04f, + 3.09862065e-04f, 2.97618732e-04f, 2.82358452e-04f, 2.64078432e-04f, + 2.42867446e-04f, 2.18818442e-04f, 1.92028457e-04f, 1.62575887e-04f, + 1.30632731e-04f, 9.63241083e-05f, 5.98270577e-05f, 2.13018198e-05f, + -1.90650636e-05f, -6.10476572e-05f, -1.04504009e-04f, -1.49141212e-04f, + -1.94818380e-04f, -2.41269356e-04f, -2.88284124e-04f, -3.35636339e-04f, + -3.83078353e-04f, -4.30375313e-04f, -4.77325331e-04f, -5.23684425e-04f, + -5.69213416e-04f, -6.13776473e-04f, -6.57085679e-04f, -6.98973293e-04f, + -7.39240301e-04f, -7.77721569e-04f, -8.14237506e-04f, -8.48630685e-04f, + -8.80760950e-04f, -9.10481345e-04f, -9.37672148e-04f, -9.62299433e-04f, + -9.84157343e-04f, -1.00327325e-03f, -1.01946138e-03f, -1.03289356e-03f, + -1.04338135e-03f, -1.05093085e-03f, -1.05547810e-03f, -1.05718776e-03f, + -1.05596199e-03f, -1.05199493e-03f, -1.04512596e-03f, -1.03564457e-03f, + -1.02347745e-03f, -1.00881749e-03f, -9.91651851e-04f, -9.72214477e-04f, + -9.50589764e-04f, -9.26823063e-04f, -9.01169667e-04f, -8.73693053e-04f, + -8.44651495e-04f, -8.14015623e-04f, -7.82096657e-04f, -7.48978970e-04f, + -7.14918806e-04f, -6.79915120e-04f, -6.44197160e-04f, -6.07973052e-04f, + -5.71340571e-04f, -5.34494985e-04f, -4.97625899e-04f, -4.60763150e-04f, + -4.24154408e-04f, -3.87781959e-04f, -3.51977273e-04f, -3.16708720e-04f, + -2.82209173e-04f, -2.48453876e-04f, -2.15649909e-04f, -1.83919068e-04f, + -1.53211006e-04f, -1.23682813e-04f, -9.53777996e-05f, -6.83598308e-05f, + -4.27405102e-05f, -1.84644678e-05f, 4.40560628e-06f, 2.57526124e-05f, + 4.57070844e-05f, 6.42470547e-05f, 8.12438667e-05f, 9.67784543e-05f, + 1.10883532e-04f, 1.23557473e-04f, 1.34866254e-04f, 1.44751012e-04f, + 1.53323607e-04f, 1.60602540e-04f, 1.66572422e-04f, 1.71555319e-04f, + 1.75228695e-04f, 1.77785942e-04f, 1.79287868e-04f, 1.80066421e-04f, + 1.79856554e-04f, 1.78600745e-04f, 1.76640402e-04f, 1.74149693e-04f, + 1.70998956e-04f, 1.67101361e-04f, 1.62736605e-04f, 1.57955365e-04f, + 1.52783832e-04f, 1.47235562e-04f, 1.41440247e-04f, 1.35411748e-04f, + 1.29174003e-04f, 1.22883471e-04f, 1.16507352e-04f, 1.10062224e-04f, + 1.03554135e-04f, 9.72109292e-05f, 9.09057438e-05f, 8.47069282e-05f, + 7.86275439e-05f, 7.27295443e-05f, 6.70460602e-05f, 6.14870567e-05f, + 5.62358268e-05f, 5.11680423e-05f, 4.63971100e-05f, 4.18604282e-05f, + 3.75581730e-05f, 3.34956971e-05f, 2.95613583e-05f, 2.62367667e-05f, + 2.30173784e-05f, 1.99216834e-05f, 1.73717570e-05f, 1.47187235e-05f, + 1.23944987e-05f, 1.06041654e-05f, 8.75464375e-06f, 1.94324625e-05f, }; // Minimum-phase equiripple FIR lowpass diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f493bb14dd..048b8b1633 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -221,8 +221,12 @@ void Avatar::updateAvatarEntities() { return; } - if (getID() == QUuid() || getID() == AVATAR_SELF_ID) { - return; // wait until MyAvatar gets an ID before doing this. + if (getID().isNull() || + getID() == AVATAR_SELF_ID || + DependencyManager::get()->getSessionUUID() == QUuid()) { + // wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong -- + // things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent". + return; } auto treeRenderer = DependencyManager::get(); @@ -1801,5 +1805,9 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() { } auto result = _skeletonModel->getScriptableModel(); result.objectID = getSessionUUID().isNull() ? AVATAR_SELF_ID : getSessionUUID(); + { + std::lock_guard lock(_materialsLock); + result.appendMaterials(_materials); + } return result; -} \ No newline at end of file +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index b2a494230b..b25df633c0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -33,6 +33,10 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : { // SkeletonModels, and by extention Avatars, use Dual Quaternion skinning. _useDualQuaternionSkinning = true; + + // Avatars all cast shadow + _canCastShadow = true; + assert(_owningAvatar); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 1bbc8cc1a5..08eee947ef 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2058,24 +2058,34 @@ static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); -static const int JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION = 0; -static const int JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION = 1; +enum class JsonAvatarFrameVersion : int { + JointRotationsInRelativeFrame = 0, + JointRotationsInAbsoluteFrame, + JointDefaultPoseBits +}; QJsonValue toJsonValue(const JointData& joint) { QJsonArray result; result.push_back(toJsonValue(joint.rotation)); result.push_back(toJsonValue(joint.translation)); + result.push_back(QJsonValue(joint.rotationIsDefaultPose)); + result.push_back(QJsonValue(joint.translationIsDefaultPose)); return result; } -JointData jointDataFromJsonValue(const QJsonValue& json) { +JointData jointDataFromJsonValue(int version, const QJsonValue& json) { JointData result; if (json.isArray()) { QJsonArray array = json.toArray(); result.rotation = quatFromJsonValue(array[0]); - result.rotationIsDefaultPose = false; result.translation = vec3FromJsonValue(array[1]); - result.translationIsDefaultPose = false; + if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) { + result.rotationIsDefaultPose = array[2].toBool(); + result.translationIsDefaultPose = array[3].toBool(); + } else { + result.rotationIsDefaultPose = false; + result.translationIsDefaultPose = false; + } } return result; } @@ -2083,7 +2093,7 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { QJsonObject AvatarData::toJson() const { QJsonObject root; - root[JSON_AVATAR_VERSION] = JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION; + root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits; if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); @@ -2158,7 +2168,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { version = json[JSON_AVATAR_VERSION].toInt(); } else { // initial data did not have a version field. - version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION; + version = (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame; } if (json.contains(JSON_AVATAR_BODY_MODEL)) { @@ -2235,7 +2245,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { // } if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { - if (version == JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION) { + if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) { // because we don't have the full joint hierarchy skeleton of the model, // we can't properly convert from relative rotations into absolute rotations. quint64 now = usecTimestampNow(); @@ -2247,7 +2257,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); jointArray.reserve(jointArrayJson.size()); for (const auto& jointJson : jointArrayJson) { - auto joint = jointDataFromJsonValue(jointJson); + auto joint = jointDataFromJsonValue(version, jointJson); jointArray.push_back(joint); } setRawJointData(jointArray); @@ -2558,4 +2568,4 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value[EntityID] = binaryEntityProperties; } -} \ No newline at end of file +} diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index a765e66bbc..0407c8508c 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -33,65 +33,8 @@ #include "FBXBaker.h" -#ifdef _WIN32 -#pragma warning( push ) -#pragma warning( disable : 4267 ) -#endif - -#include -#include - -#ifdef _WIN32 -#pragma warning( pop ) -#endif - - -FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter, - const QString& bakedOutputDir, const QString& originalOutputDir) : - _fbxURL(fbxURL), - _bakedOutputDir(bakedOutputDir), - _originalOutputDir(originalOutputDir), - _textureThreadGetter(textureThreadGetter) -{ - -} - -FBXBaker::~FBXBaker() { - if (_tempDir.exists()) { - if (!_tempDir.remove(_originalFBXFilePath)) { - qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalFBXFilePath; - } - if (!_tempDir.rmdir(".")) { - qCWarning(model_baking) << "Failed to remove temporary directory:" << _tempDir; - } - } -} - -void FBXBaker::abort() { - Baker::abort(); - - // tell our underlying TextureBaker instances to abort - // the FBXBaker will wait until all are aborted before emitting its own abort signal - for (auto& textureBaker : _bakingTextures) { - textureBaker->abort(); - } -} - -void FBXBaker::bake() { - qDebug() << "FBXBaker" << _fbxURL << "bake starting"; - - auto tempDir = PathUtils::generateTemporaryDir(); - - if (tempDir.isEmpty()) { - handleError("Failed to create a temporary directory."); - return; - } - - _tempDir = tempDir; - - _originalFBXFilePath = _tempDir.filePath(_fbxURL.fileName()); - qDebug() << "Made temporary dir " << _tempDir; - qDebug() << "Origin file path: " << _originalFBXFilePath; +void FBXBaker::bake() { + qDebug() << "FBXBaker" << _modelURL << "bake starting"; // setup the output folder for the results of this bake setupOutputFolder(); @@ -152,7 +95,7 @@ void FBXBaker::setupOutputFolder() { } // attempt to make the output folder if (!QDir().mkpath(_originalOutputDir)) { - handleError("Failed to create FBX output folder " + _bakedOutputDir); + handleError("Failed to create FBX output folder " + _originalOutputDir); return; } } @@ -160,25 +103,25 @@ void FBXBaker::setupOutputFolder() { void FBXBaker::loadSourceFBX() { // check if the FBX is local or first needs to be downloaded - if (_fbxURL.isLocalFile()) { + if (_modelURL.isLocalFile()) { // load up the local file - QFile localFBX { _fbxURL.toLocalFile() }; + QFile localFBX { _modelURL.toLocalFile() }; - qDebug() << "Local file url: " << _fbxURL << _fbxURL.toString() << _fbxURL.toLocalFile() << ", copying to: " << _originalFBXFilePath; + qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath; if (!localFBX.exists()) { //QMessageBox::warning(this, "Could not find " + _fbxURL.toString(), ""); - handleError("Could not find " + _fbxURL.toString()); + handleError("Could not find " + _modelURL.toString()); return; } // make a copy in the output folder if (!_originalOutputDir.isEmpty()) { - qDebug() << "Copying to: " << _originalOutputDir << "/" << _fbxURL.fileName(); - localFBX.copy(_originalOutputDir + "/" + _fbxURL.fileName()); + qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName(); + localFBX.copy(_originalOutputDir + "/" + _modelURL.fileName()); } - localFBX.copy(_originalFBXFilePath); + localFBX.copy(_originalModelFilePath); // emit our signal to start the import of the FBX source copy emit sourceCopyReadyToLoad(); @@ -193,9 +136,9 @@ void FBXBaker::loadSourceFBX() { networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - networkRequest.setUrl(_fbxURL); + networkRequest.setUrl(_modelURL); - qCDebug(model_baking) << "Downloading" << _fbxURL; + qCDebug(model_baking) << "Downloading" << _modelURL; auto networkReply = networkAccessManager.get(networkRequest); connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply); @@ -206,20 +149,20 @@ void FBXBaker::handleFBXNetworkReply() { auto requestReply = qobject_cast(sender()); if (requestReply->error() == QNetworkReply::NoError) { - qCDebug(model_baking) << "Downloaded" << _fbxURL; + qCDebug(model_baking) << "Downloaded" << _modelURL; // grab the contents of the reply and make a copy in the output folder - QFile copyOfOriginal(_originalFBXFilePath); + QFile copyOfOriginal(_originalModelFilePath); - qDebug(model_baking) << "Writing copy of original FBX to" << _originalFBXFilePath << copyOfOriginal.fileName(); + qDebug(model_baking) << "Writing copy of original FBX to" << _originalModelFilePath << copyOfOriginal.fileName(); if (!copyOfOriginal.open(QIODevice::WriteOnly)) { // add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made - handleError("Could not create copy of " + _fbxURL.toString() + " (Failed to open " + _originalFBXFilePath + ")"); + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")"); return; } if (copyOfOriginal.write(requestReply->readAll()) == -1) { - handleError("Could not create copy of " + _fbxURL.toString() + " (Failed to write)"); + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)"); return; } @@ -227,98 +170,32 @@ void FBXBaker::handleFBXNetworkReply() { copyOfOriginal.close(); if (!_originalOutputDir.isEmpty()) { - copyOfOriginal.copy(_originalOutputDir + "/" + _fbxURL.fileName()); + copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName()); } // emit our signal to start the import of the FBX source copy emit sourceCopyReadyToLoad(); } else { // add an error to our list stating that the FBX could not be downloaded - handleError("Failed to download " + _fbxURL.toString()); + handleError("Failed to download " + _modelURL.toString()); } } void FBXBaker::importScene() { - qDebug() << "file path: " << _originalFBXFilePath.toLocal8Bit().data() << QDir(_originalFBXFilePath).exists(); + qDebug() << "file path: " << _originalModelFilePath.toLocal8Bit().data() << QDir(_originalModelFilePath).exists(); - QFile fbxFile(_originalFBXFilePath); + QFile fbxFile(_originalModelFilePath); if (!fbxFile.open(QIODevice::ReadOnly)) { - handleError("Error opening " + _originalFBXFilePath + " for reading"); + handleError("Error opening " + _originalModelFilePath + " for reading"); return; } FBXReader reader; - qCDebug(model_baking) << "Parsing" << _fbxURL; + qCDebug(model_baking) << "Parsing" << _modelURL; _rootNode = reader._rootNode = reader.parseFBX(&fbxFile); - _geometry = reader.extractFBXGeometry({}, _fbxURL.toString()); - _textureContent = reader._textureContent; -} - -QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { - auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - - if (texturePath.startsWith(fbxPath)) { - // texture path is a child of the FBX path, return the texture path without the fbx path - return texturePath.mid(fbxPath.length()); - } else { - // the texture path was not a child of the FBX path, return the empty string - return ""; - } -} - -QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { - // first make sure we have a unique base name for this texture - // in case another texture referenced by this model has the same base name - auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; - - QString bakedTextureFileName { textureFileInfo.completeBaseName() }; - - if (nameMatches > 0) { - // there are already nameMatches texture with this name - // append - and that number to our baked texture file name so that it is unique - bakedTextureFileName += "-" + QString::number(nameMatches); - } - - bakedTextureFileName += BAKED_TEXTURE_EXT; - - // increment the number of name matches - ++nameMatches; - - return bakedTextureFileName; -} - -QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) { - - QUrl urlToTexture; - - auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); - - if (isEmbedded) { - urlToTexture = _fbxURL.toString() + "/" + apparentRelativePath.filePath(); - } else { - if (textureFileInfo.exists() && textureFileInfo.isFile()) { - // set the texture URL to the local texture that we have confirmed exists - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // external texture that we'll need to download or find - - // this is a relative file path which will require different handling - // depending on the location of the original FBX - if (_fbxURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { - // the absolute path we ran into for the texture in the FBX exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the FBX to match the behaviour of interface - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); - } - } - } - - return urlToTexture; + _geometry = reader.extractFBXGeometry({}, _modelURL.toString()); + _textureContentMap = reader._textureContent; } void FBXBaker::rewriteAndBakeSceneModels() { @@ -344,176 +221,25 @@ void FBXBaker::rewriteAndBakeSceneModels() { // TODO Pull this out of _geometry instead so we don't have to reprocess it auto extractedMesh = FBXReader::extractMesh(objectChild, meshIndex, false); - auto& mesh = extractedMesh.mesh; - - if (mesh.wasCompressed) { - handleError("Cannot re-bake a file that contains compressed mesh"); - return; - } - - Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size()); - Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); - Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); - - int64_t numTriangles { 0 }; - for (auto& part : mesh.parts) { - if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) { - handleWarning("Found a mesh part with invalid index data, skipping"); + + // Callback to get MaterialID + GetMaterialIDCallback materialIDcallback = [&extractedMesh](int partIndex) { + return extractedMesh.partMaterialTextures[partIndex].first; + }; + + // Compress mesh information and store in dracoMeshNode + FBXNode dracoMeshNode; + bool success = compressMesh(extractedMesh.mesh, hasDeformers, dracoMeshNode, materialIDcallback); + + // if bake fails - return, if there were errors and continue, if there were warnings. + if (!success) { + if (hasErrors()) { + return; + } else if (hasWarnings()) { continue; } - numTriangles += part.quadTrianglesIndices.size() / 3; - numTriangles += part.triangleIndices.size() / 3; } - - if (numTriangles == 0) { - continue; - } - - draco::TriangleSoupMeshBuilder meshBuilder; - - meshBuilder.Start(numTriangles); - - bool hasNormals { mesh.normals.size() > 0 }; - bool hasColors { mesh.colors.size() > 0 }; - bool hasTexCoords { mesh.texCoords.size() > 0 }; - bool hasTexCoords1 { mesh.texCoords1.size() > 0 }; - bool hasPerFaceMaterials { mesh.parts.size() > 1 - || extractedMesh.partMaterialTextures[0].first != 0 }; - bool needsOriginalIndices { hasDeformers }; - - int normalsAttributeID { -1 }; - int colorsAttributeID { -1 }; - int texCoordsAttributeID { -1 }; - int texCoords1AttributeID { -1 }; - int faceMaterialAttributeID { -1 }; - int originalIndexAttributeID { -1 }; - - const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION, - 3, draco::DT_FLOAT32); - if (needsOriginalIndices) { - originalIndexAttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX, - 1, draco::DT_INT32); - } - - if (hasNormals) { - normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL, - 3, draco::DT_FLOAT32); - } - if (hasColors) { - colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR, - 3, draco::DT_FLOAT32); - } - if (hasTexCoords) { - texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD, - 2, draco::DT_FLOAT32); - } - if (hasTexCoords1) { - texCoords1AttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1, - 2, draco::DT_FLOAT32); - } - if (hasPerFaceMaterials) { - faceMaterialAttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID, - 1, draco::DT_UINT16); - } - - - auto partIndex = 0; - draco::FaceIndex face; - for (auto& part : mesh.parts) { - const auto& matTex = extractedMesh.partMaterialTextures[partIndex]; - uint16_t materialID = matTex.first; - - auto addFace = [&](QVector& indices, int index, draco::FaceIndex face) { - int32_t idx0 = indices[index]; - int32_t idx1 = indices[index + 1]; - int32_t idx2 = indices[index + 2]; - - if (hasPerFaceMaterials) { - meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID); - } - - meshBuilder.SetAttributeValuesForFace(positionAttributeID, face, - &mesh.vertices[idx0], &mesh.vertices[idx1], - &mesh.vertices[idx2]); - - if (needsOriginalIndices) { - meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face, - &mesh.originalIndices[idx0], - &mesh.originalIndices[idx1], - &mesh.originalIndices[idx2]); - } - if (hasNormals) { - meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face, - &mesh.normals[idx0], &mesh.normals[idx1], - &mesh.normals[idx2]); - } - if (hasColors) { - meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face, - &mesh.colors[idx0], &mesh.colors[idx1], - &mesh.colors[idx2]); - } - if (hasTexCoords) { - meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face, - &mesh.texCoords[idx0], &mesh.texCoords[idx1], - &mesh.texCoords[idx2]); - } - if (hasTexCoords1) { - meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face, - &mesh.texCoords1[idx0], &mesh.texCoords1[idx1], - &mesh.texCoords1[idx2]); - } - }; - - for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) { - addFace(part.quadTrianglesIndices, i, face++); - } - - for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) { - addFace(part.triangleIndices, i, face++); - } - - partIndex++; - } - - auto dracoMesh = meshBuilder.Finalize(); - - if (!dracoMesh) { - handleWarning("Failed to finalize the baking of a draco Geometry node"); - continue; - } - - // we need to modify unique attribute IDs for custom attributes - // so the attributes are easily retrievable on the other side - if (hasPerFaceMaterials) { - dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID); - } - - if (hasTexCoords1) { - dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1); - } - - if (needsOriginalIndices) { - dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX); - } - - draco::Encoder encoder; - - encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); - encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12); - encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); - encoder.SetSpeedOptions(0, 5); - - draco::EncoderBuffer buffer; - encoder.EncodeMeshToBuffer(*dracoMesh, &buffer); - - FBXNode dracoMeshNode; - dracoMeshNode.name = "DracoMesh"; - auto value = QVariant::fromValue(QByteArray(buffer.data(), (int) buffer.size())); - dracoMeshNode.properties.append(value); - + objectChild.children.push_back(dracoMeshNode); static const std::vector nodeNamesToDelete { @@ -590,69 +316,25 @@ void FBXBaker::rewriteAndBakeSceneTextures() { for (FBXNode& textureChild : object->children) { if (textureChild.name == "RelativeFilename") { + QString fbxTextureFileName { textureChild.properties.at(0).toString() }; + + // grab the ID for this texture so we can figure out the + // texture type from the loaded materials + auto textureID { object->properties[0].toString() }; + auto textureType = textureTypes[textureID]; - // use QFileInfo to easily split up the existing texture filename into its components - QString fbxTextureFileName { textureChild.properties.at(0).toByteArray() }; - QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") }; + // Compress the texture information and return the new filename to be added into the FBX scene + auto bakedTextureFile = compressTexture(fbxTextureFileName, textureType); - if (hasErrors()) { - return; - } - - if (textureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) { - // re-baking an FBX that already references baked textures is a fail - // so we add an error and return from here - handleError("Cannot re-bake a file that references compressed textures"); - - return; - } - - if (!image::getSupportedFormats().contains(textureFileInfo.suffix())) { - // this is a texture format we don't bake, skip it - handleWarning(fbxTextureFileName + " is not a bakeable texture format"); - continue; - } - - // make sure this texture points to something and isn't one we've already re-mapped - if (!textureFileInfo.filePath().isEmpty()) { - // check if this was an embedded texture we have already have in-memory content for - auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit()); - - // figure out the URL to this texture, embedded or external - auto urlToTexture = getTextureURL(textureFileInfo, fbxTextureFileName, - !textureContent.isNull()); - - QString bakedTextureFileName; - if (_remappedTexturePaths.contains(urlToTexture)) { - bakedTextureFileName = _remappedTexturePaths[urlToTexture]; - } else { - // construct the new baked texture file name and file path - // ensuring that the baked texture will have a unique name - // even if there was another texture with the same name at a different path - bakedTextureFileName = createBakedTextureFileName(textureFileInfo); - _remappedTexturePaths[urlToTexture] = bakedTextureFileName; - } - - qCDebug(model_baking).noquote() << "Re-mapping" << fbxTextureFileName - << "to" << bakedTextureFileName; - - QString bakedTextureFilePath { - _bakedOutputDir + "/" + bakedTextureFileName - }; - - // write the new filename into the FBX scene - textureChild.properties[0] = bakedTextureFileName.toLocal8Bit(); - - if (!_bakingTextures.contains(urlToTexture)) { - _outputFiles.push_back(bakedTextureFilePath); - - // grab the ID for this texture so we can figure out the - // texture type from the loaded materials - QString textureID { object->properties[0].toByteArray() }; - auto textureType = textureTypes[textureID]; - - // bake this texture asynchronously - bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent); + // If no errors or warnings have occurred during texture compression add the filename to the FBX scene + if (!bakedTextureFile.isNull()) { + textureChild.properties[0] = bakedTextureFile; + } else { + // if bake fails - return, if there were errors and continue, if there were warnings. + if (hasErrors()) { + return; + } else if (hasWarnings()) { + continue; } } } @@ -671,172 +353,26 @@ void FBXBaker::rewriteAndBakeSceneTextures() { } } -void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, - const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) { - // start a bake for this texture and add it to our list to keep track of - QSharedPointer bakingTexture { - new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent), - &TextureBaker::deleteLater - }; - - // make sure we hear when the baking texture is done or aborted - connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); - connect(bakingTexture.data(), &TextureBaker::aborted, this, &FBXBaker::handleAbortedTexture); - - // keep a shared pointer to the baking texture - _bakingTextures.insert(textureURL, bakingTexture); - - // start baking the texture on one of our available worker threads - bakingTexture->moveToThread(_textureThreadGetter()); - QMetaObject::invokeMethod(bakingTexture.data(), "bake"); -} - -void FBXBaker::handleBakedTexture() { - TextureBaker* bakedTexture = qobject_cast(sender()); - - // make sure we haven't already run into errors, and that this is a valid texture - if (bakedTexture) { - if (!shouldStop()) { - if (!bakedTexture->hasErrors()) { - if (!_originalOutputDir.isEmpty()) { - // we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture - - // use the path to the texture being baked to determine if this was an embedded or a linked texture - - // it is embeddded if the texure being baked was inside a folder with the name of the FBX - // since that is the fake URL we provide when baking external textures - - if (!_fbxURL.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original FBX - - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); - - QFile originalTextureFile { - _originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName() - }; - - if (relativeTexturePath.length() > 0) { - // make the folders needed by the relative path - } - - if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { - qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _fbxURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _fbxURL.toString()); - return; - } - } - } - - - // now that this texture has been baked and handled, we can remove that TextureBaker from our hash - _bakingTextures.remove(bakedTexture->getTextureURL()); - - checkIfTexturesFinished(); - } else { - // there was an error baking this texture - add it to our list of errors - _errorList.append(bakedTexture->getErrors()); - - // we don't emit finished yet so that the other textures can finish baking first - _pendingErrorEmission = true; - - // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list - _bakingTextures.remove(bakedTexture->getTextureURL()); - - // abort any other ongoing texture bakes since we know we'll end up failing - for (auto& bakingTexture : _bakingTextures) { - bakingTexture->abort(); - } - - checkIfTexturesFinished(); - } - } else { - // we have errors to attend to, so we don't do extra processing for this texture - // but we do need to remove that TextureBaker from our list - // and then check if we're done with all textures - _bakingTextures.remove(bakedTexture->getTextureURL()); - - checkIfTexturesFinished(); - } - } -} - -void FBXBaker::handleAbortedTexture() { - // grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore - TextureBaker* bakedTexture = qobject_cast(sender()); - - if (bakedTexture) { - _bakingTextures.remove(bakedTexture->getTextureURL()); - } - - // since a texture we were baking aborted, our status is also aborted - _shouldAbort.store(true); - - // abort any other ongoing texture bakes since we know we'll end up failing - for (auto& bakingTexture : _bakingTextures) { - bakingTexture->abort(); - } - - checkIfTexturesFinished(); -} - void FBXBaker::exportScene() { // save the relative path to this FBX inside our passed output folder - auto fileName = _fbxURL.fileName(); + auto fileName = _modelURL.fileName(); auto baseName = fileName.left(fileName.lastIndexOf('.')); auto bakedFilename = baseName + BAKED_FBX_EXTENSION; - _bakedFBXFilePath = _bakedOutputDir + "/" + bakedFilename; + _bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename; auto fbxData = FBXWriter::encodeFBX(_rootNode); - QFile bakedFile(_bakedFBXFilePath); + QFile bakedFile(_bakedModelFilePath); if (!bakedFile.open(QIODevice::WriteOnly)) { - handleError("Error opening " + _bakedFBXFilePath + " for writing"); + handleError("Error opening " + _bakedModelFilePath + " for writing"); return; } bakedFile.write(fbxData); - _outputFiles.push_back(_bakedFBXFilePath); + _outputFiles.push_back(_bakedModelFilePath); - qCDebug(model_baking) << "Exported" << _fbxURL << "with re-written paths to" << _bakedFBXFilePath; -} - -void FBXBaker::checkIfTexturesFinished() { - // check if we're done everything we need to do for this FBX - // and emit our finished signal if we're done - - if (_bakingTextures.isEmpty()) { - if (shouldStop()) { - // if we're checking for completion but we have errors - // that means one or more of our texture baking operations failed - - if (_pendingErrorEmission) { - setIsFinished(true); - } - - return; - } else { - qCDebug(model_baking) << "Finished baking, emitting finsihed" << _fbxURL; - - setIsFinished(true); - } - } -} - -void FBXBaker::setWasAborted(bool wasAborted) { - if (wasAborted != _wasAborted.load()) { - Baker::setWasAborted(wasAborted); - - if (wasAborted) { - qCDebug(model_baking) << "Aborted baking" << _fbxURL; - } - } + qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath; } diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index a6034ee2b7..2888a60f73 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -19,7 +19,7 @@ #include "Baker.h" #include "TextureBaker.h" - +#include "ModelBaker.h" #include "ModelBakingLoggingCategory.h" #include @@ -30,21 +30,13 @@ static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; using TextureBakerThreadGetter = std::function; -class FBXBaker : public Baker { +class FBXBaker : public ModelBaker { Q_OBJECT public: - FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter, - const QString& bakedOutputDir, const QString& originalOutputDir = ""); - ~FBXBaker() override; - - QUrl getFBXUrl() const { return _fbxURL; } - QString getBakedFBXFilePath() const { return _bakedFBXFilePath; } - - virtual void setWasAborted(bool wasAborted) override; + using ModelBaker::ModelBaker; public slots: virtual void bake() override; - virtual void abort() override; signals: void sourceCopyReadyToLoad(); @@ -52,8 +44,6 @@ signals: private slots: void bakeSourceCopy(); void handleFBXNetworkReply(); - void handleBakedTexture(); - void handleAbortedTexture(); private: void setupOutputFolder(); @@ -64,38 +54,12 @@ private: void rewriteAndBakeSceneModels(); void rewriteAndBakeSceneTextures(); void exportScene(); - void removeEmbeddedMediaFolder(); - - void checkIfTexturesFinished(); - - QString createBakedTextureFileName(const QFileInfo& textureFileInfo); - QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false); - - void bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir, - const QString& bakedFilename, const QByteArray& textureContent = QByteArray()); - - QUrl _fbxURL; FBXNode _rootNode; FBXGeometry* _geometry; - QHash _textureContent; - - QString _bakedFBXFilePath; - - QString _bakedOutputDir; - - // If set, the original FBX and textures will also be copied here - QString _originalOutputDir; - - QDir _tempDir; - QString _originalFBXFilePath; - - QMultiHash> _bakingTextures; QHash _textureNameMatchCount; QHash _remappedTexturePaths; - TextureBakerThreadGetter _textureThreadGetter; - bool _pendingErrorEmission { false }; }; diff --git a/libraries/baking/src/JSBaker.cpp b/libraries/baking/src/JSBaker.cpp index a97a7fe5b3..9932ad633e 100644 --- a/libraries/baking/src/JSBaker.cpp +++ b/libraries/baking/src/JSBaker.cpp @@ -190,7 +190,7 @@ bool JSBaker::handleMultiLineComments(QTextStream& in) { while (!in.atEnd()) { in >> character; if (character == '*') { - if (in.read(1) == '/') { + if (in.read(1) == "/") { return true; } } @@ -228,7 +228,7 @@ bool JSBaker::isSpecialCharacter(QChar c) { // If previous character is a special character, maybe don't omit new line (depends on next character as well) bool JSBaker::isSpecialCharacterPrevious(QChar c) { return (c == '\'' || c == '$' || c == '_' || c == '}' || c == ']' || c == ')' || c == '+' || c == '-' - || c == '"' || c == "'"); + || c == '"' || c == '\''); } // If next character is a special character, maybe don't omit new line (depends on previous character as well) @@ -243,5 +243,5 @@ bool JSBaker::isSpaceOrTab(QChar c) { // Check If the currentCharacter is " or ' or ` bool JSBaker::isQuote(QChar c) { - return (c == '"' || c == "'" || c == '`'); + return (c == '"' || c == '\'' || c == '`'); } diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp new file mode 100644 index 0000000000..16a0c89c7f --- /dev/null +++ b/libraries/baking/src/ModelBaker.cpp @@ -0,0 +1,521 @@ +// +// ModelBaker.cpp +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelBaker.h" + +#include + +#include +#include + +#ifdef _WIN32 +#pragma warning( push ) +#pragma warning( disable : 4267 ) +#endif + +#include +#include + +#ifdef _WIN32 +#pragma warning( pop ) +#endif + +ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, + const QString& bakedOutputDirectory, const QString& originalOutputDirectory) : + _modelURL(inputModelURL), + _bakedOutputDir(bakedOutputDirectory), + _originalOutputDir(originalOutputDirectory), + _textureThreadGetter(inputTextureThreadGetter) +{ + auto tempDir = PathUtils::generateTemporaryDir(); + + if (tempDir.isEmpty()) { + handleError("Failed to create a temporary directory."); + return; + } + + _modelTempDir = tempDir; + + _originalModelFilePath = _modelTempDir.filePath(_modelURL.fileName()); + qDebug() << "Made temporary dir " << _modelTempDir; + qDebug() << "Origin file path: " << _originalModelFilePath; + +} + +ModelBaker::~ModelBaker() { + if (_modelTempDir.exists()) { + if (!_modelTempDir.remove(_originalModelFilePath)) { + qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalModelFilePath; + } + if (!_modelTempDir.rmdir(".")) { + qCWarning(model_baking) << "Failed to remove temporary directory:" << _modelTempDir; + } + } +} + +void ModelBaker::abort() { + Baker::abort(); + + // tell our underlying TextureBaker instances to abort + // the ModelBaker will wait until all are aborted before emitting its own abort signal + for (auto& textureBaker : _bakingTextures) { + textureBaker->abort(); + } +} + +bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) { + if (mesh.wasCompressed) { + handleError("Cannot re-bake a file that contains compressed mesh"); + return false; + } + + Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size()); + Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); + Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); + + int64_t numTriangles{ 0 }; + for (auto& part : mesh.parts) { + if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) { + handleWarning("Found a mesh part with invalid index data, skipping"); + continue; + } + numTriangles += part.quadTrianglesIndices.size() / 3; + numTriangles += part.triangleIndices.size() / 3; + } + + if (numTriangles == 0) { + return false; + } + + draco::TriangleSoupMeshBuilder meshBuilder; + + meshBuilder.Start(numTriangles); + + bool hasNormals{ mesh.normals.size() > 0 }; + bool hasColors{ mesh.colors.size() > 0 }; + bool hasTexCoords{ mesh.texCoords.size() > 0 }; + bool hasTexCoords1{ mesh.texCoords1.size() > 0 }; + bool hasPerFaceMaterials = (materialIDCallback) ? (mesh.parts.size() > 1 || materialIDCallback(0) != 0 ) : true; + bool needsOriginalIndices{ hasDeformers }; + + int normalsAttributeID { -1 }; + int colorsAttributeID { -1 }; + int texCoordsAttributeID { -1 }; + int texCoords1AttributeID { -1 }; + int faceMaterialAttributeID { -1 }; + int originalIndexAttributeID { -1 }; + + const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION, + 3, draco::DT_FLOAT32); + if (needsOriginalIndices) { + originalIndexAttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX, + 1, draco::DT_INT32); + } + + if (hasNormals) { + normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL, + 3, draco::DT_FLOAT32); + } + if (hasColors) { + colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR, + 3, draco::DT_FLOAT32); + } + if (hasTexCoords) { + texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD, + 2, draco::DT_FLOAT32); + } + if (hasTexCoords1) { + texCoords1AttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1, + 2, draco::DT_FLOAT32); + } + if (hasPerFaceMaterials) { + faceMaterialAttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID, + 1, draco::DT_UINT16); + } + + auto partIndex = 0; + draco::FaceIndex face; + uint16_t materialID; + + for (auto& part : mesh.parts) { + materialID = (materialIDCallback) ? materialIDCallback(partIndex) : partIndex; + + auto addFace = [&](QVector& indices, int index, draco::FaceIndex face) { + int32_t idx0 = indices[index]; + int32_t idx1 = indices[index + 1]; + int32_t idx2 = indices[index + 2]; + + if (hasPerFaceMaterials) { + meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID); + } + + meshBuilder.SetAttributeValuesForFace(positionAttributeID, face, + &mesh.vertices[idx0], &mesh.vertices[idx1], + &mesh.vertices[idx2]); + + if (needsOriginalIndices) { + meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face, + &mesh.originalIndices[idx0], + &mesh.originalIndices[idx1], + &mesh.originalIndices[idx2]); + } + if (hasNormals) { + meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face, + &mesh.normals[idx0], &mesh.normals[idx1], + &mesh.normals[idx2]); + } + if (hasColors) { + meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face, + &mesh.colors[idx0], &mesh.colors[idx1], + &mesh.colors[idx2]); + } + if (hasTexCoords) { + meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face, + &mesh.texCoords[idx0], &mesh.texCoords[idx1], + &mesh.texCoords[idx2]); + } + if (hasTexCoords1) { + meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face, + &mesh.texCoords1[idx0], &mesh.texCoords1[idx1], + &mesh.texCoords1[idx2]); + } + }; + + for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) { + addFace(part.quadTrianglesIndices, i, face++); + } + + for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) { + addFace(part.triangleIndices, i, face++); + } + + partIndex++; + } + + auto dracoMesh = meshBuilder.Finalize(); + + if (!dracoMesh) { + handleWarning("Failed to finalize the baking of a draco Geometry node"); + return false; + } + + // we need to modify unique attribute IDs for custom attributes + // so the attributes are easily retrievable on the other side + if (hasPerFaceMaterials) { + dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID); + } + + if (hasTexCoords1) { + dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1); + } + + if (needsOriginalIndices) { + dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX); + } + + draco::Encoder encoder; + + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); + encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12); + encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); + encoder.SetSpeedOptions(0, 5); + + draco::EncoderBuffer buffer; + encoder.EncodeMeshToBuffer(*dracoMesh, &buffer); + + FBXNode dracoNode; + dracoNode.name = "DracoMesh"; + auto value = QVariant::fromValue(QByteArray(buffer.data(), (int)buffer.size())); + dracoNode.properties.append(value); + + dracoMeshNode = dracoNode; + // Mesh compression successful return true + return true; +} + +QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) { + + QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") }; + + if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) { + // re-baking a model that already references baked textures + // this is an error - return from here + handleError("Cannot re-bake a file that already references compressed textures"); + return QString::null; + } + + if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) { + // this is a texture format we don't bake, skip it + handleWarning(modelTextureFileName + " is not a bakeable texture format"); + return QString::null; + } + + // make sure this texture points to something and isn't one we've already re-mapped + QString textureChild { QString::null }; + if (!modelTextureFileInfo.filePath().isEmpty()) { + // check if this was an embedded texture that we already have in-memory content for + QByteArray textureContent; + + // figure out the URL to this texture, embedded or external + if (!modelTextureFileInfo.filePath().isEmpty()) { + textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit()); + } + auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, !textureContent.isNull()); + + QString bakedTextureFileName; + if (_remappedTexturePaths.contains(urlToTexture)) { + bakedTextureFileName = _remappedTexturePaths[urlToTexture]; + } else { + // construct the new baked texture file name and file path + // ensuring that the baked texture will have a unique name + // even if there was another texture with the same name at a different path + bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo); + _remappedTexturePaths[urlToTexture] = bakedTextureFileName; + } + + qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName + << "to" << bakedTextureFileName; + + QString bakedTextureFilePath{ + _bakedOutputDir + "/" + bakedTextureFileName + }; + + textureChild = bakedTextureFileName; + + if (!_bakingTextures.contains(urlToTexture)) { + _outputFiles.push_back(bakedTextureFilePath); + + // bake this texture asynchronously + bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent); + } + } + + return textureChild; +} + +void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, + const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) { + + // start a bake for this texture and add it to our list to keep track of + QSharedPointer bakingTexture{ + new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent), + &TextureBaker::deleteLater + }; + + // make sure we hear when the baking texture is done or aborted + connect(bakingTexture.data(), &Baker::finished, this, &ModelBaker::handleBakedTexture); + connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture); + + // keep a shared pointer to the baking texture + _bakingTextures.insert(textureURL, bakingTexture); + + // start baking the texture on one of our available worker threads + bakingTexture->moveToThread(_textureThreadGetter()); + QMetaObject::invokeMethod(bakingTexture.data(), "bake"); +} + +void ModelBaker::handleBakedTexture() { + TextureBaker* bakedTexture = qobject_cast(sender()); + qDebug() << "Handling baked texture" << bakedTexture->getTextureURL(); + + // make sure we haven't already run into errors, and that this is a valid texture + if (bakedTexture) { + if (!shouldStop()) { + if (!bakedTexture->hasErrors()) { + if (!_originalOutputDir.isEmpty()) { + // we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture + + // use the path to the texture being baked to determine if this was an embedded or a linked texture + + // it is embeddded if the texure being baked was inside a folder with the name of the model + // since that is the fake URL we provide when baking external textures + + if (!_modelURL.isParentOf(bakedTexture->getTextureURL())) { + // for linked textures we want to save a copy of original texture beside the original model + + qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); + + // check if we have a relative path to use for the texture + auto relativeTexturePath = texturePathRelativeToModel(_modelURL, bakedTexture->getTextureURL()); + + QFile originalTextureFile{ + _originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName() + }; + + if (relativeTexturePath.length() > 0) { + // make the folders needed by the relative path + } + + if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { + qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() + << "for" << _modelURL; + } else { + handleError("Could not save original external texture " + originalTextureFile.fileName() + + " for " + _modelURL.toString()); + return; + } + } + } + + + // now that this texture has been baked and handled, we can remove that TextureBaker from our hash + _bakingTextures.remove(bakedTexture->getTextureURL()); + + checkIfTexturesFinished(); + } else { + // there was an error baking this texture - add it to our list of errors + _errorList.append(bakedTexture->getErrors()); + + // we don't emit finished yet so that the other textures can finish baking first + _pendingErrorEmission = true; + + // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list + _bakingTextures.remove(bakedTexture->getTextureURL()); + + // abort any other ongoing texture bakes since we know we'll end up failing + for (auto& bakingTexture : _bakingTextures) { + bakingTexture->abort(); + } + + checkIfTexturesFinished(); + } + } else { + // we have errors to attend to, so we don't do extra processing for this texture + // but we do need to remove that TextureBaker from our list + // and then check if we're done with all textures + _bakingTextures.remove(bakedTexture->getTextureURL()); + + checkIfTexturesFinished(); + } + } +} + +void ModelBaker::handleAbortedTexture() { + // grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore + TextureBaker* bakedTexture = qobject_cast(sender()); + + qDebug() << "Texture aborted: " << bakedTexture->getTextureURL(); + + if (bakedTexture) { + _bakingTextures.remove(bakedTexture->getTextureURL()); + } + + // since a texture we were baking aborted, our status is also aborted + _shouldAbort.store(true); + + // abort any other ongoing texture bakes since we know we'll end up failing + for (auto& bakingTexture : _bakingTextures) { + bakingTexture->abort(); + } + + checkIfTexturesFinished(); +} + +QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) { + QUrl urlToTexture; + + // use QFileInfo to easily split up the existing texture filename into its components + auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); + + if (isEmbedded) { + urlToTexture = _modelURL.toString() + "/" + apparentRelativePath.filePath(); + } else { + if (textureFileInfo.exists() && textureFileInfo.isFile()) { + // set the texture URL to the local texture that we have confirmed exists + urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); + } else { + // external texture that we'll need to download or find + + // this is a relative file path which will require different handling + // depending on the location of the original model + if (_modelURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { + // the absolute path we ran into for the texture in the model exists on this machine + // so use that file + urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); + } else { + // we didn't find the texture on this machine at the absolute path + // so assume that it is right beside the model to match the behaviour of interface + urlToTexture = _modelURL.resolved(apparentRelativePath.fileName()); + } + } + } + + return urlToTexture; +} + +QString ModelBaker::texturePathRelativeToModel(QUrl modelURL, QUrl textureURL) { + auto modelPath = modelURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); + auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); + + if (texturePath.startsWith(modelPath)) { + // texture path is a child of the model path, return the texture path without the model path + return texturePath.mid(modelPath.length()); + } else { + // the texture path was not a child of the model path, return the empty string + return ""; + } +} + +void ModelBaker::checkIfTexturesFinished() { + // check if we're done everything we need to do for this model + // and emit our finished signal if we're done + + if (_bakingTextures.isEmpty()) { + if (shouldStop()) { + // if we're checking for completion but we have errors + // that means one or more of our texture baking operations failed + + if (_pendingErrorEmission) { + setIsFinished(true); + } + + return; + } else { + qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL; + + setIsFinished(true); + } + } +} + +QString ModelBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { + // first make sure we have a unique base name for this texture + // in case another texture referenced by this model has the same base name + auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; + + QString bakedTextureFileName{ textureFileInfo.completeBaseName() }; + + if (nameMatches > 0) { + // there are already nameMatches texture with this name + // append - and that number to our baked texture file name so that it is unique + bakedTextureFileName += "-" + QString::number(nameMatches); + } + + bakedTextureFileName += BAKED_TEXTURE_EXT; + + // increment the number of name matches + ++nameMatches; + + return bakedTextureFileName; +} + +void ModelBaker::setWasAborted(bool wasAborted) { + if (wasAborted != _wasAborted.load()) { + Baker::setWasAborted(wasAborted); + + if (wasAborted) { + qCDebug(model_baking) << "Aborted baking" << _modelURL; + } + } +} diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h new file mode 100644 index 0000000000..6fd529af92 --- /dev/null +++ b/libraries/baking/src/ModelBaker.h @@ -0,0 +1,79 @@ +// +// ModelBaker.h +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelBaker_h +#define hifi_ModelBaker_h + +#include +#include +#include +#include + +#include "Baker.h" +#include "TextureBaker.h" + +#include "ModelBakingLoggingCategory.h" + +#include + +#include + +using TextureBakerThreadGetter = std::function; +using GetMaterialIDCallback = std::function ; + +class ModelBaker : public Baker { + Q_OBJECT + +public: + ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, + const QString& bakedOutputDirectory, const QString& originalOutputDirectory = ""); + virtual ~ModelBaker(); + + bool compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr); + QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE); + virtual void setWasAborted(bool wasAborted) override; + + QUrl getModelURL() const { return _modelURL; } + QString getBakedModelFilePath() const { return _bakedModelFilePath; } + +public slots: + virtual void abort() override; + +protected: + void checkIfTexturesFinished(); + + QHash _textureContentMap; + QUrl _modelURL; + QString _bakedOutputDir; + QString _originalOutputDir; + QString _bakedModelFilePath; + QDir _modelTempDir; + QString _originalModelFilePath; + +private slots: + void handleBakedTexture(); + void handleAbortedTexture(); + +private: + QString createBakedTextureFileName(const QFileInfo & textureFileInfo); + QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false); + void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir, + const QString & bakedFilename, const QByteArray & textureContent); + QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL); + + TextureBakerThreadGetter _textureThreadGetter; + QMultiHash> _bakingTextures; + QHash _textureNameMatchCount; + QHash _remappedTexturePaths; + bool _pendingErrorEmission{ false }; +}; + +#endif // hifi_ModelBaker_h diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp new file mode 100644 index 0000000000..85771ff2e3 --- /dev/null +++ b/libraries/baking/src/OBJBaker.cpp @@ -0,0 +1,404 @@ +// +// OBJBaker.cpp +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "OBJBaker.h" +#include "OBJReader.h" +#include "FBXWriter.h" + +const double UNIT_SCALE_FACTOR = 100.0; +const QByteArray PROPERTIES70_NODE_NAME = "Properties70"; +const QByteArray P_NODE_NAME = "P"; +const QByteArray C_NODE_NAME = "C"; +const QByteArray FBX_HEADER_EXTENSION = "FBXHeaderExtension"; +const QByteArray GLOBAL_SETTINGS_NODE_NAME = "GlobalSettings"; +const QByteArray OBJECTS_NODE_NAME = "Objects"; +const QByteArray GEOMETRY_NODE_NAME = "Geometry"; +const QByteArray MODEL_NODE_NAME = "Model"; +const QByteArray MATERIAL_NODE_NAME = "Material"; +const QByteArray TEXTURE_NODE_NAME = "Texture"; +const QByteArray TEXTURENAME_NODE_NAME = "TextureName"; +const QByteArray RELATIVEFILENAME_NODE_NAME = "RelativeFilename"; +const QByteArray CONNECTIONS_NODE_NAME = "Connections"; +const QByteArray CONNECTIONS_NODE_PROPERTY = "OO"; +const QByteArray CONNECTIONS_NODE_PROPERTY_1 = "OP"; +const QByteArray MESH = "Mesh"; + +void OBJBaker::bake() { + qDebug() << "OBJBaker" << _modelURL << "bake starting"; + + // trigger bakeOBJ once OBJ is loaded + connect(this, &OBJBaker::OBJLoaded, this, &OBJBaker::bakeOBJ); + + // make a local copy of the OBJ + loadOBJ(); +} + +void OBJBaker::loadOBJ() { + if (!QDir().mkpath(_bakedOutputDir)) { + handleError("Failed to create baked OBJ output folder " + _bakedOutputDir); + return; + } + + if (!QDir().mkpath(_originalOutputDir)) { + handleError("Failed to create original OBJ output folder " + _originalOutputDir); + return; + } + + // check if the OBJ is local or it needs to be downloaded + if (_modelURL.isLocalFile()) { + // loading the local OBJ + QFile localOBJ { _modelURL.toLocalFile() }; + + qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath; + + if (!localOBJ.exists()) { + handleError("Could not find " + _modelURL.toString()); + return; + } + + // make a copy in the output folder + if (!_originalOutputDir.isEmpty()) { + qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName(); + localOBJ.copy(_originalOutputDir + "/" + _modelURL.fileName()); + } + + localOBJ.copy(_originalModelFilePath); + + // local OBJ is loaded emit signal to trigger its baking + emit OBJLoaded(); + } else { + // OBJ is remote, start download + auto& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest networkRequest; + + // setup the request to follow re-directs and always hit the network + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + networkRequest.setUrl(_modelURL); + + qCDebug(model_baking) << "Downloading" << _modelURL; + auto networkReply = networkAccessManager.get(networkRequest); + + connect(networkReply, &QNetworkReply::finished, this, &OBJBaker::handleOBJNetworkReply); + } +} + +void OBJBaker::handleOBJNetworkReply() { + auto requestReply = qobject_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + qCDebug(model_baking) << "Downloaded" << _modelURL; + + // grab the contents of the reply and make a copy in the output folder + QFile copyOfOriginal(_originalModelFilePath); + + qDebug(model_baking) << "Writing copy of original obj to" << _originalModelFilePath << copyOfOriginal.fileName(); + + if (!copyOfOriginal.open(QIODevice::WriteOnly)) { + // add an error to the error list for this obj stating that a duplicate of the original obj could not be made + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")"); + return; + } + if (copyOfOriginal.write(requestReply->readAll()) == -1) { + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)"); + return; + } + + // close that file now that we are done writing to it + copyOfOriginal.close(); + + if (!_originalOutputDir.isEmpty()) { + copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName()); + } + + // remote OBJ is loaded emit signal to trigger its baking + emit OBJLoaded(); + } else { + // add an error to our list stating that the OBJ could not be downloaded + handleError("Failed to download " + _modelURL.toString()); + } +} + +void OBJBaker::bakeOBJ() { + // Read the OBJ file + QFile objFile(_originalModelFilePath); + if (!objFile.open(QIODevice::ReadOnly)) { + handleError("Error opening " + _originalModelFilePath + " for reading"); + return; + } + + QByteArray objData = objFile.readAll(); + + bool combineParts = true; // set true so that OBJReader reads material info from material library + OBJReader reader; + auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL); + + // Write OBJ Data as FBX tree nodes + FBXNode rootNode; + createFBXNodeTree(rootNode, *geometry); + + // Serialize the resultant FBX tree + auto encodedFBX = FBXWriter::encodeFBX(rootNode); + + // Export as baked FBX + auto fileName = _modelURL.fileName(); + auto baseName = fileName.left(fileName.lastIndexOf('.')); + auto bakedFilename = baseName + ".baked.fbx"; + + _bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename; + + QFile bakedFile; + bakedFile.setFileName(_bakedModelFilePath); + if (!bakedFile.open(QIODevice::WriteOnly)) { + handleError("Error opening " + _bakedModelFilePath + " for writing"); + return; + } + + bakedFile.write(encodedFBX); + + // Export successful + _outputFiles.push_back(_bakedModelFilePath); + qCDebug(model_baking) << "Exported" << _modelURL << "to" << _bakedModelFilePath; + + checkIfTexturesFinished(); +} + +void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { + // Generating FBX Header Node + FBXNode headerNode; + headerNode.name = FBX_HEADER_EXTENSION; + + // Generating global settings node + // Required for Unit Scale Factor + FBXNode globalSettingsNode; + globalSettingsNode.name = GLOBAL_SETTINGS_NODE_NAME; + + // Setting the tree hierarchy: GlobalSettings -> Properties70 -> P -> Properties + FBXNode properties70Node; + properties70Node.name = PROPERTIES70_NODE_NAME; + + FBXNode pNode; + { + pNode.name = P_NODE_NAME; + pNode.properties.append({ + "UnitScaleFactor", "double", "Number", "", + UNIT_SCALE_FACTOR + }); + } + + properties70Node.children = { pNode }; + globalSettingsNode.children = { properties70Node }; + + // Generating Object node + _objectNode.name = OBJECTS_NODE_NAME; + + // Generating Object node's child - Geometry node + FBXNode geometryNode; + geometryNode.name = GEOMETRY_NODE_NAME; + { + _geometryID = nextNodeID(); + geometryNode.properties = { + _geometryID, + GEOMETRY_NODE_NAME, + MESH + }; + } + + // Compress the mesh information and store in dracoNode + bool hasDeformers = false; // No concept of deformers for an OBJ + FBXNode dracoNode; + compressMesh(geometry.meshes[0], hasDeformers, dracoNode); + geometryNode.children.append(dracoNode); + + // Generating Object node's child - Model node + FBXNode modelNode; + modelNode.name = MODEL_NODE_NAME; + { + _modelID = nextNodeID(); + modelNode.properties = { _modelID, MODEL_NODE_NAME, MESH }; + } + + _objectNode.children = { geometryNode, modelNode }; + + // Generating Objects node's child - Material node + auto& meshParts = geometry.meshes[0].parts; + for (auto& meshPart : meshParts) { + FBXNode materialNode; + materialNode.name = MATERIAL_NODE_NAME; + if (geometry.materials.size() == 1) { + // case when no material information is provided, OBJReader considers it as a single default material + for (auto& materialID : geometry.materials.keys()) { + setMaterialNodeProperties(materialNode, materialID, geometry); + } + } else { + setMaterialNodeProperties(materialNode, meshPart.materialID, geometry); + } + + _objectNode.children.append(materialNode); + } + + // Generating Texture Node + // iterate through mesh parts and process the associated textures + auto size = meshParts.size(); + for (int i = 0; i < size; i++) { + QString material = meshParts[i].materialID; + FBXMaterial currentMaterial = geometry.materials[material]; + if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { + _textureID = nextNodeID(); + _mapTextureMaterial.emplace_back(_textureID, i); + + FBXNode textureNode; + { + textureNode.name = TEXTURE_NODE_NAME; + textureNode.properties = { _textureID }; + } + + // Texture node child - TextureName node + FBXNode textureNameNode; + { + textureNameNode.name = TEXTURENAME_NODE_NAME; + QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka"; + textureNameNode.properties = { propertyString }; + } + + // Texture node child - Relative Filename node + FBXNode relativeFilenameNode; + { + relativeFilenameNode.name = RELATIVEFILENAME_NODE_NAME; + } + + QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename; + + auto textureType = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE; + + // Compress the texture using ModelBaker::compressTexture() and store compressed file's name in the node + auto textureFile = compressTexture(textureFileName, textureType); + if (textureFile.isNull()) { + // Baking failed return + handleError("Failed to compress texture: " + textureFileName); + return; + } + relativeFilenameNode.properties = { textureFile }; + + textureNode.children = { textureNameNode, relativeFilenameNode }; + + _objectNode.children.append(textureNode); + } + } + + // Generating Connections node + FBXNode connectionsNode; + connectionsNode.name = CONNECTIONS_NODE_NAME; + + // connect Geometry to Model + FBXNode cNode; + cNode.name = C_NODE_NAME; + cNode.properties = { CONNECTIONS_NODE_PROPERTY, _geometryID, _modelID }; + connectionsNode.children = { cNode }; + + // connect all materials to model + for (auto& materialID : _materialIDs) { + FBXNode cNode; + cNode.name = C_NODE_NAME; + cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, _modelID }; + connectionsNode.children.append(cNode); + } + + // Connect textures to materials + for (const auto& texMat : _mapTextureMaterial) { + FBXNode cAmbientNode; + cAmbientNode.name = C_NODE_NAME; + cAmbientNode.properties = { + CONNECTIONS_NODE_PROPERTY_1, + texMat.first, + _materialIDs[texMat.second], + "AmbientFactor" + }; + connectionsNode.children.append(cAmbientNode); + + FBXNode cDiffuseNode; + cDiffuseNode.name = C_NODE_NAME; + cDiffuseNode.properties = { + CONNECTIONS_NODE_PROPERTY_1, + texMat.first, + _materialIDs[texMat.second], + "DiffuseColor" + }; + connectionsNode.children.append(cDiffuseNode); + } + + // Make all generated nodes children of rootNode + rootNode.children = { globalSettingsNode, _objectNode, connectionsNode }; +} + +// Set properties for material nodes +void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry) { + auto materialID = nextNodeID(); + _materialIDs.push_back(materialID); + materialNode.properties = { materialID, material, MESH }; + + FBXMaterial currentMaterial = geometry.materials[material]; + + // Setting the hierarchy: Material -> Properties70 -> P -> Properties + FBXNode properties70Node; + properties70Node.name = PROPERTIES70_NODE_NAME; + + // Set diffuseColor + FBXNode pNodeDiffuseColor; + { + pNodeDiffuseColor.name = P_NODE_NAME; + pNodeDiffuseColor.properties.append({ + "DiffuseColor", "Color", "", "A", + currentMaterial.diffuseColor[0], currentMaterial.diffuseColor[1], currentMaterial.diffuseColor[2] + }); + } + properties70Node.children.append(pNodeDiffuseColor); + + // Set specularColor + FBXNode pNodeSpecularColor; + { + pNodeSpecularColor.name = P_NODE_NAME; + pNodeSpecularColor.properties.append({ + "SpecularColor", "Color", "", "A", + currentMaterial.specularColor[0], currentMaterial.specularColor[1], currentMaterial.specularColor[2] + }); + } + properties70Node.children.append(pNodeSpecularColor); + + // Set Shininess + FBXNode pNodeShininess; + { + pNodeShininess.name = P_NODE_NAME; + pNodeShininess.properties.append({ + "Shininess", "Number", "", "A", + currentMaterial.shininess + }); + } + properties70Node.children.append(pNodeShininess); + + // Set Opacity + FBXNode pNodeOpacity; + { + pNodeOpacity.name = P_NODE_NAME; + pNodeOpacity.properties.append({ + "Opacity", "Number", "", "A", + currentMaterial.opacity + }); + } + properties70Node.children.append(pNodeOpacity); + + materialNode.children.append(properties70Node); +} diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h new file mode 100644 index 0000000000..e888c7b1d8 --- /dev/null +++ b/libraries/baking/src/OBJBaker.h @@ -0,0 +1,54 @@ +// +// OBJBaker.h +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OBJBaker_h +#define hifi_OBJBaker_h + +#include "Baker.h" +#include "TextureBaker.h" +#include "ModelBaker.h" + +#include "ModelBakingLoggingCategory.h" + +using TextureBakerThreadGetter = std::function; + +using NodeID = qlonglong; + +class OBJBaker : public ModelBaker { + Q_OBJECT +public: + using ModelBaker::ModelBaker; + +public slots: + virtual void bake() override; + +signals: + void OBJLoaded(); + +private slots: + void bakeOBJ(); + void handleOBJNetworkReply(); + +private: + void loadOBJ(); + void createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry); + void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry); + NodeID nextNodeID() { return _nodeID++; } + + NodeID _nodeID { 0 }; + NodeID _geometryID; + NodeID _modelID; + std::vector _materialIDs; + NodeID _textureID; + std::vector> _mapTextureMaterial; + FBXNode _objectNode; +}; +#endif // hifi_OBJBaker_h diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index d8dd7f5e35..06a487d6ea 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -128,6 +128,7 @@ namespace controller { makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"), + makeButtonPair(Action::SPRINT, "Sprint"), makeAxisPair(Action::RETICLE_CLICK, "ReticleClick"), makeAxisPair(Action::RETICLE_X, "ReticleX"), @@ -183,6 +184,7 @@ namespace controller { makeButtonPair(Action::ACTION2, "ACTION2"), makeButtonPair(Action::CONTEXT_MENU, "CONTEXT_MENU"), makeButtonPair(Action::TOGGLE_MUTE, "TOGGLE_MUTE"), + makeButtonPair(Action::SPRINT, "SPRINT") }; return availableInputs; } diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index a133d62c9f..0c77d63863 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -174,6 +174,7 @@ enum class Action { TRACKED_OBJECT_13, TRACKED_OBJECT_14, TRACKED_OBJECT_15, + SPRINT, NUM_ACTIONS }; diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 2c60ca25f5..fbc87898a4 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -96,9 +96,9 @@ namespace controller { Q_INVOKABLE QObject* parseMapping(const QString& json); Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl); - Q_INVOKABLE const QVariantMap& getHardware() { return _hardware; } - Q_INVOKABLE const QVariantMap& getActions() { return _actions; } - Q_INVOKABLE const QVariantMap& getStandard() { return _standard; } + Q_INVOKABLE const QVariantMap getHardware() { return _hardware; } + Q_INVOKABLE const QVariantMap getActions() { return _actions; } + Q_INVOKABLE const QVariantMap getStandard() { return _standard; } Q_INVOKABLE void startInputRecording(); Q_INVOKABLE void stopInputRecording(); Q_INVOKABLE void startInputPlayback(); diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 29f011fba2..371deec7d5 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -73,7 +73,8 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID; - for (const auto& inputMapping : device->getAvailableInputs()) { + auto inputs = device->getAvailableInputs(); + for (const auto& inputMapping : inputs) { const auto& input = inputMapping.first; // Ignore aliases if (_endpointsByInput.count(input)) { @@ -126,7 +127,8 @@ void UserInputMapper::removeDevice(int deviceID) { _mappingsByDevice.erase(mappingsEntry); } - for (const auto& inputMapping : device->getAvailableInputs()) { + auto inputs = device->getAvailableInputs(); + for (const auto& inputMapping : inputs) { const auto& input = inputMapping.first; auto endpoint = _endpointsByInput.find(input); if (endpoint != _endpointsByInput.end()) { @@ -171,7 +173,7 @@ InputDevice::Pointer UserInputMapper::getDevice(const Input& input) { } } -QString UserInputMapper::getDeviceName(uint16 deviceID) { +QString UserInputMapper::getDeviceName(uint16 deviceID) { Locker locker(_lock); if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { return _registeredDevices[deviceID]->_name; @@ -181,7 +183,7 @@ QString UserInputMapper::getDeviceName(uint16 deviceID) { int UserInputMapper::findDevice(QString name) const { Locker locker(_lock); - for (auto device : _registeredDevices) { + for (const auto& device : _registeredDevices) { if (device.second->_name == name) { return device.first; } @@ -192,7 +194,7 @@ int UserInputMapper::findDevice(QString name) const { QVector UserInputMapper::getDeviceNames() { Locker locker(_lock); QVector result; - for (auto device : _registeredDevices) { + for (const auto& device : _registeredDevices) { QString deviceName = device.second->_name.split(" (")[0]; result << deviceName; } @@ -218,7 +220,7 @@ Input UserInputMapper::findDeviceInput(const QString& inputName) const { const auto& device = _registeredDevices.at(deviceID); auto deviceInputs = device->getAvailableInputs(); - for (auto input : deviceInputs) { + for (const auto& input : deviceInputs) { if (input.second == inputName) { return input.first; } @@ -321,7 +323,8 @@ QVector UserInputMapper::getAllActions() const { QString UserInputMapper::getActionName(Action action) const { Locker locker(_lock); - for (auto actionPair : getActionInputs()) { + auto inputs = getActionInputs(); + for (const auto& actionPair : inputs) { if (actionPair.first.channel == toInt(action)) { return actionPair.second; } @@ -331,18 +334,20 @@ QString UserInputMapper::getActionName(Action action) const { QString UserInputMapper::getStandardPoseName(uint16_t pose) { Locker locker(_lock); - for (auto posePair : getStandardInputs()) { + auto inputs = getStandardInputs(); + for (const auto& posePair : inputs) { if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) { return posePair.second; } } return QString(); -} +} QVector UserInputMapper::getActionNames() const { Locker locker(_lock); QVector result; - for (auto actionPair : getActionInputs()) { + auto inputs = getActionInputs(); + for (const auto& actionPair : inputs) { result << actionPair.second; } return result; @@ -357,7 +362,7 @@ Pose UserInputMapper::getPoseState(Action action) const { bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = false; - for (auto device : _registeredDevices) { + for (const auto& device : _registeredDevices) { toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand); } return toReturn; @@ -469,7 +474,7 @@ void UserInputMapper::runMappings() { if (debugRoutes) { qCDebug(controllers) << "Beginning mapping frame"; } - for (auto endpointEntry : this->_endpointsByInput) { + for (const auto& endpointEntry : _endpointsByInput) { endpointEntry.second->reset(); } @@ -542,9 +547,9 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } - // Most endpoints can only be read once (though a given mapping can route them to + // Most endpoints can only be read once (though a given mapping can route them to // multiple places). Consider... If the default is to wire the A button to JUMP - // and someone else wires it to CONTEXT_MENU, I don't want both to occur when + // and someone else wires it to CONTEXT_MENU, I don't want both to occur when // I press the button. The exception is if I'm wiring a control back to itself // in order to adjust my interface, like inverting the Y axis on an analog stick if (!route->peek && !source->readable()) { @@ -897,7 +902,8 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) if (value.isArray()) { // Support "when" : [ "GamePad.RB", "GamePad.LB" ] Conditional::List children; - for (auto arrayItem : value.toArray()) { + auto array = value.toArray(); + for (const auto& arrayItem : array) { Conditional::Pointer childConditional = parseConditional(arrayItem); if (!childConditional) { return Conditional::Pointer(); @@ -908,7 +914,7 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) } else if (value.isString()) { // Support "when" : "GamePad.RB" auto conditionalToken = value.toString(); - + // Detect for modifier case (Not...) QString conditionalModifier; const QString JSON_CONDITIONAL_MODIFIER_NOT("!"); @@ -943,12 +949,12 @@ Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) { result = Filter::getFactory().create(value.toString()); } else if (value.isObject()) { result = Filter::parse(value.toObject()); - } + } if (!result) { qWarning() << "Invalid filter definition " << value; } - + return result; } @@ -960,7 +966,7 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { if (value.isArray()) { Filter::List result; auto filtersArray = value.toArray(); - for (auto filterValue : filtersArray) { + for (const auto& filterValue : filtersArray) { Filter::Pointer filter = parseFilter(filterValue); if (!filter) { return Filter::List(); @@ -968,7 +974,7 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { result.push_back(filter); } return result; - } + } Filter::Pointer filter = parseFilter(value); if (!filter) { @@ -980,7 +986,8 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { if (value.isArray()) { ArrayEndpoint::Pointer result = std::make_shared(); - for (auto arrayItem : value.toArray()) { + auto array = value.toArray(); + for (const auto& arrayItem : array) { Endpoint::Pointer destination = parseEndpoint(arrayItem); if (!destination) { return Endpoint::Pointer(); @@ -988,14 +995,14 @@ Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { result->_children.push_back(destination); } return result; - } - + } + return parseEndpoint(value); } Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { if (value.isObject()) { - auto object = value.toObject(); + auto object = value.toObject(); if (object.contains("makeAxis")) { auto axisValue = object.value("makeAxis"); if (axisValue.isArray()) { @@ -1017,7 +1024,8 @@ Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { Endpoint::Pointer UserInputMapper::parseAny(const QJsonValue& value) { if (value.isArray()) { Endpoint::List children; - for (auto arrayItem : value.toArray()) { + auto array = value.toArray(); + for (const auto& arrayItem : array) { Endpoint::Pointer destination = parseEndpoint(arrayItem); if (!destination) { return Endpoint::Pointer(); @@ -1162,7 +1170,7 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) { template bool hasDebuggableRoute(const T& routes) { - for (auto route : routes) { + for (const auto& route : routes) { if (route->debug) { return true; } @@ -1174,7 +1182,7 @@ bool hasDebuggableRoute(const T& routes) { void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) { Locker locker(_lock); // New routes for a device get injected IN FRONT of existing routes. Routes - // are processed in order so this ensures that the standard -> action processing + // are processed in order so this ensures that the standard -> action processing // takes place after all of the hardware -> standard or hardware -> action processing // because standard -> action is the first set of routes added. Route::List standardRoutes = mapping->routes; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp index ef9f04402b..f73268b41f 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -16,11 +16,14 @@ using namespace controller; void ActionEndpoint::apply(float newValue, const Pointer& source) { - InputRecorder* inputRecorder = InputRecorder::getInstance(); auto userInputMapper = DependencyManager::get(); - QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); - if(inputRecorder->isPlayingback()) { - newValue = inputRecorder->getActionState(actionName); + InputRecorder* inputRecorder = InputRecorder::getInstance(); + QString actionName; + if (inputRecorder->isPlayingback() || inputRecorder->isRecording()) { + actionName = userInputMapper->getActionName(Action(_input.getChannel())); + if (inputRecorder->isPlayingback()) { + newValue = inputRecorder->getActionState(actionName); + } } _currentValue += newValue; @@ -32,10 +35,12 @@ void ActionEndpoint::apply(float newValue, const Pointer& source) { void ActionEndpoint::apply(const Pose& value, const Pointer& source) { _currentPose = value; - InputRecorder* inputRecorder = InputRecorder::getInstance(); auto userInputMapper = DependencyManager::get(); - QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); - inputRecorder->setActionState(actionName, _currentPose); + InputRecorder* inputRecorder = InputRecorder::getInstance(); + if (inputRecorder->isRecording()) { + QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); + inputRecorder->setActionState(actionName, _currentPose); + } if (!_currentPose.isValid()) { return; diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp index 7f0e80cbae..2f20cd82c6 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp @@ -6,10 +6,43 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// NOTE: we don't need to include this header unless/until we add additional symbols. -// By removing this header we prevent these warnings on windows: -// -// warning LNK4221: This object file does not define any previously undefined public symbols, -// so it will not be used by any link operation that consumes this library -// -//#include "JSEndpoint.h" \ No newline at end of file +#include "JSEndpoint.h" +#include "../../Logging.h" + +using namespace controller; + +QString formatException(const QJSValue& exception) { + QString note { "UncaughtException" }; + QString result; + + const auto message = exception.toString(); + const auto fileName = exception.property("fileName").toString(); + const auto lineNumber = exception.property("lineNumber").toString(); + const auto stacktrace = exception.property("stack").toString(); + + const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3"; + const QString SCRIPT_BACKTRACE_SEP = "\n "; + + result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); + if (!stacktrace.isEmpty()) { + result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); + } + return result; +} + +float JSEndpoint::peek() const { + QJSValue result = _callable.call(); + if (result.isError()) { + qCDebug(controllers).noquote() << formatException(result); + return 0.0f; + } else { + return (float)result.toNumber(); + } +} + +void JSEndpoint::apply(float newValue, const Pointer& source) { + QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) })); + if (result.isError()) { + qCDebug(controllers).noquote() << formatException(result); + } +} diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h index 24d5ec93e9..4d179da8e6 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h @@ -24,16 +24,11 @@ public: : Endpoint(Input::INVALID_INPUT), _callable(callable) { } - virtual float peek() const override { - return (float)const_cast(this)->_callable.call().toNumber(); - } - - virtual void apply(float newValue, const Pointer& source) override { - _callable.call(QJSValueList({ QJSValue(newValue) })); - } + virtual float peek() const override; + virtual void apply(float newValue, const Pointer& source) override; private: - QJSValue _callable; + mutable QJSValue _callable; }; } diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp index 3e7fde347e..e2c48d776f 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -7,6 +7,7 @@ // #include "ScriptEndpoint.h" +#include "../../Logging.h" #include @@ -14,6 +15,25 @@ using namespace controller; +QString formatException(const QScriptValue& exception) { + QString note { "UncaughtException" }; + QString result; + + const auto message = exception.toString(); + const auto fileName = exception.property("fileName").toString(); + const auto lineNumber = exception.property("lineNumber").toString(); + const auto stacktrace = exception.property("stack").toString(); + + const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3"; + const QString SCRIPT_BACKTRACE_SEP = "\n "; + + result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); + if (!stacktrace.isEmpty()) { + result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); + } + return result; +} + float ScriptEndpoint::peek() const { const_cast(this)->updateValue(); return _lastValueRead; @@ -26,10 +46,11 @@ void ScriptEndpoint::updateValue() { } QScriptValue result = _callable.call(); - - // If the callable ever returns a non-number, we assume it's a pose - // and start reporting ourselves as a pose. - if (result.isNumber()) { + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + _lastValueRead = 0.0f; + } else if (result.isNumber()) { _lastValueRead = (float)_callable.call().toNumber(); } else { Pose::fromScriptValue(result, _lastPoseRead); @@ -52,8 +73,12 @@ void ScriptEndpoint::internalApply(float value, int sourceID) { Q_ARG(int, sourceID)); return; } - _callable.call(QScriptValue(), + QScriptValue result = _callable.call(QScriptValue(), QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) })); + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + } } Pose ScriptEndpoint::peekPose() const { @@ -67,6 +92,10 @@ void ScriptEndpoint::updatePose() { return; } QScriptValue result = _callable.call(); + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + } Pose::fromScriptValue(result, _lastPoseRead); } @@ -85,6 +114,10 @@ void ScriptEndpoint::internalApply(const Pose& newPose, int sourceID) { Q_ARG(int, sourceID)); return; } - _callable.call(QScriptValue(), + QScriptValue result = _callable.call(QScriptValue(), QScriptValueList({ Pose::toScriptValue(_callable.engine(), newPose), QScriptValue(sourceID) })); + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + } } diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index b725c91174..f33af1b580 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -11,6 +11,7 @@ #include +#include #include #include #include @@ -24,12 +25,16 @@ static const QString FULLSCREEN = "Fullscreen"; void Basic2DWindowOpenGLDisplayPlugin::customizeContext() { #if defined(Q_OS_ANDROID) + qreal dpi = getFullscreenTarget()->physicalDotsPerInch(); + _virtualPadPixelSize = dpi * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI; + auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png"; auto image = QImage(iconPath); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } if ((image.width() > 0) && (image.height() > 0)) { + image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio); _virtualPadStickTexture = gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), @@ -50,6 +55,8 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() { image = image.convertToFormat(QImage::Format_ARGB32); } if ((image.width() > 0) && (image.height() > 0)) { + image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio); + _virtualPadStickBaseTexture = gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), @@ -62,6 +69,29 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() { _virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits()); _virtualPadStickBaseTexture->setAutoGenerateMips(true); } + + _virtualPadJumpBtnPixelSize = dpi * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI; + iconPath = PathUtils::resourcesPath() + "images/fly.png"; + image = QImage(iconPath); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + if ((image.width() > 0) && (image.height() > 0)) { + image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio); + image = image.mirrored(); + + _virtualPadJumpBtnTexture = gpu::Texture::createStrict( + gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), + image.width(), image.height(), + gpu::Texture::MAX_NUM_MIPS, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + _virtualPadJumpBtnTexture->setSource("virtualPad jump"); + auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha(); + _virtualPadJumpBtnTexture->setUsage(usage.build()); + _virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); + _virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits()); + _virtualPadJumpBtnTexture->setAutoGenerateMips(true); + } #endif Parent::customizeContext(); } @@ -90,9 +120,10 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { #if defined(Q_OS_ANDROID) auto& virtualPadManager = VirtualPad::Manager::instance(); - if(virtualPadManager.getLeftVirtualPad()->isBeingTouched()) { + if(virtualPadManager.getLeftVirtualPad()->isShown()) { // render stick base - auto stickBaseTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getFirstTouch()); + auto stickBaseTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getFirstTouch(), + _virtualPadPixelSize, _virtualPadPixelSize); render([&](gpu::Batch& batch) { batch.enableStereo(false); batch.setProjectionTransform(mat4()); @@ -104,7 +135,8 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.draw(gpu::TRIANGLE_STRIP, 4); }); // render stick head - auto stickTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getCurrentTouch()); + auto stickTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getCurrentTouch(), + _virtualPadPixelSize, _virtualPadPixelSize); render([&](gpu::Batch& batch) { batch.enableStereo(false); batch.setProjectionTransform(mat4()); @@ -115,6 +147,20 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); }); + + // render stick head + auto jumpTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(), + _virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize); + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_cursorPipeline); + batch.setResourceTexture(0, _virtualPadJumpBtnTexture); + batch.resetViewTransform(); + batch.setModelTransform(jumpTransform); + batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); } #endif Parent::compositeExtra(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 7a69a40f19..a061a4c923 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -45,5 +45,9 @@ private: #if defined(Q_OS_ANDROID) gpu::TexturePointer _virtualPadStickTexture; gpu::TexturePointer _virtualPadStickBaseTexture; + qreal _virtualPadPixelSize; + + gpu::TexturePointer _virtualPadJumpBtnTexture; + qreal _virtualPadJumpBtnPixelSize; #endif }; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 74225b5b39..fb53ca253f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -458,9 +458,8 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const return result; } -glm::mat4 CompositorHelper::getPoint2DTransform(const glm::vec2& point) const { +glm::mat4 CompositorHelper::getPoint2DTransform(const glm::vec2& point, float sizeX, float sizeY) const { glm::mat4 result; - static const float PIXEL_SIZE = 512.0f; const auto canvasSize = vec2(toGlm(_renderingWidget->size()));; QPoint qPoint(point.x,point.y); vec2 position = toGlm(_renderingWidget->mapFromGlobal(qPoint)); @@ -469,7 +468,7 @@ glm::mat4 CompositorHelper::getPoint2DTransform(const glm::vec2& point) const { position -= 1.0; position.y *= -1.0f; - vec2 size = PIXEL_SIZE / canvasSize; + vec2 size = vec2(sizeX / canvasSize.x, sizeY / canvasSize.y); result = glm::scale(glm::translate(glm::mat4(), vec3(position, 0.0f)), vec3(size, 1.0f)); return result; } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 5b65315f45..de115b0554 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -90,7 +90,7 @@ public: glm::vec2 getReticleMaximumPosition() const; glm::mat4 getReticleTransform(const glm::mat4& eyePose = glm::mat4(), const glm::vec3& headPosition = glm::vec3()) const; - glm::mat4 getPoint2DTransform(const glm::vec2& point = glm::vec2()) const; + glm::mat4 getPoint2DTransform(const glm::vec2& point, float sizeX , float sizeY) const; ReticleInterface* getReticleInterface() { return _reticleInterface; } diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index 6a19a34727..47a213cf71 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -21,10 +21,6 @@ glm::uvec2 NullDisplayPlugin::getRecommendedRenderSize() const { return glm::uvec2(100, 100); } -bool NullDisplayPlugin::hasFocus() const { - return false; -} - void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) { if (frame) { _gpuContext->consumeFrameUpdates(frame); diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 97b71b5780..11563b3798 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -16,7 +16,6 @@ public: grouping getGrouping() const override { return DEVELOPER; } glm::uvec2 getRecommendedRenderSize() const override; - bool hasFocus() const override; void submitFrame(const gpu::FramePointer& newFrame) override; QImage getScreenshot(float aspectRatio = 0.0f) const override; QImage getSecondaryCameraScreenshot() const override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 9bd7d89634..4b33565bfd 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -361,7 +361,7 @@ void OpenGLDisplayPlugin::customizeContext() { auto presentThread = DependencyManager::get(); Q_ASSERT(thread() == presentThread->thread()); - getGLBackend()->setCameraCorrection(mat4()); + getGLBackend()->setCameraCorrection(mat4(), mat4(), true); for (auto& cursorValue : _cursorsData) { auto& cursorData = cursorValue.second; @@ -530,7 +530,11 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur batch.setStateScissorRect(scissor); batch.setViewportTransform(viewport); batch.setResourceTexture(0, texture); +#ifndef USE_GLES batch.setPipeline(_presentPipeline); +#else + batch.setPipeline(_simplePipeline); +#endif batch.draw(gpu::TRIANGLE_STRIP, 4); if (copyFbo) { gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight()); @@ -692,6 +696,9 @@ void OpenGLDisplayPlugin::present() { incrementPresentCount(); if (_currentFrame) { + auto correction = getViewCorrection(); + getGLBackend()->setCameraCorrection(correction, _prevRenderView); + _prevRenderView = correction * _currentFrame->view; { withPresentThreadLock([&] { _renderRate.increment(); @@ -828,11 +835,6 @@ glm::uvec2 OpenGLDisplayPlugin::getSurfaceSize() const { return result; } -bool OpenGLDisplayPlugin::hasFocus() const { - auto window = _container->getPrimaryWidget(); - return window ? window->hasFocus() : false; -} - void OpenGLDisplayPlugin::assertNotPresentThread() const { Q_ASSERT(QThread::currentThread() != _presentThread); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index bf06486095..1cc060161b 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -98,8 +98,6 @@ protected: virtual void compositePointer(); virtual void compositeExtra() {}; - virtual bool hasFocus() const override; - // These functions must only be called on the presentation thread virtual void customizeContext(); virtual void uncustomizeContext(); @@ -118,6 +116,7 @@ protected: void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer fbo); void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor); virtual void updateFrameData(); + virtual glm::mat4 getViewCorrection() { return glm::mat4(); } void withOtherThreadContext(std::function f) const; @@ -137,6 +136,7 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame { nullptr }; + mat4 _prevRenderView; gpu::FramebufferPointer _compositeFramebuffer; gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _mirrorHUDPipeline; diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index 6e397efbe5..40063652c8 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -7,6 +7,8 @@ // #include "DebugHmdDisplayPlugin.h" +#include + #include #include @@ -41,7 +43,15 @@ bool DebugHmdDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } bool DebugHmdDisplayPlugin::internalActivate() { + _isAutoRotateEnabled = _container->getBoolSetting("autoRotate", true); + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), tr("Auto Rotate"), + [this](bool clicked) { + _isAutoRotateEnabled = clicked; + _container->setBoolSetting("autoRotate", _isAutoRotateEnabled); + }, true, _isAutoRotateEnabled); + _ipd = 0.0327499993f * 2.0f; + // Would be nice to know why the left and right projection matrices are slightly dissymetrical _eyeProjections[0][0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; _eyeProjections[0][1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; _eyeProjections[0][2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; @@ -50,10 +60,15 @@ bool DebugHmdDisplayPlugin::internalActivate() { _eyeProjections[1][1] = vec4{ 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; _eyeProjections[1][2] = vec4{ 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; _eyeProjections[1][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; - _eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); - _eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); + // No need to do so here as this will done in Parent::internalActivate + //_eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); + //_eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); _eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, 0.0149999997, 1.0 }; _eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, 0.0149999997, 1.0 }; + _eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); + _eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); + _eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, -0.0149999997, 1.0 }; + _eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, -0.0149999997, 1.0 }; _renderTargetSize = { 3024, 1680 }; _cullingProjection = _eyeProjections[0]; // This must come after the initialization, so that the values calculated @@ -63,10 +78,13 @@ bool DebugHmdDisplayPlugin::internalActivate() { } void DebugHmdDisplayPlugin::updatePresentPose() { - float yaw = sinf(secTimestampNow()) * 0.25f; - float pitch = cosf(secTimestampNow()) * 0.25f; - // Simulates head pose latency correction - _currentPresentFrameInfo.presentPose = - glm::mat4_cast(glm::angleAxis(yaw, Vectors::UP)) * - glm::mat4_cast(glm::angleAxis(pitch, Vectors::RIGHT)); + Parent::updatePresentPose(); + if (_isAutoRotateEnabled) { + float yaw = sinf(secTimestampNow()) * 0.25f; + float pitch = cosf(secTimestampNow()) * 0.25f; + // Simulates head pose latency correction + _currentPresentFrameInfo.presentPose = + glm::mat4_cast(glm::angleAxis(yaw, Vectors::UP)) * + glm::mat4_cast(glm::angleAxis(pitch, Vectors::RIGHT)) ; + } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h index cd6fdd44b9..f2b1f36419 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h @@ -28,5 +28,7 @@ protected: bool isHmdMounted() const override { return true; } bool internalActivate() override; private: + static const QString NAME; + bool _isAutoRotateEnabled{ true }; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 8b255d9974..2a32a7d5ce 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -58,6 +58,18 @@ QRect HmdDisplayPlugin::getRecommendedHUDRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } +glm::mat4 HmdDisplayPlugin::getEyeToHeadTransform(Eye eye) const { + return _eyeOffsets[eye]; +} + +glm::mat4 HmdDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +glm::mat4 HmdDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + return _cullingProjection; +} + #define DISABLE_PREVIEW_MENU_ITEM_DELAY_MS 500 bool HmdDisplayPlugin::internalActivate() { @@ -324,12 +336,14 @@ void HmdDisplayPlugin::updateFrameData() { } updatePresentPose(); +} +glm::mat4 HmdDisplayPlugin::getViewCorrection() { if (_currentFrame) { auto batchPose = _currentFrame->pose; - auto currentPose = _currentPresentFrameInfo.presentPose; - auto correction = glm::inverse(batchPose) * currentPose; - getGLBackend()->setCameraCorrection(correction); + return glm::inverse(_currentPresentFrameInfo.presentPose) * batchPose; + } else { + return glm::mat4(); } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index d2d30df093..3639952524 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -26,9 +26,9 @@ public: ~HmdDisplayPlugin(); bool isHmd() const override final { return true; } float getIPD() const override final { return _ipd; } - glm::mat4 getEyeToHeadTransform(Eye eye) const override final { return _eyeOffsets[eye]; } - glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override { return _eyeProjections[eye]; } - glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override { return _cullingProjection; } + glm::mat4 getEyeToHeadTransform(Eye eye) const override final; + glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; + glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; glm::uvec2 getRecommendedUiSize() const override final; glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; } bool isDisplayVisible() const override { return isHmdMounted(); } @@ -59,6 +59,7 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; + glm::mat4 getViewCorrection() override; std::array _eyeOffsets; std::array _eyeProjections; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index ae8f9ec039..cfdfb1fc21 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -101,3 +101,4 @@ void StereoDisplayPlugin::internalDeactivate() { float StereoDisplayPlugin::getRecommendedAspectRatio() const { return aspect(Parent::getRecommendedRenderSize()); } + diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index 3b481dce97..c4205ea1db 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -26,7 +26,7 @@ public: // the IPD at the Application level, the way we now allow with HMDs. // If that becomes an issue then we'll need to break up the functionality similar // to the HMD plugins. - // virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; + //virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; protected: virtual bool internalActivate() override; diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 00879e1380..280b44cec0 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -55,7 +55,7 @@ HTTPConnection::~HTTPConnection() { QHash HTTPConnection::parseUrlEncodedForm() { // make sure we have the correct MIME type - QList elements = _requestHeaders.value("Content-Type").split(';'); + QList elements = requestHeader("Content-Type").split(';'); QString contentType = elements.at(0).trimmed(); if (contentType != "application/x-www-form-urlencoded") { @@ -75,7 +75,7 @@ QHash HTTPConnection::parseUrlEncodedForm() { QList HTTPConnection::parseFormData() const { // make sure we have the correct MIME type - QList elements = _requestHeaders.value("Content-Type").split(';'); + QList elements = requestHeader("Content-Type").split(';'); QString contentType = elements.at(0).trimmed(); @@ -251,7 +251,7 @@ void HTTPConnection::readHeaders() { if (trimmed.isEmpty()) { _socket->disconnect(this, SLOT(readHeaders())); - QByteArray clength = _requestHeaders.value("Content-Length"); + QByteArray clength = requestHeader("Content-Length"); if (clength.isEmpty()) { _parentManager->handleHTTPRequest(this, _requestUrl); @@ -275,7 +275,7 @@ void HTTPConnection::readHeaders() { respond("400 Bad Request", "The header was malformed."); return; } - _lastRequestHeader = trimmed.left(idx); + _lastRequestHeader = trimmed.left(idx).toLower(); QByteArray& value = _requestHeaders[_lastRequestHeader]; if (!value.isEmpty()) { value.append(", "); diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index 60408d4325..e4d23e3c90 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -72,8 +72,8 @@ public: /// Returns a reference to the request URL. const QUrl& requestUrl () const { return _requestUrl; } - /// Returns a reference to the request headers. - const Headers& requestHeaders () const { return _requestHeaders; } + /// Returns a copy of the request header value. If it does not exist, it will return a default constructed QByteArray. + QByteArray requestHeader(const QString& key) const { return _requestHeaders.value(key.toLower().toLocal8Bit()); } /// Returns a reference to the request content. const QByteArray& requestContent () const { return _requestContent; } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index d3c9f3d4bd..b61f24972a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -135,10 +135,8 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St template std::shared_ptr make_renderer(const EntityItemPointer& entity) { - T* rawResult = new T(entity); - // We want to use deleteLater so that renderer destruction gets pushed to the main thread - return std::shared_ptr(rawResult, std::bind(&QObject::deleteLater, rawResult)); + return std::shared_ptr(new T(entity), [](T* ptr) { ptr->deleteLater(); }); } EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) { @@ -164,7 +162,12 @@ ItemKey EntityRenderer::getKey() { return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); } - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + // This allows shapes to cast shadows + if (_canCastShadow) { + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withShadowCaster(); + } else { + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + } } uint32_t EntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { @@ -377,6 +380,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); + _canCastShadow = entity->getCanCastShadow(); _cauterized = entity->getCauterized(); _needsRenderUpdate = false; }); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index d34a1127ae..ada57c8ab0 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -129,6 +129,7 @@ protected: bool _isFading{ _entitiesShouldFadeFunction() }; bool _prevIsTransparent { false }; bool _visible { false }; + bool _canCastShadow { false }; bool _cauterized { false }; bool _moving { false }; bool _needsRenderUpdate { false }; diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 119a46b68f..7cea841bf0 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -9,6 +9,7 @@ #include "RenderableMaterialEntityItem.h" #include "RenderPipelines.h" +#include "GeometryCache.h" using namespace render; using namespace render::entities; @@ -17,7 +18,7 @@ bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityP if (entity->getMaterial() != _drawMaterial) { return true; } - if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) { + if (entity->getParentID() != _parentID) { return true; } if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) { @@ -30,8 +31,6 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& withWriteLock([&] { _drawMaterial = entity->getMaterial(); _parentID = entity->getParentID(); - _clientOnly = entity->getClientOnly(); - _owningAvatarID = entity->getOwningAvatarID(); _materialMappingPos = entity->getMaterialMappingPos(); _materialMappingScale = entity->getMaterialMappingScale(); _materialMappingRot = entity->getMaterialMappingRot(); @@ -90,138 +89,6 @@ ShapeKey MaterialEntityRenderer::getShapeKey() { return builder.build(); } -glm::vec3 MaterialEntityRenderer::getVertexPos(float phi, float theta) { - return glm::vec3(glm::sin(theta) * glm::cos(phi), glm::cos(theta), glm::sin(theta) * glm::sin(phi)); -} - -glm::vec3 MaterialEntityRenderer::getTangent(float phi, float theta) { - return glm::vec3(-glm::cos(theta) * glm::cos(phi), glm::sin(theta), -glm::cos(theta) * glm::sin(phi)); -} - -void MaterialEntityRenderer::addVertex(std::vector& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv) { - buffer.push_back(pos.x); buffer.push_back(pos.y); buffer.push_back(pos.z); - buffer.push_back(tan.x); buffer.push_back(tan.y); buffer.push_back(tan.z); - buffer.push_back(uv.x); buffer.push_back(uv.y); -} - -void MaterialEntityRenderer::addTriangleFan(std::vector& buffer, int stack, int step) { - float v1 = ((float)stack) / STACKS; - float theta1 = v1 * (float)M_PI; - glm::vec3 tip = getVertexPos(0, theta1); - float v2 = ((float)(stack + step)) / STACKS; - float theta2 = v2 * (float)M_PI; - for (int i = 0; i < SLICES; i++) { - float u1 = ((float)i) / SLICES; - float u2 = ((float)(i + step)) / SLICES; - float phi1 = u1 * M_PI_TIMES_2; - float phi2 = u2 * M_PI_TIMES_2; - /* (flipped for negative step) - p1 - / \ - / \ - / \ - p3 ------ p2 - */ - - glm::vec3 pos2 = getVertexPos(phi2, theta2); - glm::vec3 pos3 = getVertexPos(phi1, theta2); - - glm::vec3 tan1 = getTangent(0, theta1); - glm::vec3 tan2 = getTangent(phi2, theta2); - glm::vec3 tan3 = getTangent(phi1, theta2); - - glm::vec2 uv1 = glm::vec2((u1 + u2) / 2.0f, v1); - glm::vec2 uv2 = glm::vec2(u2, v2); - glm::vec2 uv3 = glm::vec2(u1, v2); - - addVertex(buffer, tip, tan1, uv1); - addVertex(buffer, pos2, tan2, uv2); - addVertex(buffer, pos3, tan3, uv3); - - _numVertices += 3; - } -} - -int MaterialEntityRenderer::_numVertices = 0; -std::shared_ptr MaterialEntityRenderer::_streamFormat = nullptr; -std::shared_ptr MaterialEntityRenderer::_stream = nullptr; -std::shared_ptr MaterialEntityRenderer::_verticesBuffer = nullptr; - -void MaterialEntityRenderer::generateMesh() { - _streamFormat = std::make_shared(); - _stream = std::make_shared(); - _verticesBuffer = std::make_shared(); - - const int NUM_POS_COORDS = 3; - const int NUM_TANGENT_COORDS = 3; - const int VERTEX_TANGENT_OFFSET = NUM_POS_COORDS * sizeof(float); - const int VERTEX_TEXCOORD_OFFSET = VERTEX_TANGENT_OFFSET + NUM_TANGENT_COORDS * sizeof(float); - - _streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - _streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - _streamFormat->setAttribute(gpu::Stream::TANGENT, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_TANGENT_OFFSET); - _streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET); - - _stream->addBuffer(_verticesBuffer, 0, _streamFormat->getChannels().at(0)._stride); - - std::vector vertexBuffer; - - // Top - addTriangleFan(vertexBuffer, 0, 1); - - // Middle section - for (int j = 1; j < STACKS - 1; j++) { - float v1 = ((float)j) / STACKS; - float v2 = ((float)(j + 1)) / STACKS; - float theta1 = v1 * (float)M_PI; - float theta2 = v2 * (float)M_PI; - for (int i = 0; i < SLICES; i++) { - float u1 = ((float)i) / SLICES; - float u2 = ((float)(i + 1)) / SLICES; - float phi1 = u1 * M_PI_TIMES_2; - float phi2 = u2 * M_PI_TIMES_2; - - /* - p2 ---- p3 - | / | - | / | - | / | - p1 ---- p4 - */ - - glm::vec3 pos1 = getVertexPos(phi1, theta2); - glm::vec3 pos2 = getVertexPos(phi1, theta1); - glm::vec3 pos3 = getVertexPos(phi2, theta1); - glm::vec3 pos4 = getVertexPos(phi2, theta2); - - glm::vec3 tan1 = getTangent(phi1, theta2); - glm::vec3 tan2 = getTangent(phi1, theta1); - glm::vec3 tan3 = getTangent(phi2, theta1); - glm::vec3 tan4 = getTangent(phi2, theta2); - - glm::vec2 uv1 = glm::vec2(u1, v2); - glm::vec2 uv2 = glm::vec2(u1, v1); - glm::vec2 uv3 = glm::vec2(u2, v1); - glm::vec2 uv4 = glm::vec2(u2, v2); - - addVertex(vertexBuffer, pos1, tan1, uv1); - addVertex(vertexBuffer, pos2, tan2, uv2); - addVertex(vertexBuffer, pos3, tan3, uv3); - - addVertex(vertexBuffer, pos3, tan3, uv3); - addVertex(vertexBuffer, pos4, tan4, uv4); - addVertex(vertexBuffer, pos1, tan1, uv1); - - _numVertices += 6; - } - } - - // Bottom - addTriangleFan(vertexBuffer, STACKS, -1); - - _verticesBuffer->append(vertexBuffer.size() * sizeof(float), (gpu::Byte*) vertexBuffer.data()); -} - void MaterialEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableMaterialEntityItem::render"); Q_ASSERT(args->_batch); @@ -233,7 +100,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { graphics::MaterialPointer drawMaterial; Transform textureTransform; withReadLock([&] { - parentID = _clientOnly ? _owningAvatarID : _parentID; + parentID = _parentID; renderTransform = _renderTransform; drawMaterial = _drawMaterial; textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0)); @@ -252,14 +119,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { args->_details._materialSwitches++; // Draw! - if (_numVertices == 0) { - generateMesh(); - } + DependencyManager::get()->renderSphere(batch); - batch.setInputFormat(_streamFormat); - batch.setInputStream(0, *_stream); - batch.draw(gpu::TRIANGLES, _numVertices, 0); - - const int NUM_VERTICES_PER_TRIANGLE = 3; - args->_details._trianglesRendered += _numVertices / NUM_VERTICES_PER_TRIANGLE; + args->_details._trianglesRendered += (int)DependencyManager::get()->getSphereTriangleCount(); } diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h index fef1a41138..96c720f79f 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h @@ -32,28 +32,12 @@ private: ShapeKey getShapeKey() override; QUuid _parentID; - bool _clientOnly; - QUuid _owningAvatarID; glm::vec2 _materialMappingPos; glm::vec2 _materialMappingScale; float _materialMappingRot; Transform _renderTransform; std::shared_ptr _drawMaterial; - - static int _numVertices; - static std::shared_ptr _streamFormat; - static std::shared_ptr _stream; - static std::shared_ptr _verticesBuffer; - - void generateMesh(); - void addTriangleFan(std::vector& buffer, int stack, int step); - static glm::vec3 getVertexPos(float phi, float theta); - static glm::vec3 getTangent(float phi, float theta); - static void addVertex(std::vector& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv); - const int SLICES = 15; - const int STACKS = 9; - const float M_PI_TIMES_2 = 2.0f * (float)M_PI; }; } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 5f926d1bb9..c4fa71a488 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -286,7 +286,7 @@ bool RenderableModelEntityItem::supportsDetailedRayIntersection() const { } bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { auto model = getModel(); if (!model) { @@ -974,6 +974,10 @@ scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScript auto result = _model->getScriptableModel(); result.objectID = getEntity()->getID(); + { + std::lock_guard lock(_materialsLock); + result.appendMaterials(_materials); + } return result; } @@ -1395,6 +1399,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } // TODO? early exit here when not visible? + if (model->canCastShadow() != _canCastShadow) { + model->setCanCastShadow(_canCastShadow, scene, viewTaskBits, false); + } + if (_needsCollisionGeometryUpdate) { setCollisionMeshKey(entity->getCollisionMeshKey()); _needsCollisionGeometryUpdate = false; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 5d7d84b7bc..68bc70c8a9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -68,7 +68,7 @@ public: virtual bool supportsDetailedRayIntersection() const override; virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 2b1de8d11b..0211daff1e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -565,7 +565,7 @@ public: #endif bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 0a00d1cb73..70c87dca6f 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -53,7 +53,7 @@ public: virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 22cd98b08a..feb88bed4b 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -19,6 +19,8 @@ #include "render-utils/simple_vert.h" #include "render-utils/simple_frag.h" +#include "RenderPipelines.h" + //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT #include @@ -108,11 +110,96 @@ bool ShapeEntityRenderer::isTransparent() const { if (_procedural.isEnabled() && _procedural.isFading()) { return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; } - - // return _entity->getLocalRenderAlpha() < 1.0f || Parent::isTransparent(); + + auto mat = _materials.find("0"); + if (mat != _materials.end()) { + if (mat->second.top().material) { + auto matKey = mat->second.top().material->getKey(); + if (matKey.isTranslucent()) { + return true; + } + } + } + return Parent::isTransparent(); } +ItemKey ShapeEntityRenderer::getKey() { + ItemKey::Builder builder; + builder.withTypeShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + + withReadLock([&] { + if (isTransparent()) { + builder.withTransparent(); + } else if (_canCastShadow) { + builder.withShadowCaster(); + } + }); + + return builder.build(); +} + +bool ShapeEntityRenderer::useMaterialPipeline() const { + bool proceduralReady = resultWithReadLock([&] { + return _procedural.isReady(); + }); + if (proceduralReady) { + return false; + } + + graphics::MaterialKey drawMaterialKey; + auto mat = _materials.find("0"); + if (mat != _materials.end() && mat->second.top().material) { + drawMaterialKey = mat->second.top().material->getKey(); + } + + if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) { + return true; + } + + // If the material is using any map, we need to use a material ShapeKey + for (int i = 0; i < graphics::Material::MapChannel::NUM_MAP_CHANNELS; i++) { + if (drawMaterialKey.isMapChannel(graphics::Material::MapChannel(i))) { + return true; + } + } + return false; +} + +ShapeKey ShapeEntityRenderer::getShapeKey() { + if (useMaterialPipeline()) { + graphics::MaterialKey drawMaterialKey; + if (_materials["0"].top().material) { + drawMaterialKey = _materials["0"].top().material->getKey(); + } + + bool isTranslucent = drawMaterialKey.isTranslucent(); + bool hasTangents = drawMaterialKey.isNormalMap(); + bool hasLightmap = drawMaterialKey.isLightmapMap(); + bool isUnlit = drawMaterialKey.isUnlit(); + + ShapeKey::Builder builder; + builder.withMaterial(); + + if (isTranslucent) { + builder.withTranslucent(); + } + if (hasTangents) { + builder.withTangents(); + } + if (hasLightmap) { + builder.withLightmap(); + } + if (isUnlit) { + builder.withUnlit(); + } + + return builder.build(); + } else { + return Parent::getShapeKey(); + } +} + void ShapeEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableShapeEntityItem::render"); Q_ASSERT(args->_batch); @@ -149,7 +236,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } else { geometryCache->renderShape(batch, geometryShape, outColor); } - } else { + } else if (!useMaterialPipeline()) { // FIXME, support instanced multi-shape rendering using multidraw indirect outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); @@ -158,6 +245,11 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } else { geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline); } + } else { + RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing); + args->_details._materialSwitches++; + + geometryCache->renderShape(batch, geometryShape); } const auto triCount = geometryCache->getShapeTriangleCount(geometryShape); @@ -169,8 +261,12 @@ scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel() { auto geometryCache = DependencyManager::get(); auto geometryShape = geometryCache->getShapeForEntityShape(_shape); glm::vec3 vertexColor; - if (_materials["0"].top().material) { - vertexColor = _materials["0"].top().material->getAlbedo(); + { + std::lock_guard lock(_materialsLock); + result.appendMaterials(_materials); + if (_materials["0"].top().material) { + vertexColor = _materials["0"].top().material->getAlbedo(); + } } if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { result.objectID = getEntity()->getID(); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index de855ce0c6..463ef187fc 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -24,6 +24,10 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; +protected: + ItemKey getKey() override; + ShapeKey getShapeKey() override; + private: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; @@ -32,6 +36,8 @@ private: virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; + bool useMaterialPipeline() const; + Procedural _procedural; QString _lastUserData; Transform _renderTransform; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 968c181940..e58eb540e8 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -111,7 +111,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { if (!_geometryID) { _geometryID = geometryCache->allocateID(); } - geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false, false); + geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); float scale = _lineHeight / _textRenderer->getFontSize(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f7aaa43b7d..f31ed4e238 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -34,7 +34,7 @@ static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount{ 0 }; -// Don't allow more than 100 concurrent web views +// Don't allow more than 20 concurrent web views static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; @@ -46,6 +46,19 @@ static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; +WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { + if (urlString.isEmpty()) { + return ContentType::NoContent; + } + + const QUrl url(urlString); + if (url.scheme() == "http" || url.scheme() == "https" || + urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { + return ContentType::HtmlContent; + } + return ContentType::QmlContent; +} + WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { static std::once_flag once; std::call_once(once, [&]{ @@ -75,8 +88,14 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe return true; } - if (uvec2(getWindowSize(entity)) != toGlm(_webSurface->size())) { - return true; + { + QSharedPointer webSurface; + withReadLock([&] { + webSurface = _webSurface; + }); + if (webSurface && uvec2(getWindowSize(entity)) != toGlm(webSurface->size())) { + return true; + } } if (_lastSourceUrl != entity->getSourceUrl()) { @@ -95,9 +114,15 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } bool WebEntityRenderer::needsRenderUpdate() const { - if (!_webSurface) { - // If we have rendered recently, and there is no web surface, we're going to create one - return true; + { + QSharedPointer webSurface; + withReadLock([&] { + webSurface = _webSurface; + }); + if (!webSurface) { + // If we have rendered recently, and there is no web surface, we're going to create one + return true; + } } return Parent::needsRenderUpdate(); @@ -123,13 +148,45 @@ void WebEntityRenderer::onTimeout() { } void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - withWriteLock([&] { - // This work must be done on the main thread - if (!hasWebSurface()) { - // If we couldn't create a new web surface, exit - if (!buildWebSurface(entity)) { - return; + // If the content type has changed, or the old content type was QML, we need to + // destroy the existing surface (because surfaces don't support changing the root + // object, so subsequent loads of content just overlap the existing content + bool urlChanged = false; + { + auto newSourceUrl = entity->getSourceUrl(); + auto newContentType = getContentType(newSourceUrl); + auto currentContentType = ContentType::NoContent; + withReadLock([&] { + urlChanged = _lastSourceUrl != newSourceUrl; + currentContentType = _contentType; + }); + + if (urlChanged) { + if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { + destroyWebSurface(); } + + withWriteLock([&] { + _lastSourceUrl = newSourceUrl; + _contentType = newContentType; + }); + } + } + + + withWriteLock([&] { + if (_contentType == ContentType::NoContent) { + return; + } + + // This work must be done on the main thread + // If we couldn't create a new web surface, exit + if (!hasWebSurface() && !buildWebSurface(entity)) { + return; + } + + if (urlChanged) { + _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -138,11 +195,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); } - if (_lastSourceUrl != entity->getSourceUrl()) { - _lastSourceUrl = entity->getSourceUrl(); - loadSourceURL(); - } - _lastDPI = entity->getDPI(); _lastLocked = entity->getLocked(); @@ -199,7 +251,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { } bool WebEntityRenderer::hasWebSurface() { - return (bool)_webSurface; + return (bool)_webSurface && _webSurface->getRootItem(); } bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { @@ -232,9 +284,6 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { // Let us interact with the keyboard surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); }); - _fadeStartTime = usecTimestampNow(); - loadSourceURL(); - _webSurface->resume(); // forward web events to EntityScriptingInterface auto entities = DependencyManager::get(); @@ -243,7 +292,30 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { emit entities->webEventReceived(entityItemID, message); }); - return true; + if (_contentType == ContentType::HtmlContent) { + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the + // web entity + if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(DEFAULT_MAX_FPS); + } + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + item->setProperty("url", _lastSourceUrl); + }); + } else if (_contentType == ContentType::QmlContent) { + _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { + if (item && item->objectName() == "tabletRoot") { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); + } + }); + } + _fadeStartTime = usecTimestampNow(); + _webSurface->resume(); + + return _webSurface->getRootItem(); } void WebEntityRenderer::destroyWebSurface() { @@ -289,32 +361,6 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con return dims; } -void WebEntityRenderer::loadSourceURL() { - const QUrl sourceUrl(_lastSourceUrl); - if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || - _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { - _contentType = htmlContent; - - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - } - - _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); - }); - } else { - _contentType = qmlContent; - _webSurface->load(_lastSourceUrl); - if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - } -} - void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { if (!_lastLocked && _webSurface) { PointerEvent webEvent = event; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 309e750f53..3100014e9b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -47,15 +47,19 @@ private: bool buildWebSurface(const TypedEntityPointer& entity); void destroyWebSurface(); bool hasWebSurface(); - void loadSourceURL(); glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; + int _geometryId{ 0 }; - enum contentType { - htmlContent, - qmlContent + enum class ContentType { + NoContent, + HtmlContent, + QmlContent }; - contentType _contentType; + + static ContentType getContentType(const QString& urlString); + + ContentType _contentType{ ContentType::NoContent }; QSharedPointer _webSurface; glm::vec3 _contextPosition; gpu::TexturePointer _texture; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 34703804cd..5a3caa55fe 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -330,6 +330,7 @@ void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity sunLight->setColor(ColorUtils::toVec3(_keyLightProperties.getColor())); sunLight->setIntensity(_keyLightProperties.getIntensity()); sunLight->setDirection(entity->getTransform().getRotation() * _keyLightProperties.getDirection()); + sunLight->setCastShadows(_keyLightProperties.getCastShadows()); } void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& entity) { @@ -432,7 +433,7 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) { _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE); // keep whatever is assigned on the ambient map/sphere until texture is loaded -} + } } void ZoneEntityRenderer::updateAmbientMap() { diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index 769b87f2a9..0cf9596cce 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -30,7 +30,7 @@ void main(void) { varTexcoord = inTexCoord0.st; // pass along the diffuse color - varColor = colorToLinearRGBA(inColor); + varColor = color_sRGBAToLinear(inColor); // standard transform diff --git a/libraries/entities-renderer/src/paintStroke_fade.slv b/libraries/entities-renderer/src/paintStroke_fade.slv index 9f10fa5d91..b6075caaf8 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slv +++ b/libraries/entities-renderer/src/paintStroke_fade.slv @@ -31,7 +31,7 @@ void main(void) { varTexcoord = inTexCoord0.st; // pass along the diffuse color - varColor = colorToLinearRGBA(inColor); + varColor = color_sRGBAToLinear(inColor); // standard transform diff --git a/libraries/entities/src/AmbientLightPropertyGroup.h b/libraries/entities/src/AmbientLightPropertyGroup.h index fbbc7c9900..591ea6a6fa 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.h +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -27,6 +27,14 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +/**jsdoc + * Ambient light is defined by the following properties. + * @typedef {object} Entities.AmbientLight + * @property {number} ambientIntensity=0.5 - The intensity of the light. + * @property {string} ambientURL="" - A cube map image that defines the color of the light coming from each direction. If + * "" then the entity's {@link Entities.Skybox|Skybox} url property value is used, unless that also is "" in which + * case the entity's ambientLightMode property is set to "inherit". + */ class AmbientLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 2af56fb6b2..82af60ed1a 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -44,6 +44,19 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b } +/**jsdoc + * The AnimationProperties are used to configure an animation. + * @typedef Entities.AnimationProperties + * @property {string} url="" - The URL of the FBX file that has the animation. + * @property {number} fps=30 - The speed in frames/s that the animation is played at. + * @property {number} firstFrame=0 - The first frame to play in the animation. + * @property {number} lastFrame=100000 - The last frame to play in the animation. + * @property {number} currentFrame=0 - The current frame being played in the animation. + * @property {boolean} running=false - If true then the animation should play. + * @property {boolean} loop=true - If true then the animation should be continuously repeated in a loop. + * @property {boolean} hold=false - If true then the rotations and translations of the last frame played should be + * maintained when the animation stops playing. + */ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, Animation, animation, URL, url); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index f424c02e6e..d43bdd7b51 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -94,6 +94,49 @@ variables. These argument variables are used by the code which is run when bull #include "EntityDynamicInterface.h" +/**jsdoc +*

An entity action may be one of the following types:

+* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +*
ValueTypeDescriptionArguments
"far-grab"Avatar actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity. Collisions +* between the entity and the user's avatar are disabled during the far-grab.{@link Entities.ActionArguments-FarGrab}
"hold"Avatar actionPositions and rotates an entity relative to an avatar's hand. Collisions between the entity and the user's avatar +* are disabled during the hold.{@link Entities.ActionArguments-Hold}
"offset"Object actionMoves an entity so that it is a set distance away from a target point.{@link Entities.ActionArguments-Offset}
"tractor"Object actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity.{@link Entities.ActionArguments-Tractor}
"travel-oriented"Object actionOrients an entity to align with its direction of travel.{@link Entities.ActionArguments-TravelOriented}
"hinge"Object constraintLets an entity pivot about an axis or connects two entities with a hinge joint.{@link Entities.ActionArguments-Hinge}
"slider"Object constraintLets an entity slide and rotate along an axis, or connects two entities that slide and rotate along a shared +* axis.{@link Entities.ActionArguments-Slider|ActionArguments-Slider}
"cone-twist"Object constraintConnects two entities with a joint that can move through a cone and can twist.{@link Entities.ActionArguments-ConeTwist}
"ball-socket"Object constraintConnects two entities with a ball and socket joint.{@link Entities.ActionArguments-BallSocket}
"spring"Synonym for "tractor". Legacy value.
+* @typedef {string} Entities.ActionType +*/ +// Note: The "none" action type is not listed because it's an internal "uninitialized" value and not useful for scripts. EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicTypeString) { QString normalizedDynamicTypeString = dynamicTypeString.toLower().remove('-').remove('_'); if (normalizedDynamicTypeString == "none") { diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 90740948ce..5d7bd61854 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -91,6 +91,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, return; } + if (entityTree && entityTree->isServerlessMode()) { + // if we are in a serverless domain, don't send edit packets + return; + } + QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); if (type == PacketType::EntityAdd) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ec0bdbd2ae..8a87a98a10 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -62,7 +62,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : EntityItem::~EntityItem() { // these pointers MUST be correct at delete, else we probably have a dangling backpointer // to this EntityItem in the corresponding data structure. - assert(!_simulated); + assert(!_simulated || (!_element && !_physicsInfo)); assert(!_element); assert(!_physicsInfo); } @@ -91,6 +91,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_REGISTRATION_POINT; requestedProperties += PROP_ANGULAR_DAMPING; requestedProperties += PROP_VISIBLE; + requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_COLLISIONLESS; requestedProperties += PROP_COLLISION_MASK; requestedProperties += PROP_DYNAMIC; @@ -249,6 +250,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, getCollisionless()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, getCollisionMask()); APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic()); @@ -691,7 +693,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // the entity-server is awarding us ownership which is what we want _simulationOwner.set(newSimOwner); } - } else if (newSimOwner.matchesValidID(myNodeID) && !_hasBidOnSimulation) { + } else if (newSimOwner.matchesValidID(myNodeID) && !_simulationOwner.pendingTake(now)) { // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, // this could happen when the user reloads the cache and entity tree. markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); @@ -799,6 +801,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless); READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint8_t, setCollisionMask); READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic); @@ -960,7 +963,11 @@ void EntityItem::setHref(QString value) { // If the string has something and doesn't start with with "hifi://" it shouldn't be set // We allow the string to be empty, because that's the initial state of this property - if ( !(value.toLower().startsWith("hifi://")) && !value.isEmpty()) { + if (!value.isEmpty() && + !(value.toLower().startsWith("hifi://")) && + !(value.toLower().startsWith("file://")) + // TODO: serverless-domains will eventually support http and https also + ) { return; } withWriteLock([&] { @@ -1234,6 +1241,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionless, getCollisionless); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionMask, getCollisionMask); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dynamic, getDynamic); @@ -1346,6 +1354,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); // Certifiable Properties @@ -1902,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } -void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { +void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) { if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) { qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; } @@ -1933,14 +1942,10 @@ void EntityItem::clearSimulationOwnership() { } -void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) { +void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) { _simulationOwner.setPendingPriority(priority, timestamp); } -void EntityItem::rememberHasSimulationOwnershipBid() const { - _hasBidOnSimulation = true; -} - QString EntityItem::actionsToDebugString() { QString result; QVector serializedActions; @@ -2723,6 +2728,28 @@ void EntityItem::setVisible(bool value) { } } +bool EntityItem::getCanCastShadow() const { + bool result; + withReadLock([&] { + result = _canCastShadow; + }); + return result; +} + +void EntityItem::setCanCastShadow(bool value) { + bool changed = false; + withWriteLock([&] { + if (_canCastShadow != value) { + changed = true; + _canCastShadow = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + bool EntityItem::isChildOfMyAvatar() const { QUuid ancestorID = findAncestorOfType(NestableType::Avatar); return !ancestorID.isNull() && (ancestorID == Physics::getSessionUUID() || ancestorID == AVATAR_SELF_ID); @@ -2931,13 +2958,6 @@ void EntityItem::retrieveMarketplacePublicKey() { } void EntityItem::preDelete() { - // clear out any left-over actions - EntityTreeElementPointer element = _element; // use local copy of _element for logic below - EntityTreePointer entityTree = element ? element->getTree() : nullptr; - EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; - if (simulation) { - clearActions(simulation); - } } void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { @@ -2957,4 +2977,4 @@ std::unordered_map EntityItem::getMaterial toReturn = _materials; } return toReturn; -} \ No newline at end of file +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index b12417c496..de98c1a47a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -58,6 +58,9 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr getMaterials(); + void setSimulationOwnershipExpiry(uint64_t expiry) { _simulationOwnershipExpiry = expiry; } + uint64_t getSimulationOwnershipExpiry() const { return _simulationOwnershipExpiry; } + signals: void requestRenderUpdate(); @@ -551,6 +561,7 @@ protected: glm::vec3 _registrationPoint { ENTITY_ITEM_DEFAULT_REGISTRATION_POINT }; float _angularDamping { ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING }; bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE }; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS }; uint8_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT }; bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC }; @@ -614,9 +625,6 @@ protected: static quint64 _rememberDeletedActionTime; mutable QHash _previouslyDeletedActions; - // per entity keep state if it ever bid on simulation, so that we can ignore false simulation ownership - mutable bool _hasBidOnSimulation { false }; - QUuid _sourceUUID; /// the server node UUID we came from bool _clientOnly { false }; @@ -637,6 +645,7 @@ protected: quint64 _lastUpdatedAngularVelocityTimestamp { 0 }; quint64 _lastUpdatedAccelerationTimestamp { 0 }; quint64 _lastUpdatedQueryAACubeTimestamp { 0 }; + uint64_t _simulationOwnershipExpiry { 0 }; bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a2724c4cbf..8b916a875f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -316,6 +316,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); + CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow); CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping); @@ -362,6 +363,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -439,6 +441,710 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { return changedProperties; } +/**jsdoc + * Different entity types have different properties: some common to all entities (listed below) and some specific to each + * {@link Entities.EntityType|EntityType} (linked to below). The properties are accessed as an object of property names and + * values. + * + * @typedef {object} Entities.EntityProperties + * @property {Uuid} id - The ID of the entity. Read-only. + * @property {string} name="" - A name for the entity. Need not be unique. + * @property {Entities.EntityType} type - The entity type. You cannot change the type of an entity after it's created. (Though + * its value may switch among "Box", "Shape", and "Sphere" depending on changes to + * the shape property set for entities of these types.) Read-only. + * @property {boolean} clientOnly=false - If true then the entity is an avatar entity; otherwise it is a server + * entity. An avatar entity follows you to each domain you visit, rendering at the same world coordinates unless it's + * parented to your avatar. Value cannot be changed after the entity is created.
+ * The value can also be set at entity creation by using the clientOnly parameter in + * {@link Entities.addEntity}. + * @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if clientOnly is + * true, otherwise {@link Uuid|Uuid.NULL}. Read-only. + * + * @property {string} created - The UTC date and time that the entity was created, in ISO 8601 format as + * yyyy-MM-ddTHH:mm:ssZ. Read-only. + * @property {number} age - The age of the entity in seconds since it was created. Read-only. + * @property {string} ageAsText - The age of the entity since it was created, formatted as h hours m minutes s + * seconds. + * @property {number} lifetime=-1 - How long an entity lives for, in seconds, before being automatically deleted. A value of + * -1 means that the entity lives for ever. + * @property {number} lastEdited - When the entity was last edited, expressed as the number of microseconds since + * 1970-01-01T00:00:00 UTC. Read-only. + * @property {Uuid} lastEditedBy - The session ID of the avatar or agent that most recently created or edited the entity. + * Read-only. + * + * @property {boolean} locked=false - Whether or not the entity can be edited or deleted. If true then the + * entity's properties other than locked cannot be changed, and the entity cannot be deleted. + * @property {boolean} visible=true - Whether or not the entity is rendered. If true then the entity is rendered. + * @property {boolean} canCastShadows=true - Whether or not the entity casts shadows. Currently applicable only to + * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a + * {@link Entities.EntityType|Zone} entity with castShadows enabled in its {@link Entities.EntityProperties-Zone|keyLight} property. + * + * @property {Vec3} position=0,0,0 - The position of the entity. + * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. + * @property {Vec3} registrationPoint=0.5,0.5,0.5 - The point in the entity that is set to the entity's position and is rotated + * about, {@link Vec3|Vec3.ZERO} – {@link Vec3|Vec3.ONE}. A value of {@link Vec3|Vec3.ZERO} is the entity's + * minimum x, y, z corner; a value of {@link Vec3|Vec3.ONE} is the entity's maximum x, y, z corner. + * + * @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise + * {@link Vec3|Vec3.ZERO}. Read-only. + * @property {Vec3} naturalDimensions - The dimensions of the entity's unscaled mesh model if it has one, otherwise + * {@link Vec3|Vec3.ONE}. Read-only. + * + * @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates. + * @property {number} damping=0.39347 - How much to slow down the linear velocity of an entity over time, 0.0 + * – 1.0. A higher damping value slows down the entity more quickly. The default value is for an + * exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 of its + * initial value. + * @property {Vec3} angularVelocity=0,0,0 - The angular velocity of the entity in rad/s with respect to its axes, about its + * registration point. + * @property {number} angularDamping=0.39347 - How much to slow down the angular velocity of an entity over time, + * 0.01.0. A higher damping value slows down the entity more quickly. The default value + * is for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 + * of its initial value. + * + * @property {Vec3} gravity=0,0,0 - The acceleration due to gravity in m/s2 that the entity should move with, in + * world coordinates. Set to { x: 0, y: -9.8, z: 0 } to simulate Earth's gravity. Gravity is applied to an + * entity's motion only if its dynamic property is true. If changing an entity's + * gravity from {@link Vec3|Vec3.ZERO}, you need to give it a small velocity in order to kick off + * physics simulation. + * The gravity value is applied in addition to the acceleration value. + * @property {Vec3} acceleration=0,0,0 - A general acceleration in m/s2 that the entity should move with, in world + * coordinates. The acceleration is applied to an entity's motion only if its dynamic property is + * true. If changing an entity's acceleration from {@link Vec3|Vec3.ZERO}, you need to give it a + * small velocity in order to kick off physics simulation. + * The acceleration value is applied in addition to the gravity value. + * @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, 0.0 – + * 0.99. The higher the value, the more bouncy. + * @property {number} friction=0.5 - How much to slow down an entity when it's moving against another, 0.0 – + * 10.0. The higher the value, the more quickly it slows down. Examples: 0.1 for ice, + * 0.9 for sandpaper. + * @property {number} density=1000 - The density of the entity in kg/m3, 100 for balsa wood – + * 10000 for silver. The density is used in conjunction with the entity's bounding box volume to work out its + * mass in the application of physics. + * + * @property {boolean} collisionless=false - Whether or not the entity should collide with items per its + * collisionMask property. If true then the entity does not collide. + * @property {boolean} ignoreForCollisions=false - Synonym for collisionless. + * @property {Entities.CollisionMask} collisionMask=31 - What types of items the entity should collide with. + * @property {string} collidesWith="static,dynamic,kinematic,myAvatar,otherAvatar," - Synonym for collisionMask, + * in text format. + * @property {string} collisionSoundURL="" - The sound to play when the entity experiences a collision. Valid file formats are + * as per the {@link SoundCache} object. + * @property {boolean} dynamic=false - Whether or not the entity should be affected by collisions. If true then + * the entity's movement is affected by collisions. + * @property {boolean} collisionsWillMove=false - Synonym for dynamic. + * + * @property {string} href="" - A "hifi://" metaverse address that a user is taken to when they click on the entity. + * @property {string} description="" - A description of the href property value. + * + * @property {string} userData="" - Used to store extra data about the entity in JSON format. WARNING: Other apps such as the + * Create app can also use this property, so make sure you handle data stored by other apps — edit only your bit and + * leave the rest of the data intact. You can use JSON.parse() to parse the string into a JavaScript object + * which you can manipulate the properties of, and use JSON.stringify() to convert the object into a string to + * put in the property. + * + * @property {string} script="" - The URL of the client entity script, if any, that is attached to the entity. + * @property {number} scriptTimestamp=0 - Intended to be used to indicate when the client entity script was loaded. Should be + * an integer number of milliseconds since midnight GMT on January 1, 1970 (e.g., as supplied by Date.now(). + * If you update the property's value, the script is re-downloaded and reloaded. This is how the "reload" + * button beside the "script URL" field in properties tab of the Create app works. + * @property {string} serverScripts="" - The URL of the server entity script, if any, that is attached to the entity. + * + * @property {Uuid} parentID=Uuid.NULL - The ID of the entity or avatar that this entity is parented to. {@link Uuid|Uuid.NULL} + * if the entity is not parented. + * @property {number} parentJointIndex=65535 - The joint of the entity or avatar that this entity is parented to. Use + * 65535 or -1 to parent to the entity or avatar's position and orientation rather than a joint. + * @property {Vec3} localPosition=0,0,0 - The position of the entity relative to its parent if the entity is parented, + * otherwise the same value as position. If the entity is parented to an avatar and is clientOnly + * so that it scales with the avatar, this value remains the original local position value while the avatar scale changes. + * @property {Quat} localRotation=0,0,0,1 - The rotation of the entity relative to its parent if the entity is parented, + * otherwise the same value as rotation. + * @property {Vec3} localVelocity=0,0,0 - The velocity of the entity relative to its parent if the entity is parented, + * otherwise the same value as velocity. + * @property {Vec3} localAngularVelocity=0,0,0 - The angular velocity of the entity relative to its parent if the entity is + * parented, otherwise the same value as position. + * @property {Vec3} localDimensions - The dimensions of the entity. If the entity is parented to an avatar and is + * clientOnly so that it scales with the avatar, this value remains the original dimensions value while the + * avatar scale changes. + * + * @property {Entities.BoundingBox} boundingBox - The axis-aligned bounding box that tightly encloses the entity. + * Read-only. + * @property {AACube} queryAACube - The axis-aligned cube that determines where the entity lives in the entity server's octree. + * The cube may be considerably larger than the entity in some situations, e.g., when the entity is grabbed by an avatar: + * the position of the entity is determined through avatar mixer updates and so the AA cube is expanded in order to reduce + * unnecessary entity server updates. Scripts should not change this property's value. + * + * @property {string} actionData="" - Base-64 encoded compressed dump of the actions associated with the entity. This property + * is typically not used in scripts directly; rather, functions that manipulate an entity's actions update it. + * The size of this property increases with the number of actions. Because this property value has to fit within a High + * Fidelity datagram packet there is a limit to the number of actions that an entity can have, and edits which would result + * in overflow are rejected. + * Read-only. + * @property {Entities.RenderInfo} renderInfo - Information on the cost of rendering the entity. Currently information is only + * provided for Model entities. Read-only. + * + * @property {string} itemName="" - Certifiable name of the Marketplace item. + * @property {string} itemDescription="" - Certifiable description of the Marketplace item. + * @property {string} itemCategories="" - Certifiable category of the Marketplace item. + * @property {string} itemArtist="" - Certifiable artist that created the Marketplace item. + * @property {string} itemLicense="" - Certifiable license URL for the Marketplace item. + * @property {number} limitedRun=4294967295 - Certifiable maximum integer number of editions (copies) of the Marketplace item + * allowed to be sold. + * @property {number} editionNumber=0 - Certifiable integer edition (copy) number or the Marketplace item. Each copy sold in + * the Marketplace is numbered sequentially, starting at 1. + * @property {number} entityInstanceNumber=0 - Certifiable integer instance number for identical entities in a Marketplace + * item. A Marketplace item may have identical parts. If so, then each is numbered sequentially with an instance number. + * @property {string} marketplaceID="" - Certifiable UUID for the Marketplace item, as used in the URL of the item's download + * and its Marketplace Web page. + * @property {string} certificateID="" - Hash of the entity's static certificate JSON, signed by the artist's private key. + * @property {number} staticCertificateVersion=0 - The version of the method used to generate the certificateID. + * + * @see The different entity types have additional properties as follows: + * @see {@link Entities.EntityProperties-Box|EntityProperties-Box} + * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} + * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} + * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} + * @see {@link Entities.EntityProperties-Model|EntityProperties-Model} + * @see {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} + * @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} + * @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} + * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} + * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} + * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} + * @see {@link Entities.EntityProperties-Zone|EntityProperties-Zone} + */ + +/**jsdoc + * The "Box" {@link Entities.EntityType|EntityType} is the same as the "Shape" + * {@link Entities.EntityType|EntityType} except that its shape value is always set to "Cube" + * when the entity is created. If its shape property value is subsequently changed then the entity's + * type will be reported as "Sphere" if the shape is set to "Sphere", + * otherwise it will be reported as "Shape". + * @typedef {object} Entities.EntityProperties-Box + */ + +/**jsdoc + * The "Light" {@link Entities.EntityType|EntityType} adds local lighting effects. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Light + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Entity surface outside these dimensions are not lit + * by the light. + * @property {Color} color=255,255,255 - The color of the light emitted. + * @property {number} intensity=1 - The brightness of the light. + * @property {number} falloffRadius=0.1 - The distance from the light's center at which intensity is reduced by 25%. + * @property {boolean} isSpotlight=false - If true then the light is directional, emitting along the entity's + * local negative z-axis; otherwise the light is a point light which emanates in all directions. + * @property {number} exponent=0 - Affects the softness of the spotlight beam: the higher the value the softer the beam. + * @property {number} cutoff=1.57 - Affects the size of the spotlight beam: the higher the value the larger the beam. + * @example Create a spotlight pointing at the ground. + * Entities.addEntity({ + * type: "Light", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -4 })), + * rotation: Quat.fromPitchYawRollDegrees(-75, 0, 0), + * dimensions: { x: 5, y: 5, z: 5 }, + * intensity: 100, + * falloffRadius: 0.3, + * isSpotlight: true, + * exponent: 20, + * cutoff: 30, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Line" {@link Entities.EntityType|EntityType} draws thin, straight lines between a sequence of two or more + * points. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Line + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Must be sufficient to contain all the + * linePoints. + * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's + * position. A maximum of 70 points can be specified. The property's value is set only if all the linePoints + * lie within the entity's dimensions. + * @property {number} lineWidth=2 - Currently not used. + * @property {Color} color=255,255,255 - The color of the line. + * @example Draw lines in a "V". + * var entity = Entities.addEntity({ + * type: "Line", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 1 }, + * linePoints: [ + * { x: -1, y: 1, z: 0 }, + * { x: 0, y: -1, z: 0 }, + * { x: 1, y: 1, z: 0 }, + * ], + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Material" {@link Entities.EntityType|EntityType} modifies the existing materials on + * {@link Entities.EntityType|Model} entities, {@link Entities.EntityType|Shape} entities (albedo only), + * {@link Overlays.OverlayType|model overlays}, and avatars. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
+ * To apply a material to an entity or overlay, set the material entity's parentID property to the entity or + * overlay's ID. + * To apply a material to an avatar, set the material entity's parentID property to the avatar's session UUID. + * To apply a material to your avatar such that it persists across domains and log-ins, create the material as an avatar entity + * by setting the clientOnly parameter in {@link Entities.addEntity} to true. + * Material entities render as non-scalable spheres if they don't have their parent set. + * @typedef {object} Entities.EntityProperties-Material + * @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append ?name to the URL, the + * material with that name in the {@link MaterialResource} will be applied to the entity.
+ * Alternatively, set the property value to "userData" to use the {@link Entities.EntityProperties|userData} + * entity property to live edit the material resource values. + * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is + * applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of + * 0. + * @property {string|number} parentMaterialName="0" - Selects the submesh or submeshes within the parent to apply the material + * to. If in the format "mat::string", all submeshes with material name "string" are replaced. + * Otherwise the property value is parsed as an unsigned integer, specifying the mesh index to modify. Invalid values are + * parsed to 0. + * @property {string} materialMappingMode="uv" - How the material is mapped to the entity. Either "uv" or + * "projected". Currently, only "uv" is supported. + * @property {Vec2} materialMappingPos=0,0 - Offset position in UV-space of the top left of the material, range + * { x: 0, y: 0 }{ x: 1, y: 1 }. + * @property {Vec2} materialMappingScale=1,1 - How much to scale the material within the parent's UV-space. + * @property {number} materialMappingRot=0 - How much to rotate the material within the parent's UV-space, in degrees. + * @example Color a sphere using a Material entity. + * var entityID = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 1, y: 1, z: 1 }, + * color: { red: 128, green: 128, blue: 128 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var materialID = Entities.addEntity({ + * type: "Material", + * parentID: entityID, + * materialURL: "userData", + * priority: 1, + * userData: JSON.stringify({ + * materials: { + * // Can only set albedo on a Shape entity. + * // Value overrides entity's "color" property. + * albedo: [1.0, 0, 0] + * } + * }), + * }); + */ + +/**jsdoc + * The "Model" {@link Entities.EntityType|EntityType} displays an FBX or OBJ model. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Model + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. When adding an entity, if no dimensions + * value is specified then the model is automatically sized to its + * {@link Entities.EntityProperties|naturalDimensions}. + * @property {Color} color=255,255,255 - Currently not used. + * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".
+ * Note: If the name ends with "default-image-model.fbx" then the entity is considered to be an "Image" + * entity, in which case the textures property should be set per the example. + * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the + * model's original textures. Use a texture name from the originalTextures property to override that texture. + * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no + * overrides. You can use JSON.stringify() to convert a JavaScript object of name, URL pairs into a JSON + * string. + * @property {string} originalTextures="{}" - A JSON string of texture name, URL pairs used in the model. The property value is + * filled in after the entity has finished rezzing (i.e., textures have loaded). You can use JSON.parse() to + * parse the JSON string into a JavaScript object of name, URL pairs. Read-only. + * + * @property {ShapeType} shapeType="none" - The shape of the collision hull used if collisions are enabled. + * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * "compound". + * + * @property {Entities.AnimationProperties} animation - An animation to play on the model. + * + * @property {Quat[]} jointRotations=[]] - Joint rotations applied to the model; [] if none are applied or the + * model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative to + * each joint's parent.
+ * Joint rotations can be set by {@link Entities.setLocalJointRotation|setLocalJointRotation} and similar functions, or by + * setting the value of this property. If you set a joint rotation using this property you also need to set the + * corresponding jointRotationsSet value to true. + * @property {boolean[]} jointRotationsSet=[]] - true values for joints that have had rotations applied, + * false otherwise; [] if none are applied or the model hasn't loaded. The array indexes are per + * {@link Entities.getJointIndex|getJointIndex}. + * @property {Vec3[]} jointTranslations=[]] - Joint translations applied to the model; [] if none are applied or + * the model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative + * to each joint's parent.
+ * Joint translations can be set by {@link Entities.setLocalJointTranslation|setLocalJointTranslation} and similar + * functions, or by setting the value of this property. If you set a joint translation using this property you also need to + * set the corresponding jointTranslationsSet value to true. + * @property {boolean[]} jointTranslationsSet=[]] - true values for joints that have had translations applied, + * false otherwise; [] if none are applied or the model hasn't loaded. The array indexes are per + * {@link Entities.getJointIndex|getJointIndex}. + * @property {boolean} relayParentJoints=false - If true and the entity is parented to an avatar, then the + * avatar's joint rotations are applied to the entity's joints. + * + * @example Rez a Vive tracker puck. + * var entity = Entities.addEntity({ + * type: "Model", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -2 })), + * rotation: MyAvatar.orientation, + * modelURL: "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj", + * dimensions: { x: 0.0945, y: 0.0921, z: 0.0423 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * @example Create an "Image" entity like you can in the Create app. + * var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; + * var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + * var entity = Entities.addEntity({ + * type: "Model", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -3 })), + * rotation: MyAvatar.orientation, + * dimensions: { + * x: 0.5385, + * y: 0.2819, + * z: 0.0092 + * }, + * shapeType: "box", + * collisionless: true, + * modelURL: IMAGE_MODEL, + * textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }), + * lifetime: 300 // Delete after 5 minutes + * }); + */ + +/**jsdoc + * The "ParticleEffect" {@link Entities.EntityType|EntityType} displays a particle system that can be used to + * simulate things such as fire, smoke, snow, magic spells, etc. The particles emanate from an ellipsoid or part thereof. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-ParticleEffect + + * @property {boolean} isEmitting=true - If true then particles are emitted. + * @property {number} maxParticles=1000 - The maximum number of particles to render at one time. Older particles are deleted if + * necessary when new ones are created. + * @property {number} lifespan=3s - How long, in seconds, each particle lives. + * @property {number} emitRate=15 - The number of particles per second to emit. + * @property {number} emitSpeed=5 - The speed, in m/s, that each particle is emitted at. + * @property {number} speedSpread=1 - The spread in speeds at which particles are emitted at. If emitSpeed == 5 + * and speedSpread == 1, particles will be emitted with speeds in the range 4m/s – 6m/s. + * @property {vec3} emitAcceleration=0,-9.8,0 - The acceleration that is applied to each particle during its lifetime. The + * default is Earth's gravity value. + * @property {vec3} accelerationSpread=0,0,0 - The spread in accelerations that each particle is given. If + * emitAccelerations == {x: 0, y: -9.8, z: 0} and accelerationSpread == + * {x: 0, y: 1, z: 0}, each particle will have an acceleration in the range, {x: 0, y: -10.8, z: 0} + * – {x: 0, y: -8.8, z: 0}. + * @property {Vec3} dimensions - The dimensions of the particle effect, i.e., a bounding box containing all the particles + * during their lifetimes, assuming that emitterShouldTrail is false. Read-only. + * @property {boolean} emitterShouldTrail=false - If true then particles are "left behind" as the emitter moves, + * otherwise they stay with the entity's dimensions. + * + * @property {Quat} emitOrientation=-0.707,0,0,0.707 - The orientation of particle emission relative to the entity's axes. By + * default, particles emit along the entity's local z-axis, and azimuthStart and azimuthFinish + * are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e., + * the particles emit vertically. + * @property {vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted. + * @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted; + * range 0.01.0 for the ellipsoid center to the ellipsoid surface, respectively. + * Particles are emitted from the portion of the ellipsoid that lies between emitRadiusStart and the + * ellipsoid's surface. + * @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted + * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the + * ellipsoid that lies between polarStart and polarFinish. + * @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted + * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the + * ellipsoid that lies between polarStart and polarFinish. + * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local + * z-axis at which particles start being emitted; range -Math.PIMath.PI. Particles are + * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. + * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local + * z-axis at which particles stop being emitted; range -Math.PIMath.PI. Particles are + * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. + * + * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, + * use PNG format. + * @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life. + * @property {number} radiusStart=0.025 - The radius of each particle at the start of its life. If not explicitly set, the + * particleRadius value is used. + * @property {number} radiusFinish=0.025 - The radius of each particle at the end of its life. If not explicitly set, the + * particleRadius value is used. + * @property {number} radiusSpread=0 - Currently not used. + * @property {Color} color=255,255,255 - The color of each particle at the middle of its life. + * @property {Color} colorStart=255,255,255 - The color of each particle at the start of its life. If not explicitly set, the + * color value is used. + * @property {Color} colorFinish=255,255,255 - The color of each particle at the end of its life. If not explicitly set, the + * color value is used. + * @property {Color} colorSpread=0,0,0 - Currently not used. + * @property {number} alpha=1 - The alpha of each particle at the middle of its life. + * @property {number} alphaStart=1 - The alpha of each particle at the start of its life. If not explicitly set, the + * alpha value is used. + * @property {number} alphaFinish=1 - The alpha of each particle at the end of its life. If not explicitly set, the + * alpha value is used. + * @property {number} alphaSpread=0 - Currently not used. + * + * @property {ShapeType} shapeType="none" - Currently not used. Read-only. + * + * @example Create a ball of green smoke. + * particles = Entities.addEntity({ + * type: "ParticleEffect", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -4 })), + * lifespan: 5, + * emitRate: 10, + * emitSpeed: 0.02, + * speedSpread: 0.01, + * emitAcceleration: { x: 0, y: 0.02, z: 0 }, + * polarFinish: Math.PI, + * textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + * particleRadius: 0.1, + * color: { red: 0, green: 255, blue: 0 }, + * alphaFinish: 0, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "PolyLine" {@link Entities.EntityType|EntityType} draws textured, straight lines between a sequence of + * points. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-PolyLine + * @property {Vec3} dimensions=1,1,1 - The dimensions of the entity, i.e., the size of the bounding box that contains the + * lines drawn. + * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's + * position. A maximum of 70 points can be specified. Must be specified in order for the entity to render. + * @property {Vec3[]} normals=[]] - The normal vectors for the line's surface at the linePoints. The values are + * relative to the entity's orientation. Must be specified in order for the entity to render. + * @property {number[]} strokeWidths=[]] - The widths, in m, of the line at the linePoints. Must be specified in + * order for the entity to render. + * @property {number} lineWidth=2 - Currently not used. + * @property {Vec3[]} strokeColors=[]] - Currently not used. + * @property {Color} color=255,255,255 - The base color of the line, which is multiplied with the color of the texture for + * rendering. + * @property {string} textures="" - The URL of a JPG or PNG texture to use for the lines. If you want transparency, use PNG + * format. + * @property {boolean} isUVModeStretch=true - If true, the texture is stretched to fill the whole line, otherwise + * the texture repeats along the line. + * @example Draw a textured "V". + * var entity = Entities.addEntity({ + * type: "PolyLine", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * linePoints: [ + * { x: -1, y: 0.5, z: 0 }, + * { x: 0, y: 0, z: 0 }, + * { x: 1, y: 0.5, z: 0 } + * ], + * normals: [ + * { x: 0, y: 0, z: 1 }, + * { x: 0, y: 0, z: 1 }, + * { x: 0, y: 0, z: 1 } + * ], + * strokeWidths: [ 0.1, 0.1, 0.1 ], + * color: { red: 255, green: 0, blue: 0 }, // Use just the red channel from the image. + * textures: "http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/flowArts/trails.png", + * isUVModeStretch: true, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "PolyVox" {@link Entities.EntityType|EntityType} displays a set of textured voxels. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * If you have two or more neighboring PolyVox entities of the same size abutting each other, you can display them as joined by + * configuring their voxelSurfaceStyle and neighbor ID properties.
+ * PolyVox entities uses a library from Volumes of Fun. Their + * library documentation may be useful to read. + * @typedef {object} Entities.EntityProperties-PolyVox + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. + * @property {Vec3} voxelVolumeSize=32,32,32 - Integer number of voxels along each axis of the entity, in the range + * 1,1,1 to 128,128,128. The dimensions of each voxel is + * dimensions / voxelVolumesize. + * @property {string} voxelData="ABAAEAAQAAAAHgAAEAB42u3BAQ0AAADCoPdPbQ8HFAAAAPBuEAAAAQ==" - Base-64 encoded compressed dump of + * the PolyVox data. This property is typically not used in scripts directly; rather, functions that manipulate a PolyVox + * entity update it.
+ * The size of this property increases with the size and complexity of the PolyVox entity, with the size depending on how + * the particular entity's voxels compress. Because this property value has to fit within a High Fidelity datagram packet + * there is a limit to the size and complexity of a PolyVox entity, and edits which would result in an overflow are + * rejected. + * @property {Entities.PolyVoxSurfaceStyle} voxelSurfaceStyle=2 - The style of rendering the voxels' surface and how + * neighboring PolyVox entities are joined. + * @property {string} xTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local x-axis. JPG or + * PNG format. If no texture is specified the surfaces display white. + * @property {string} yTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local y-axis. JPG or + * PNG format. If no texture is specified the surfaces display white. + * @property {string} zTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local z-axis. JPG or + * PNG format. If no texture is specified the surfaces display white. + * @property {Uuid} xNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local x-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} yNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local y-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} zNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local z-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} xPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local x-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} yPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local y-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} zPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local z-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @example Create a textured PolyVox sphere. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var texture = "http://public.highfidelity.com/cozza13/tuscany/Concrete2.jpg"; + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: position, + * dimensions: { x: 2, y: 2, z: 2 }, + * xTextureURL: texture, + * yTextureURL: texture, + * zTextureURL: texture, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setVoxelSphere(polyVox, position, 0.8, 255); + */ + +/**jsdoc + * The "Shape" {@link Entities.EntityType|EntityType} displays an entity of a specified shape. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Shape + * @property {Entities.Shape} shape="Sphere" - The shape of the entity. + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. + * @property {Color} color=255,255,255 - The color of the entity. + * @example Create a cylinder. + * var shape = Entities.addEntity({ + * type: "Shape", + * shape: "Cylinder", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.4, y: 0.6, z: 0.4 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Sphere" {@link Entities.EntityType|EntityType} is the same as the "Shape" + * {@link Entities.EntityType|EntityType} except that its shape value is always set to "Sphere" + * when the entity is created. If its shape property value is subsequently changed then the entity's + * type will be reported as "Box" if the shape is set to "Cube", + * otherwise it will be reported as "Shape". + * @typedef {object} Entities.EntityProperties-Sphere + */ + +/**jsdoc + * The "Text" {@link Entities.EntityType|EntityType} displays a 2D rectangle of text in the domain. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Text + * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. + * @property {string} text="" - The text to display on the face of the entity. Text wraps if necessary to fit. New lines can be + * created using \n. Overflowing lines are not displayed. + * @property {number} lineHeight=0.1 - The height of each line of text (thus determining the font size). + * @property {Color} textColor=255,255,255 - The color of the text. + * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. + * @property {boolean} faceCamera=false - If true, the entity is oriented to face each user's camera (i.e., it + * differs for each user present). + * @example Create a text entity. + * var text = Entities.addEntity({ + * type: "Text", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.6, y: 0.3, z: 0.01 }, + * lineHeight: 0.12, + * text: "Hello\nthere!", + * faceCamera: true, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Web" {@link Entities.EntityType|EntityType} displays a browsable Web page. Each user views their own copy + * of the Web page: if one user navigates to another page on the entity, other users do not see the change; if a video is being + * played, users don't see it in sync. + * The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Web + * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. + * @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate + * on the Web entity. + * @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter + * (multiply by 1 / 0.0254 = 39.3701) then multiply dimensions.x and dimensions.y by that value + * you get the resolution in pixels. + * @example Create a Web entity displaying at 1920 x 1080 resolution. + * var METERS_TO_INCHES = 39.3701; + * var entity = Entities.addEntity({ + * type: "Web", + * sourceUrl: "https://highfidelity.com/", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -4 })), + * rotation: MyAvatar.orientation, + * dimensions: { + * x: 3, + * y: 3 * 1080 / 1920, + * z: 0.01 + * }, + * dpi: 1920 / (3 * METERS_TO_INCHES), + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Zone" {@link Entities.EntityType|EntityType} is a volume of lighting effects and avatar permissions. + * Avatar interaction events such as {@link Entities.enterEntity} are also often used with a Zone entity. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Zone + * @property {Vec3} dimensions=0.1,0.1,0.1 - The size of the volume in which the zone's lighting effects and avatar permissions + * have effect. + * + * @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar + * permissions have effect. Reverts to the default value if set to "none", or set to "compound" + * and compoundShapeURL is "". + * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * "compound". + * + * @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:
+ * "inherit": The key light from any enclosing zone continues into this zone.
+ * "disabled": The key light from any enclosing zone and the key light of this zone are disabled in this + * zone.
+ * "enabled": The key light properties of this zone are enabled, overriding the key light of from any + * enclosing zone. + * @property {Entities.KeyLight} keyLight - The key light properties of the zone. + * + * @property {string} ambientLightMode="inherit" - Configures the ambient light in the zone. Possible values:
+ * "inherit": The ambient light from any enclosing zone continues into this zone.
+ * "disabled": The ambient light from any enclosing zone and the ambient light of this zone are disabled in + * this zone.
+ * "enabled": The ambient light properties of this zone are enabled, overriding the ambient light from any + * enclosing zone. + * @property {Entities.AmbientLight} ambientLight - The ambient light properties of the zone. + * + * @property {string} skyboxMode="inherit" - Configures the skybox displayed in the zone. Possible values:
+ * "inherit": The skybox from any enclosing zone is dislayed in this zone.
+ * "disabled": The skybox from any enclosing zone and the skybox of this zone are disabled in this zone.
+ * "enabled": The skybox properties of this zone are enabled, overriding the skybox from any enclosing zone. + * @property {Entities.Skybox} skybox - The skybox properties of the zone. + * + * @property {string} hazeMode="inherit" - Configures the haze in the zone. Possible values:
+ * "inherit": The haze from any enclosing zone continues into this zone.
+ * "disabled": The haze from any enclosing zone and the haze of this zone are disabled in this zone.
+ * "enabled": The haze properties of this zone are enabled, overriding the haze from any enclosing zone. + * @property {Entities.Haze} haze - The haze properties of the zone. + * + * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise they cannot. + * @property {boolean} ghostingAllowed=true - If true then visitors with avatar collisions turned off will not + * collide with content in the zone; otherwise visitors will always collide with content in the zone. + + * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the + * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to + * certain properties.
+ *
+ * function filter(properties) {
+ *     // Test and edit properties object values,
+ *     // e.g., properties.modelURL, as required.
+ *     return properties;
+ * }
+ * 
+ * + * @example Create a zone that casts a red key light along the x-axis. + * var zone = Entities.addEntity({ + * type: "Zone", + * position: MyAvatar.position, + * dimensions: { x: 100, y: 100, z: 100 }, + * keyLightMode: "enabled", + * keyLight: { + * "color": { "red": 255, "green": 0, "blue": 0 }, + * "direction": { "x": 1, "y": 0, "z": 0 } + * }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics) const { // If strictSemantics is true and skipDefaults is false, then all and only those properties are copied for which the property flag // is included in _desiredProperties, or is one of the specially enumerated ALWAYS properties below. @@ -490,6 +1196,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); // Relevant to Shape and Model entities only. COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISIONLESS, collisionless); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISIONLESS, collisionless, ignoreForCollisions, getCollisionless()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_MASK, collisionMask); @@ -498,10 +1205,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_DYNAMIC, dynamic, collisionsWillMove, getDynamic()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera); // Text only. COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); // Certifiable Properties COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_NAME, itemName); @@ -519,7 +1227,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL); - // Boxes, Spheres, Light, Line, Model(??), Particle, PolyLine + // Light, Line, Model, ParticleEffect, PolyLine, Shape COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR, color); // Particles only @@ -546,7 +1254,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_SPREAD, colorSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_START, colorStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_FINISH, colorFinish); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_SPREAD, alphaSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); @@ -567,12 +1274,15 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); } + + // FIXME: Shouldn't provide a shapeType property for Box and Sphere entities. if (_type == EntityTypes::Box) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Box")); } if (_type == EntityTypes::Sphere) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Sphere")); } + if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); } @@ -651,11 +1361,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Line || _type == EntityTypes::PolyLine) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_WIDTH, lineWidth); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_POINTS, linePoints); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NORMALS, normals); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_COLORS, strokeColors); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NORMALS, normals); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_COLORS, strokeColors); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); // Polyline only. } // Materials @@ -667,8 +1377,17 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_DATA, materialData); } + /**jsdoc + * The axis-aligned bounding box of an entity. + * @typedef Entities.BoundingBox + * @property {Vec3} brn - The bottom right near (minimum axes values) corner of the AA box. + * @property {Vec3} tfl - The top far left (maximum axes values) corner of the AA box. + * @property {Vec3} center - The center of the AA box. + * @property {Vec3} dimensions - The dimensions of the AA box. + */ if (!skipDefaults && !strictSemantics) { AABox aaBox = getAABox(); QScriptValue boundingBox = engine->newObject(); @@ -690,6 +1409,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); @@ -698,13 +1418,23 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable // Rendering info if (!skipDefaults && !strictSemantics) { QScriptValue renderInfo = engine->newObject(); + /**jsdoc + * Information on how an entity is rendered. Properties are only filled in for Model entities; other + * entity types have an empty object, {}. + * @typedef {object} Entities.RenderInfo + * @property {number} verticesCount - The number of vertices in the entity. + * @property {number} texturesCount - The number of textures in the entity. + * @property {number} textureSize - The total size of the textures in the entity, in bytes. + * @property {boolean} hasTransparent - Is true if any of the textures has transparency. + * @property {number} drawCalls - The number of draw calls required to render the entity. + */ // currently only supported by models if (_type == EntityTypes::Model) { renderInfo.setProperty("verticesCount", (int)getRenderInfoVertexCount()); // FIXME - theoretically the number of vertex could be > max int @@ -717,6 +1447,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(renderInfo, renderInfo); // Gettable but not settable } + // FIXME: These properties should already have been set above. properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly())); properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); @@ -751,6 +1482,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, glmVec3, setAngularVelocity); COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping); COPY_PROPERTY_FROM_QSCRIPTVALUE(visible, bool, setVisible); + COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); COPY_PROPERTY_FROM_QSCRIPTVALUE(color, xColor, setColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(colorSpread, xColor, setColorSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(colorStart, xColor, setColorStart); @@ -809,6 +1541,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -922,6 +1655,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(angularVelocity); COPY_PROPERTY_IF_CHANGED(angularDamping); COPY_PROPERTY_IF_CHANGED(visible); + COPY_PROPERTY_IF_CHANGED(canCastShadow); COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(colorSpread); COPY_PROPERTY_IF_CHANGED(colorStart); @@ -1094,6 +1828,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue std::call_once(initMap, [](){ ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool); + ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_ROTATION, Rotation, rotation, glm::quat); @@ -1170,6 +1905,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString); // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); @@ -1187,6 +1923,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); + ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_CAST_SHADOW, KeyLightCastShadows, keyLightCastShadows, bool); + ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); @@ -1412,6 +2150,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, properties.getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, properties.getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, properties.getVisible()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, properties.getCollisionless()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, properties.getCollisionMask()); APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, properties.getDynamic()); @@ -1563,6 +2302,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, properties.getMaterialMappingPos()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, properties.getMaterialMappingScale()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, properties.getMaterialMappingRot()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, properties.getMaterialData()); } APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); @@ -1784,6 +2524,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONLESS, bool, setCollisionless); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_MASK, uint8_t, setCollisionMask); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DYNAMIC, bool, setDynamic); @@ -1930,6 +2671,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_DATA, QString, setMaterialData); } READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); @@ -2053,6 +2795,7 @@ void EntityItemProperties::markAllChanged() { _angularDampingChanged = true; _nameChanged = true; _visibleChanged = true; + _canCastShadowChanged = true; _colorChanged = true; _alphaChanged = true; _modelURLChanged = true; @@ -2112,6 +2855,7 @@ void EntityItemProperties::markAllChanged() { _materialMappingPosChanged = true; _materialMappingScaleChanged = true; _materialMappingRotChanged = true; + _materialDataChanged = true; // Certifiable Properties _itemNameChanged = true; @@ -2130,6 +2874,9 @@ void EntityItemProperties::markAllChanged() { _ambientLight.markAllChanged(); _skybox.markAllChanged(); + _keyLightModeChanged = true; + _skyboxModeChanged = true; + _ambientLightModeChanged = true; _hazeModeChanged = true; _animation.markAllChanged(); @@ -2250,6 +2997,9 @@ QList EntityItemProperties::listChangedProperties() { if (visibleChanged()) { out += "visible"; } + if (canCastShadowChanged()) { + out += "canCastShadow"; + } if (rotationChanged()) { out += "rotation"; } @@ -2457,6 +3207,9 @@ QList EntityItemProperties::listChangedProperties() { if (materialMappingRotChanged()) { out += "materialMappingRot"; } + if (materialDataChanged()) { + out += "materialData"; + } // Certifiable Properties if (itemNameChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 36ca47291c..38e4f0c8c0 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -117,6 +117,7 @@ public: // bool _fooChanged { false }; DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool, ENTITY_ITEM_DEFAULT_VISIBLE); + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3, ENTITY_ITEM_DEFAULT_DIMENSIONS); DEFINE_PROPERTY_REF(PROP_ROTATION, Rotation, rotation, glm::quat, ENTITY_ITEM_DEFAULT_ROTATION); @@ -207,7 +208,7 @@ public: DEFINE_PROPERTY(PROP_NORMALS, Normals, normals, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector, QVector()); - DEFINE_PROPERTY(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, bool, true); + DEFINE_PROPERTY(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, bool, true); DEFINE_PROPERTY_REF(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString, ""); DEFINE_PROPERTY_REF(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString, ""); DEFINE_PROPERTY_REF(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString, ""); @@ -229,6 +230,7 @@ public: DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0)); DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1)); DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); + DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, ""); // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); @@ -325,7 +327,7 @@ public: void clearSimulationOwner(); void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const QByteArray& data); - void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); } + void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); } void setActionDataDirty() { _actionDataChanged = true; } @@ -426,6 +428,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Velocity, velocity, "in meters"); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Name, name, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Visible, visible, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, CanCastShadow, canCastShadow, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Rotation, rotation, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Density, density, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Gravity, gravity, ""); @@ -500,6 +503,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalPosition, localPosition, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalRotation, localRotation, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalVelocity, localVelocity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalAngularVelocity, localAngularVelocity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalDimensions, localDimensions, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index eb09a64628..0e0c2994cd 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,6 +46,7 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; +const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { true }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; diff --git a/libraries/entities/src/EntityNodeData.h b/libraries/entities/src/EntityNodeData.h index eb5a1610cc..090a5e0526 100644 --- a/libraries/entities/src/EntityNodeData.h +++ b/libraries/entities/src/EntityNodeData.h @@ -33,7 +33,7 @@ public: // these can only be called from the OctreeSendThread for the given Node void insertSentFilteredEntity(const QUuid& entityID) { _sentFilteredEntities.insert(entityID); } void removeSentFilteredEntity(const QUuid& entityID) { _sentFilteredEntities.remove(entityID); } - bool sentFilteredEntity(const QUuid& entityID) { return _sentFilteredEntities.contains(entityID); } + bool sentFilteredEntity(const QUuid& entityID) const { return _sentFilteredEntities.contains(entityID); } QSet getSentFilteredEntities() { return _sentFilteredEntities; } // the following flagged extra entity methods can only be called from the OctreeSendThread for the given Node diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index b65d5d1a3f..99a5f287ea 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -20,6 +20,7 @@ enum EntityPropertyList { // these properties are supported by the EntityItem base class PROP_VISIBLE, + PROP_CAN_CAST_SHADOW, PROP_POSITION, PROP_DIMENSIONS, PROP_ROTATION, @@ -205,6 +206,11 @@ enum EntityPropertyList { PROP_HAZE_MODE, + PROP_KEYLIGHT_COLOR, + PROP_KEYLIGHT_INTENSITY, + PROP_KEYLIGHT_DIRECTION, + PROP_KEYLIGHT_CAST_SHADOW, + PROP_HAZE_RANGE, PROP_HAZE_COLOR, PROP_HAZE_GLARE_COLOR, @@ -234,6 +240,7 @@ enum EntityPropertyList { PROP_MATERIAL_MAPPING_POS, PROP_MATERIAL_MAPPING_SCALE, PROP_MATERIAL_MAPPING_ROT, + PROP_MATERIAL_DATA, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line @@ -254,10 +261,6 @@ enum EntityPropertyList { // Aliases/Piggyback properties for Zones. These properties intentionally reuse the enum values for // other properties which will never overlap with each other. We do this so that we don't have to expand // the size of the properties bitflags mask - PROP_KEYLIGHT_COLOR = PROP_COLOR, - PROP_KEYLIGHT_INTENSITY = PROP_INTENSITY, - PROP_KEYLIGHT_DIRECTION = PROP_EXPONENT, - PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 93cbe781be..58898216fa 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -241,30 +241,23 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _activityTracking.addedEntityCount++; + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); + EntityItemProperties propertiesWithSimID = properties; if (clientOnly) { - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); + const QUuid myNodeID = sessionID; propertiesWithSimID.setClientOnly(clientOnly); propertiesWithSimID.setOwningAvatarID(myNodeID); } + propertiesWithSimID.setLastEditedBy(sessionID); + bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); - auto dimensions = propertiesWithSimID.getDimensions(); - float volume = dimensions.x * dimensions.y * dimensions.z; - auto density = propertiesWithSimID.getDensity(); - auto newVelocity = propertiesWithSimID.getVelocity().length(); - float cost = calculateCost(density * volume, 0, newVelocity); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - return QUuid(); - } - EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. @@ -295,9 +288,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties // queue the packet if (success) { - emit debitEnergySource(cost); queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); - return id; } else { return QUuid(); @@ -321,6 +312,11 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin if (!textures.isEmpty()) { properties.setTextures(textures); } + + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); + properties.setLastEditedBy(sessionID); + return addEntity(properties); } @@ -376,29 +372,15 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& _activityTracking.editedEntityCount++; - EntityItemProperties properties = scriptSideProperties; + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); - auto dimensions = properties.getDimensions(); - float volume = dimensions.x * dimensions.y * dimensions.z; - auto density = properties.getDensity(); - auto newVelocity = properties.getVelocity().length(); - float oldVelocity = { 0.0f }; + EntityItemProperties properties = scriptSideProperties; + properties.setLastEditedBy(sessionID); EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); - - //if there is no local entity entity tree, no existing velocity, use 0. - float cost = calculateCost(density * volume, oldVelocity, newVelocity); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - return QUuid(); - } else { - //debit the avatar energy and continue - emit debitEnergySource(cost); - } - return id; } // If we have a local entity tree set, then also update it. @@ -410,7 +392,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& return; } - auto nodeList = DependencyManager::get(); if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { // don't edit other avatar's avatarEntities return; @@ -420,9 +401,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. - //existing entity, retrieve old velocity for check down below - oldVelocity = entity->getWorldVelocity().length(); - if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } @@ -442,23 +420,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); - - float cost = calculateCost(density * volume, oldVelocity, newVelocity); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - updatedEntity = false; - } else { - //debit the avatar energy and continue - updatedEntity = _entityTree->updateEntity(entityID, properties); - if (updatedEntity) { - emit debitEnergySource(cost); - } - } + updatedEntity = _entityTree->updateEntity(entityID, properties); }); // FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially - // breaks avatar energy and entities that are parented. + // breaks entities that are parented. // // To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist // in the local entity tree, we need to allow those edits to go through to the server. @@ -500,7 +466,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); - entity->rememberHasSimulationOwnershipBid(); } } if (properties.queryAACubeRelatedPropertyChanged()) { @@ -523,6 +488,27 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } } }); + } else { + // Sometimes ESS don't have the entity they are trying to edit in their local tree. In this case, + // convertPropertiesFromScriptSemantics doesn't get called and local* edits will get dropped. + // This is because, on the script side, "position" is in world frame, but in the network + // protocol and in the internal data-structures, "position" is "relative to parent". + // Compensate here. The local* versions will get ignored during the edit-packet encoding. + if (properties.localPositionChanged()) { + properties.setPosition(properties.getLocalPosition()); + } + if (properties.localRotationChanged()) { + properties.setRotation(properties.getLocalRotation()); + } + if (properties.localVelocityChanged()) { + properties.setVelocity(properties.getLocalVelocity()); + } + if (properties.localAngularVelocityChanged()) { + properties.setAngularVelocity(properties.getLocalAngularVelocity()); + } + if (properties.localDimensionsChanged()) { + properties.setDimensions(properties.getLocalDimensions()); + } } }); if (!entityFound) { @@ -538,7 +524,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& NestableType nestableType = nestable->getNestableType(); if (nestableType == NestableType::Overlay || nestableType == NestableType::Avatar) { qCWarning(entities) << "attempted edit on non-entity: " << id << nestable->getName(); - return QUuid(); // null UUID to indicate failure + return QUuid(); // null script value to indicate failure } } } @@ -577,26 +563,11 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { return; } - auto dimensions = entity->getScaledDimensions(); - float volume = dimensions.x * dimensions.y * dimensions.z; - auto density = entity->getDensity(); - auto velocity = entity->getWorldVelocity().length(); - float cost = calculateCost(density * volume, velocity, 0); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - shouldDelete = false; - return; - } else { - //debit the avatar energy and continue - emit debitEnergySource(cost); - } - if (entity->getLocked()) { shouldDelete = false; } else { // only delete local entities, server entities will round trip through the server filters - if (entity->getClientOnly()) { + if (entity->getClientOnly() || _entityTree->isServerlessMode()) { _entityTree->deleteEntity(entityID); } } @@ -1017,6 +988,25 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c QString faceName = ""; // handle BoxFace + /**jsdoc + *

A BoxFace specifies the face of an axis-aligned (AA) box. + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
+ * @typedef {string} BoxFace + */ + // FIXME: Move enum to string function to BoxBase.cpp. switch (value.face) { case MIN_X_FACE: faceName = "MIN_X_FACE"; @@ -1266,10 +1256,10 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, } doTransmit = actor(simulation, entity); + _entityTree->entityChanged(entity); if (doTransmit) { properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); - _entityTree->entityChanged(entity); } }); @@ -1793,23 +1783,6 @@ void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, con } } -float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { - return std::abs(mass * (newVelocity - oldVelocity)); -} - -void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) { - // qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy; - _currentAvatarEnergy = energy; -} - -float EntityScriptingInterface::getCostMultiplier() { - return costMultiplier; -} - -void EntityScriptingInterface::setCostMultiplier(float value) { - costMultiplier = value; -} - // TODO move this someplace that makes more sense... bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 6b4cd81e44..4c2a2a47b4 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -52,6 +52,23 @@ private: QPointer _engine; }; +/**jsdoc + * The result of a {@link PickRay} search using {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}. + * @typedef {object} Entities.RayToEntityIntersectionResult + * @property {boolean} intersects - true if the {@link PickRay} intersected an entity, otherwise + * false. + * @property {boolean} accurate - Is always true. + * @property {Uuid} entityID - The ID if the entity intersected, if any, otherwise null. + * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. + * @property {Vec3} intersection - The intersection point. + * @property {Vec3} surfaceNormal - The surface normal of the entity at the intersection point. + * @property {BoxFace} face - The face of the entity's axis-aligned box that the ray intersects. + * @property {object} extraInfo - Extra information depending on the entity intersected. Currently, only Model + * entities provide extra information, and the information provided depends on the precisionPicking parameter + * value that the search function was called with. + */ +// "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock. class RayToEntityIntersectionResult { public: RayToEntityIntersectionResult(); @@ -72,14 +89,19 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra /**jsdoc + * The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible + * to everyone and typically are persisted to the domain. For Interface scripts, the entities available are those that + * Interface has displayed and so knows about. + * * @namespace Entities + * @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus. + * If no entity has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to + * clear keyboard focus. */ /// handles scripting of Entity commands from JS passed to assigned clients class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT - Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy) - Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier) Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity) friend EntityPropertyMetadataRequest; @@ -100,65 +122,94 @@ public: void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setEntitiesScriptEngine(QSharedPointer engine); - float calculateCost(float mass, float oldVelocity, float newVelocity); void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } public slots: /**jsdoc - * Returns `true` if the DomainServer will allow this Node/Avatar to make changes - * + * Check whether or not you can change the locked property of entities. Locked entities have their + * locked property set to true and cannot be edited or deleted. Whether or not you can change + * entities' locked properties is configured in the domain server's permissions. * @function Entities.canAdjustLocks - * @return {bool} `true` if the client can adjust locks, `false` if not. + * @returns {boolean} true if the client can change the locked property of entities, + * otherwise false. + * @example Set an entity's locked property to true if you can. + * if (Entities.canAdjustLocks()) { + * Entities.editEntity(entityID, { locked: true }); + * } else { + * Window.alert("You do not have the permissions to set an entity locked!"); + * } */ Q_INVOKABLE bool canAdjustLocks(); /**jsdoc + * Check whether or not you can rez (create) new entities in the domain. * @function Entities.canRez - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new entities + * @returns {boolean} true if the domain server will allow the script to rez (create) new entities, + * otherwise false. */ Q_INVOKABLE bool canRez(); /**jsdoc + * Check whether or not you can rez (create) new temporary entities in the domain. Temporary entities are entities with a + * finite lifetime property value set. * @function Entities.canRezTmp - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new temporary entities + * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary + * entities, otherwise false. */ Q_INVOKABLE bool canRezTmp(); /**jsdoc - * @function Entities.canRezCertified - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new certified entities - */ + * Check whether or not you can rez (create) new certified entities in the domain. Certified entities are entities that have + * PoP certificates. + * @function Entities.canRezCertified + * @returns {boolean} true if the domain server will allow the script to rez (create) new certified + * entities, otherwise false. + */ Q_INVOKABLE bool canRezCertified(); /**jsdoc - * @function Entities.canRezTmpCertified - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new temporary certified entities - */ + * Check whether or not you can rez (create) new temporary certified entities in the domain. Temporary entities are entities + * with a finite lifetime property value set. Certified entities are entities that have PoP certificates. + * @function Entities.canRezTmpCertified + * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary + * certified entities, otherwise false. + */ Q_INVOKABLE bool canRezTmpCertified(); /**jsdoc - * @function Entities.canWriteAssets - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server - */ + * Check whether or not you can make changes to the asset server's assets. + * @function Entities.canWriteAssets + * @returns {boolean} true if the domain server will allow the script to make changes to the asset server's + * assets, otherwise false. + */ Q_INVOKABLE bool canWriteAssets(); /**jsdoc - * @function Entities.canReplaceContent - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set - */ + * Check whether or not you can replace the domain's content set. + * @function Entities.canReplaceContent + * @returns {boolean} true if the domain server will allow the script to replace the domain's content set, + * otherwise false. + */ Q_INVOKABLE bool canReplaceContent(); /**jsdoc - * Add a new entity with the specified properties. If `clientOnly` is true, the entity will - * not be sent to the server and will only be visible/accessible on the local client. - * + * Add a new entity with specified properties. * @function Entities.addEntity - * @param {EntityItemProperties} properties Properties of the entity to create. - * @param {bool} [clientOnly=false] Whether the entity should only exist locally or not. - * @return {EntityID} The entity ID of the newly created entity. The ID will be a null - * UUID (`{00000000-0000-0000-0000-000000000000}`) if the entity could not be created. + * @param {Entities.EntityProperties} properties - The properties of the entity to create. + * @param {boolean} [clientOnly=false] - If true, or if clientOnly is set true in + * the properties, the entity is created as an avatar entity; otherwise it is created on the server. An avatar entity + * follows you to each domain you visit, rendering at the same world coordinates unless it's parented to your avatar. + * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}. + * @example Create a box entity in front of your avatar. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * print("Entity created: " + entityID); */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false); @@ -168,115 +219,202 @@ public slots: bool collisionless, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc - * Return the properties for the specified {EntityID}. - * not be sent to the server and will only be visible/accessible on the local client. - * @param {EntityItemProperties} properties Properties of the entity to create. - * @param {EntityPropertyFlags} [desiredProperties=[]] Array containing the names of the properties you - * would like to get. If the array is empty, all properties will be returned. - * @return {EntityItemProperties} The entity properties for the specified entity. + * Get the properties of an entity. + * @function Entities.getEntityProperties + * @param {Uuid} entityID - The ID of the entity to get the properties of. + * @param {string[]} [desiredProperties=[]] - Array of the names of the properties to get. If the array is empty, + * all properties are returned. + * @returns {Entities.EntityProperties} The properties of the entity if the entity can be found, otherwise an empty object. + * @example Report the color of a new box entity. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * var properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID); Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties); /**jsdoc - * Updates an entity with the specified properties. - * + * Update an entity with specified properties. * @function Entities.editEntity - * @return {EntityID} The EntityID of the entity if the edit was successful, otherwise the null {EntityID}. + * @param {Uuid} entityID - The ID of the entity to edit. + * @param {Entities.EntityProperties} properties - The properties to update the entity with. + * @returns {Uuid} The ID of the entity if the edit was successful, otherwise null. + * @example Change the color of an entity. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * var properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); + * + * Entities.editEntity(entityID, { + * color: { red: 255, green: 0, blue: 0 } + * }); + * properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE QUuid editEntity(QUuid entityID, const EntityItemProperties& properties); /**jsdoc - * Deletes an entity. - * + * Delete an entity. * @function Entities.deleteEntity - * @param {EntityID} entityID The ID of the entity to delete. + * @param {Uuid} entityID - The ID of the entity to delete. + * @example Delete an entity a few seconds after creating it. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * + * Script.setTimeout(function () { + * Entities.deleteEntity(entityID); + * }, 3000); */ Q_INVOKABLE void deleteEntity(QUuid entityID); + /**jsdoc - * Call a method on an entity in the same context as this function is called. Allows a script - * to call a method on an entity's script. The method will execute in the entity script engine. - * If the entity does not have an entity script or the method does not exist, this call will - * have no effect. If it is running an entity script (specified by the `script` property) - * and it exposes a property with the specified name `method`, it will be called - * using `params` as the list of arguments. If this is called within an entity script, the - * method will be executed on the client in the entity script engine in which it was called. If - * this is called in an entity server script, the method will be executed on the entity server - * script engine. - * + * Call a method in a client entity script from a client script or client entity script, or call a method in a server + * entity script from a server entity script. The entity script method must be exposed as a property in the target client + * entity script. Additionally, if calling a server entity script, the server entity script must include the method's name + * in an exposed property called remotelyCallable that is an array of method names that can be called. * @function Entities.callEntityMethod - * @param {EntityID} entityID The ID of the entity to call the method on. - * @param {string} method The name of the method to call. - * @param {string[]} params The list of parameters to call the specified method with. + * @param {Uuid} entityID - The ID of the entity to call the method in. + * @param {string} method - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. */ Q_INVOKABLE void callEntityMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Call a server method on an entity. Allows a client entity script to call a method on an - * entity's server script. The method will execute in the entity server script engine. If - * the entity does not have an entity server script or the method does not exist, this call will - * have no effect. If the entity is running an entity script (specified by the `serverScripts` property) - * and it exposes a property with the specified name `method`, it will be called using `params` as - * the list of arguments. - * - * @function Entities.callEntityServerMethod - * @param {EntityID} entityID The ID of the entity to call the method on. - * @param {string} method The name of the method to call. - * @param {string[]} params The list of parameters to call the specified method with. - */ + * Call a method in a server entity script from a client script or client entity script. The entity script method must be + * exposed as a property in the target server entity script. Additionally, the target server entity script must include the + * method's name in an exposed property called remotelyCallable that is an array of method names that can be + * called. + * @function Entities.callEntityServerMethod + * @param {Uuid} entityID - The ID of the entity to call the method in. + * @param {string} method - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + */ Q_INVOKABLE void callEntityServerMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Call a client method on an entity on a specific client node. Allows a server entity script to call a - * method on an entity's client script for a particular client. The method will execute in the entity script - * engine on that single client. If the entity does not have an entity script or the method does not exist, or - * the client is not connected to the domain, or you attempt to make this call outside of the entity server - * script, this call will have no effect. - * - * @function Entities.callEntityClientMethod - * @param {SessionID} clientSessionID The session ID of the client to call the method on. - * @param {EntityID} entityID The ID of the entity to call the method on. - * @param {string} method The name of the method to call. - * @param {string[]} params The list of parameters to call the specified method with. - */ - Q_INVOKABLE void callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, const QStringList& params = QStringList()); + * Call a method in a specific user's client entity script from a server entity script. The entity script method must be + * exposed as a property in the target client entity script. + * @function Entities.callEntityClientMethod + * @param {Uuid} clientSessionID - The session ID of the user to call the method in. + * @param {Uuid} entityID - The ID of the entity to call the method in. + * @param {string} method - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + */ + Q_INVOKABLE void callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, + const QStringList& params = QStringList()); + /**jsdoc - * finds the closest model to the center point, within the radius - * will return a EntityItemID.isKnownID = false if no models are in the radius - * this function will not find any models in script engine contexts which don't have access to models + * Find the entity with a position closest to a specified point and within a specified radius. * @function Entities.findClosestEntity - * @param {vec3} center point - * @param {float} radius to search - * @return {EntityID} The EntityID of the entity that is closest and in the radius. + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @returns {Uuid} The ID of the entity that is closest to the center and within the radius if + * there is one, otherwise null. + * @example Find the closest entity within 10m of your avatar. + * var entityID = Entities.findClosestEntity(MyAvatar.position, 10); + * print("Closest entity: " + entityID); */ + /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QUuid findClosestEntity(const glm::vec3& center, float radius) const; - /// finds models within the search sphere specified by the center point and radius + /**jsdoc + * Find all entities that intersect a sphere defined by a center point and radius. + * @function Entities.findEntities + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @returns {Uuid[]} An array of entity IDs that were found that intersect the search sphere. The array is empty if no + * entities could be found. + * @example Report how many entities are within 10m of your avatar. + * var entityIDs = Entities.findEntities(MyAvatar.position, 10); + * print("Number of entities within 10m: " + entityIDs.length); + */ /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; - /// finds models within the box specified by the corner and dimensions + /**jsdoc + * Find all entities whose axis-aligned boxes intersect a search axis-aligned box defined by its minimum coordinates corner + * and dimensions. + * @function Entities.findEntitiesInBox + * @param {Vec3} corner - The corner of the search AA box with minimum co-ordinate values. + * @param {Vec3} dimensions - The dimensions of the search AA box. + * @returns {Uuid[]} An array of entity IDs whose AA boxes intersect the search AA box. The array is empty if no entities + * could be found. + */ /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; - /// finds models within the frustum - /// the frustum must have the following properties: - /// - position - /// - orientation - /// - projection - /// - centerRadius + /**jsdoc + * Find all entities whose axis-aligned boxes intersect a search frustum. + * @function Entities.findEntitiesInFrustum + * @param {ViewFrustum} frustum - The frustum to search in. The position, orientation, + * projection, and centerRadius properties must be specified. + * @returns {Uuid[]} An array of entity IDs axis-aligned boxes intersect the frustum. The array is empty if no entities + * could be found. + * @example Report the number of entities in view. + * var entityIDs = Entities.findEntitiesInFrustum(Camera.frustum); + * print("Number of entities in view: " + entityIDs.length); + */ /// this function will not find any models in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; - /// finds entities of the indicated type within a sphere given by the center point and radius - /// @param {QString} string representation of entity type - /// @param {vec3} center point - /// @param {float} radius to search + /**jsdoc + * Find all entities of a particular type that intersect a sphere defined by a center point and radius. + * @function Entities.findEntitiesByType + * @param {Entities.EntityType} entityType - The type of entity to search for. + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @returns {Uuid[]} An array of entity IDs of the specified type that intersect the search sphere. The array is empty if + * no entities could be found. + * @example Report the number of Model entities within 10m of your avatar. + * var entityIDs = Entities.findEntitiesByType("Model", MyAvatar.position, 10); + * print("Number of Model entities within 10m: " + entityIDs.length); + */ /// this function will not find any entities in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; + /**jsdoc + * Find the first entity intersected by a {@link PickRay}. Light and Zone entities are not + * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} + * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.
+ * @function Entities.findRayIntersection + * @param {PickRay} pickRay - The PickRay to use for finding entities. + * @param {boolean} [precisionPicking=false] - If true and the intersected entity is a Model + * entity, the result's extraInfo property includes more information than it otherwise would. + * @param {Uuid[]} [entitiesToInclude=[]] - If not empty then the search is restricted to these entities. + * @param {Uuid[]} [entitiesToDiscard=[]] - Entities to ignore during the search. + * @param {boolean} [visibleOnly=false] - If true then only entities that are + * {@link Entities.EntityProperties|visible} are searched. + * @param {boolean} [collideableOnly=false] - If true then only entities that are not + * {@link Entities.EntityProperties|collisionless} are searched. + * @returns {Entities.RayToEntityIntersectionResult} The result of the search for the first intersected entity. + * @example Find the entity directly in front of your avatar. + * var pickRay = { + * origin: MyAvatar.position, + * direction: Quat.getFront(MyAvatar.orientation) + * }; + * + * var intersection = Entities.findRayIntersection(pickRay, true); + * if (intersection.intersects) { + * print("Entity in front of avatar: " + intersection.entityID); + * } else { + * print("No entity in front of avatar."); + * } + */ /// If the scripting context has visible entities, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. @@ -289,187 +427,1416 @@ public slots: const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly); + /**jsdoc + * Find the first entity intersected by a {@link PickRay}. Light and Zone entities are not + * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} + * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.
+ * This is a synonym for {@link Entities.findRayIntersection|findRayIntersection}. + * @function Entities.findRayIntersectionBlocking + * @param {PickRay} pickRay - The PickRay to use for finding entities. + * @param {boolean} [precisionPicking=false] - If true and the intersected entity is a Model + * entity, the result's extraInfo property includes more information than it otherwise would. + * @param {Uuid[]} [entitiesToInclude=[]] - If not empty then the search is restricted to these entities. + * @param {Uuid[]} [entitiesToDiscard=[]] - Entities to ignore during the search. + * @deprecated This function is deprecated and will soon be removed. Use + * {@link Entities.findRayIntersection|findRayIntersection} instead; it blocks and performs the same function. + */ /// If the scripting context has visible entities, this will determine a ray intersection, and will block in /// order to return an accurate result - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, + const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); + + /**jsdoc + * Reloads an entity's server entity script such that the latest version re-downloaded. + * @function Entities.reloadServerScripts + * @param {Uuid} entityID - The ID of the entity to reload the server entity script of. + * @returns {boolean} true if the reload request was successfully sent to the server, otherwise + * false. + */ Q_INVOKABLE bool reloadServerScripts(QUuid entityID); /**jsdoc - * Query additional metadata for "magic" Entity properties like `script` and `serverScripts`. - * - * @function Entities.queryPropertyMetadata - * @param {EntityID} entityID The ID of the entity. - * @param {string} property The name of the property extended metadata is wanted for. - * @param {ResultCallback} callback Executes callback(err, result) with the query results. + * Gets the status of server entity script attached to an entity + * @function Entities.getServerScriptStatus + * @property {Uuid} entityID - The ID of the entity to get the server entity script status for. + * @property {Entities~getServerScriptStatusCallback} callback - The function to call upon completion. + * @returns {boolean} true always. */ /**jsdoc - * Query additional metadata for "magic" Entity properties like `script` and `serverScripts`. - * - * @function Entities.queryPropertyMetadata - * @param {EntityID} entityID The ID of the entity. - * @param {string} property The name of the property extended metadata is wanted for. - * @param {Object} thisObject The scoping "this" context that callback will be executed within. - * @param {ResultCallback} callbackOrMethodName Executes thisObject[callbackOrMethodName](err, result) with the query results. + * Called when {@link Entities.getServerScriptStatus} is complete. + * @callback Entities~getServerScriptStatusCallback + * @param {boolean} success - true if the server entity script status could be obtained, otherwise + * false. + * @param {boolean} isRunning - true if there is a server entity script running, otherwise false. + * @param {string} status - "running" if there is a server entity script running, otherwise an error string. + * @param {string} errorInfo - "" if there is a server entity script running, otherwise it may contain extra + * information on the error. */ - Q_INVOKABLE bool queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - Q_INVOKABLE bool getServerScriptStatus(QUuid entityID, QScriptValue callback); + /**jsdoc + * Get metadata for certain entity properties such as script and serverScripts. + * @function Entities.queryPropertyMetadata + * @param {Uuid} entityID - The ID of the entity to get the metadata for. + * @param {string} property - The property name to get the metadata for. + * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. + * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise + * false. + * @throws Throws an error if property is not handled yet or callback is not a function. + */ + /**jsdoc + * Get metadata for certain entity properties such as script and serverScripts. + * @function Entities.queryPropertyMetadata + * @param {Uuid} entityID - The ID of the entity to get the metadata for. + * @param {string} property - The property name to get the metadata for. + * @param {object} scope - The "this" context that the callback will be executed within. + * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. + * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise + * false. + * @throws Throws an error if property is not handled yet or callback is not a function. + */ + /**jsdoc + * Called when {@link Entities.queryPropertyMetadata} is complete. + * @callback Entities~queryPropertyMetadataCallback + * @param {string} error - undefined if there was no error, otherwise an error message. + * @param {object} result - The metadata for the requested entity property if there was no error, otherwise + * undefined. + */ + Q_INVOKABLE bool queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + + + /**jsdoc + * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. By default, Light + * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using + * {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.setLightsArePickable + * @param {boolean} value - Set true to make ray picks intersect the bounding box of + * {@link Entities.EntityType|Light} entities, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void setLightsArePickable(bool value); + + /**jsdoc + * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. Ray picks are + * done using {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.getLightsArePickable + * @returns {boolean} true if ray picks intersect the bounding box of {@link Entities.EntityType|Light} + * entities, otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool getLightsArePickable() const; + /**jsdoc + * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. By default, Light + * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using + * {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.setZonesArePickable + * @param {boolean} value - Set true to make ray picks intersect the bounding box of + * {@link Entities.EntityType|Zone} entities, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void setZonesArePickable(bool value); + + /**jsdoc + * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. Ray picks are + * done using {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.getZonesArePickable + * @returns {boolean} true if ray picks intersect the bounding box of {@link Entities.EntityType|Zone} + * entities, otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool getZonesArePickable() const; + /**jsdoc + * Set whether or not {@link Entities.EntityType|Zone} entities' boundaries should be drawn. Currently not used. + * @function Entities.setDrawZoneBoundaries + * @param {boolean} value - Set to true if {@link Entities.EntityType|Zone} entities' boundaries should be + * drawn, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void setDrawZoneBoundaries(bool value); + + /**jsdoc + * Get whether or not {@link Entities.EntityType|Zone} entities' boundaries should be drawn. Currently not used. + * @function Entities.getDrawZoneBoundaries + * @returns {boolean} true if {@link Entities.EntityType|Zone} entities' boundaries should be drawn, + * otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool getDrawZoneBoundaries() const; + + /**jsdoc + * Set the values of all voxels in a spherical portion of a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxelSphere + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} center - The center of the sphere of voxels to set, in world coordinates. + * @param {number} radius - The radius of the sphere of voxels to set, in world coordinates. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox sphere. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: position, + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 32, y: 32, z: 32 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setVoxelSphere(polyVox, position, 0.9, 255); + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); + + /**jsdoc + * Set the values of all voxels in a capsule-shaped portion of a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxelCapsule + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} start - The center of the sphere of voxels to set, in world coordinates. + * @param {Vec3} end - The center of the sphere of voxels to set, in world coordinates. + * @param {number} radius - The radius of the capsule cylinder and spherical ends, in world coordinates. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox capsule shape. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: position, + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 32, y: 32, z: 32 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * var startPosition = Vec3.sum({ x: -0.5, y: 0, z: 0 }, position); + * var endPosition = Vec3.sum({ x: 0.5, y: 0, z: 0 }, position); + * Entities.setVoxelCapsule(polyVox, startPosition, endPosition, 0.5, 255); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setVoxelCapsule(QUuid entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value); + /**jsdoc + * Set the value of a particular voxels in a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxel + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} position - The position relative to the minimum axes values corner of the entity. The + * position coordinates are rounded to the nearest integer to get the voxel coordinate. The minimum axes + * corner voxel is { x: 0, y: 0, z: 0 }. + * @param {number} value - If value % 256 == 0 then voxel is cleared, otherwise the voxel is set. + * @example Create a cube PolyVox entity and clear the minimum axes corner voxel. + * var entity = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(entity, 1); + * Entities.setVoxel(entity, { x: 0, y: 0, z: 0 }, 0); + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value); + + /**jsdoc + * Set the values of all voxels in a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setAllVoxels + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox cube. + * var entity = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(entity, 1); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); - Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition, - const glm::vec3& cuboidSize, int value); - Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector& points); - Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); - - Q_INVOKABLE void dumpTree() const; - - Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); - Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); - Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); - Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); - Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); + /**jsdoc + * Set the values of all voxels in a cubic portion of a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxelsInCuboid + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} lowPosition - The position of the minimum axes value corner of the cube of voxels to set, in voxel + * coordinates. + * @param {Vec3} cuboidSize - The size of the cube of voxels to set, in voxel coordinates. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox cube and clear the voxels in one corner. + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(polyVox, 1); + * var cuboidPosition = { x: 12, y: 12, z: 12 }; + * var cuboidSize = { x: 4, y: 4, z: 4 }; + * Entities.setVoxelsInCuboid(polyVox, cuboidPosition, cuboidSize, 0); + */ + // FIXME move to a renderable entity interface + Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value); + /**jsdoc + * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to world coordinates. Voxel coordinates are + * relative to the minimum axes values corner of the entity with a scale of Vec3.ONE being the dimensions of + * each voxel. + * @function Entities.voxelCoordsToWorldCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. + * @returns {Vec3} The world coordinates of the voxelCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * @example Create a PolyVox cube with the 0,0,0 voxel replaced by a sphere. + * // Cube PolyVox with 0,0,0 voxel missing. + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(polyVox, 1); + * Entities.setVoxel(polyVox, { x: 0, y: 0, z: 0 }, 0); + * + * // Red sphere in 0,0,0 corner position. + * var cornerPosition = Entities.voxelCoordsToWorldCoords(polyVox, { x: 0, y: 0, z: 0 }); + * var voxelDimensions = Vec3.multiply(2 / 16, Vec3.ONE); + * var sphere = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(cornerPosition, Vec3.multiply(0.5, voxelDimensions)), + * dimensions: voxelDimensions, + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords); + + /**jsdoc + * Convert world coordinates to voxel coordinates in a {@link Entities.EntityType|PolyVox} entity. Voxel coordinates are + * relative to the minimum axes values corner of the entity, with a scale of Vec3.ONE being the dimensions of + * each voxel. + * @function Entities.worldCoordsToVoxelCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} worldCoords - The world coordinates. May be outside the entity's bounding box. + * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); + + /**jsdoc + * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to local coordinates relative to the minimum + * axes value corner of the entity, with the scale being the same as world coordinates. + * @function Entities.voxelCoordsToLocalCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. + * @returns {Vec3} The local coordinates of the voxelCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * @example Get the world dimensions of a voxel in a PolyVox entity. + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * var voxelDimensions = Entities.voxelCoordsToLocalCoords(polyVox, Vec3.ONE); + * print("Voxel dimensions: " + JSON.stringify(voxelDimensions)); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords); + + /**jsdoc + * Convert local coordinates to voxel coordinates in a {@link Entities.EntityType|PolyVox} entity. Local coordinates are + * relative to the minimum axes value corner of the entity, with the scale being the same as world coordinates. + * @function Entities.localCoordsToVoxelCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} localCoords - The local coordinates. May be outside the entity's bounding box. + * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); + /**jsdoc + * Set the linePoints property of a {@link Entities.EntityType|Line} entity. + * @function Entities.setAllPoints + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Line} entity. + * @param {Vec3[]} points - The array of points to set the entity's linePoints property to. + * @returns {boolean} true if the entity's property was updated, otherwise false. The property + * may fail to be updated if the entity does not exist, the entity is not a {@link Entities.EntityType|Line} entity, + * one of the points is outside the entity's dimensions, or the number of points is greater than the maximum allowed. + * @example Change the shape of a Line entity. + * // Draw a horizontal line between two points. + * var entity = Entities.addEntity({ + * type: "Line", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 1 }, + * linePoints: [ + * { x: -1, y: 0, z: 0 }, + * { x:1, y: -0, z: 0 } + * ], + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Change the line to be a "V". + * Script.setTimeout(function () { + * Entities.setAllPoints(entity, [ + * { x: -1, y: 1, z: 0 }, + * { x: 0, y: -1, z: 0 }, + * { x: 1, y: 1, z: 0 }, + * ]); + * }, 2000); + */ + Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector& points); + + /**jsdoc + * Append a point to a {@link Entities.EntityType|Line} entity. + * @function Entities.appendPoint + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Line} entity. + * @param {Vec3} point - The point to add to the line. The coordinates are relative to the entity's position. + * @returns {boolean} true if the point was added to the line, otherwise false. The point may + * fail to be added if the entity does not exist, the entity is not a {@link Entities.EntityType|Line} entity, the + * point is outside the entity's dimensions, or the maximum number of points has been reached. + * @example Append a point to a Line entity. + * // Draw a line between two points. + * var entity = Entities.addEntity({ + * type: "Line", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 1 }, + * linePoints: [ + * { x: -1, y: 1, z: 0 }, + * { x: 0, y: -1, z: 0 } + * ], + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Add a third point to create a "V". + * Entities.appendPoint(entity, { x: 1, y: 1, z: 0 }); + */ + Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); + + /**jsdoc + * Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about — domain + * and client-only — to the program log. + * @function Entities.dumpTree + */ + Q_INVOKABLE void dumpTree() const; + + + /**jsdoc + * Add an action to an entity. An action is registered with the physics engine and is applied every physics simulation + * step. Any entity may have more than one action associated with it, but only as many as will fit in an entity's + * actionData property. + * @function Entities.addAction + * @param {Entities.ActionType} actionType - The type of action. + * @param {Uuid} entityID - The ID of the entity to add the action to. + * @param {Entities.ActionArguments} arguments - Configure the action. + * @returns {Uuid} The ID of the action added if successfully added, otherwise null. + * @example Constrain a cube to move along a vertical line. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * dynamic: true, + * collisionless: false, + * userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }", + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var actionID = Entities.addAction("slider", entityID, { + * axis: { x: 0, y: 1, z: 0 }, + * linearLow: 0, + * linearHigh: 0.6 + * }); + */ + Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); + + /**jsdoc + * Update an entity action. + * @function Entities.updateAction + * @param {Uuid} entityID - The ID of the entity with the action to update. + * @param {Uuid} actionID - The ID of the action to update. + * @param {Entities.ActionArguments} arguments - The arguments to update. + * @returns {boolean} true if the update was successful, otherwise false. + */ + Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); + + /**jsdoc + * Delete an action from an entity. + * @function Entities.deleteAction + * @param {Uuid} entityID - The ID of entity to delete the action from. + * @param {Uuid} actionID - The ID of the action to delete. + * @returns {boolean} true if the update was successful, otherwise false. + */ + Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); + + /**jsdoc + * Get the IDs of the actions that are associated with an entity. + * @function Entities.getActionIDs + * @param {Uuid} entityID - The entity to get the action IDs for. + * @returns {Uuid[]} An array of action IDs if any are found, otherwise an empty array. + */ + Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); + + /**jsdoc + * Get the arguments of an action. + * @function Entities.getActionArguments + * @param {Uuid} entityID - The ID of the entity with the action. + * @param {Uuid} actionID - The ID of the action to get the arguments of. + * @returns {Entities.ActionArguments} The arguments of the requested action if found, otherwise an empty object. + */ + Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); + + + /**jsdoc + * Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.getAbsoluteJointTranslationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Vec3} The translation of the joint relative to the entity's position and orientation if the entity is a + * {@link Entities.EntityType|Model} entity, the entity is loaded, and the joint index is valid; otherwise + * {@link Vec3(0)|Vec3.ZERO}. + */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.getAbsoluteJointRotationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Quat} The rotation of the joint relative to the entity's orientation if the entity is a + * {@link Entities.EntityType|Model} entity, the entity is loaded, and the joint index is valid; otherwise + * {@link Quat(0)|Quat.IDENTITY}. + * @example Compare the local and absolute rotations of an avatar model's left hand joint. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "LeftHand"); + * var localRotation = Entities.getLocalJointRotation(entityID, index); + * var absoluteRotation = Entities.getAbsoluteJointRotationInObjectFrame(entityID, index); + * print("Left hand local rotation: " + JSON.stringify(Quat.safeEulerAngles(localRotation))); + * print("Left hand absolute rotation: " + JSON.stringify(Quat.safeEulerAngles(absoluteRotation))); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Set the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.setAbsoluteJointTranslationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Vec3} translation - The translation to set the joint to relative to the entity's position and orientation. + * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the translation is different to the joint's current translation; otherwise + * false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec3 translation); + + /**jsdoc + * Set the rotation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.setAbsoluteJointRotationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Quat} rotation - The rotation to set the joint to relative to the entity's orientation. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the rotation is different to the joint's current rotation; otherwise false. + * @example Raise an avatar model's left palm. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "LeftHand"); + * var absoluteRotation = Entities.getAbsoluteJointRotationInObjectFrame(entityID, index); + * absoluteRotation = Quat.multiply(Quat.fromPitchYawRollDegrees(0, 0, 90), absoluteRotation); + * var success = Entities.setAbsoluteJointRotationInObjectFrame(entityID, index, absoluteRotation); + * print("Success: " + success); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::quat rotation); + + /**jsdoc + * Get the local translation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.getLocalJointTranslation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Vec3} The local translation of the joint if the entity is a {@link Entities.EntityType|Model} entity, the + * entity is loaded, and the joint index is valid; otherwise {@link Vec3(0)|Vec3.ZERO}. + */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 getLocalJointTranslation(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Get the local rotation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.getLocalJointRotation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Quat} The local rotation of the joint if the entity is a {@link Entities.EntityType|Model} entity, the entity + * is loaded, and the joint index is valid; otherwise {@link Quat(0)|Quat.IDENTITY}. + * @example Report the local rotation of an avatar model's head joint. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "Head"); + * var rotation = Entities.getLocalJointRotation(entityID, index); + * print("Head local rotation: " + JSON.stringify(Quat.safeEulerAngles(rotation))); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::quat getLocalJointRotation(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Set the local translation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointTranslation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Vec3} translation - The local translation to set the joint to. + * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the translation is different to the joint's current translation; otherwise + * false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec3 translation); + + /**jsdoc + * Set the local rotation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointRotation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Quat} rotation - The local rotation to set the joint to. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the rotation is different to the joint's current rotation; otherwise false. + * @example Make an avatar model turn its head left. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "Head"); + * var rotation = Quat.fromPitchYawRollDegrees(0, 60, 0); + * var success = Entities.setLocalJointRotation(entityID, index, rotation); + * print("Success: " + success); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointRotation(const QUuid& entityID, int jointIndex, glm::quat rotation); + + /**jsdoc + * Set the local translations of joints in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointTranslations + * @param {Uuid} entityID - The ID of the entity. + * @param {Vec3[]} translations - The local translations to set the joints to. + * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the model has joints, and at least one of the translations is different to the model's current translations; + * otherwise false. + */ + // FIXME move to a renderable entity interface + Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); + + /**jsdoc + * Set the local rotations of joints in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointRotations + * @param {Uuid} entityID - The ID of the entity. + * @param {Quat[]} rotations - The local rotations to set the joints to. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the model has joints, and at least one of the rotations is different to the model's current rotations; otherwise + * false. + * @example Raise both palms of an avatar model. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * + * // Get all the joint rotations. + * var jointNames = Entities.getJointNames(entityID); + * var jointRotations = []; + * for (var i = 0, length = jointNames.length; i < length; i++) { + * var index = Entities.getJointIndex(entityID, jointNames[i]); + * jointRotations.push(Entities.getLocalJointRotation(entityID, index)); + * } + * + * // Raise both palms. + * var index = jointNames.indexOf("LeftHand"); + * jointRotations[index] = Quat.multiply(Quat.fromPitchYawRollDegrees(-90, 0, 0), jointRotations[index]); + * index = jointNames.indexOf("RightHand"); + * jointRotations[index] = Quat.multiply(Quat.fromPitchYawRollDegrees(-90, 0, 0), jointRotations[index]); + * + * // Update all the joint rotations. + * var success = Entities.setLocalJointRotations(entityID, jointRotations); + * print("Success: " + success); + * }, 2000); + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointRotations(const QUuid& entityID, const QVector& rotations); - Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); + + /**jsdoc + * Set the local rotations and translations of joints in a {@link Entities.EntityType|Model} entity. This is the same as + * calling both {@link Entities.setLocalJointRotations|setLocalJointRotations} and + * {@link Entities.setLocalJointTranslations|setLocalJointTranslations} at the same time. + * @function Entities.setLocalJointsData + * @param {Uuid} entityID - The ID of the entity. + * @param {Quat[]} rotations - The local rotations to set the joints to. + * @param {Vec3[]} translations - The local translations to set the joints to. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the model has joints, and at least one of the rotations or translations is different to the model's current values; + * otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointsData(const QUuid& entityID, const QVector& rotations, const QVector& translations); + + /**jsdoc + * Get the index of a named joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.getJointIndex + * @param {Uuid} entityID - The ID of the entity. + * @param {string} name - The name of the joint. + * @returns {number} The integer index of the joint if the entity is a {@link Entities.EntityType|Model} entity, the entity + * is loaded, and the joint is present; otherwise -1. The joint indexes are in order per + * {@link Entities.getJointNames|getJointNames}. + * @example Report the index of a model's head joint. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "Head"); + * print("Head joint index: " + index); + * }, 2000); + */ // FIXME move to a renderable entity interface Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); + + /**jsdoc + * Get the names of all the joints in a {@link Entities.EntityType|Model} entity. + * @function Entities.getJointNames + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Model} entity. + * @returns {string[]} The names of all the joints in the entity if it is a {@link Entities.EntityType|Model} entity and + * is loaded, otherwise an empty array. The joint names are in order per {@link Entities.getJointIndex|getJointIndex}. + * @example Report a model's joint names. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var jointNames = Entities.getJointNames(entityID); + * print("Joint names: " + JSON.stringify(jointNames)); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); + /**jsdoc + * Get the IDs of entities, overlays, and avatars that are directly parented to an entity. To get all descendants of an + * entity, recurse on the IDs returned by the function. + * @function Entities.getChildrenIDs + * @param {Uuid} parentID - The ID of the entity to get the children IDs of. + * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID + * entity. Does not include children's children, etc. The array is empty if no children can be found or + * parentID cannot be found. + * @example Report the children of an entity. + * function createEntity(description, position, parent) { + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: position, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * print(description + ": " + entity); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * var root = createEntity("Root", position, Uuid.NULL); + * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); + * var grandChild = createEntity("Grandchild", Vec3.sum(position, { x: 0, y: -2, z: 0 }), child); + * + * var children = Entities.getChildrenIDs(root); + * print("Children of root: " + JSON.stringify(children)); // Only the child entity. + */ Q_INVOKABLE QVector getChildrenIDs(const QUuid& parentID); + + /**jsdoc + * Get the IDs of entities, overlays, and avatars that are directly parented to an entity, overlay, or avatar model's joint. + * @function Entities.getChildrenIDsOfJoint + * @param {Uuid} parentID - The ID of the entity, overlay, or avatar to get the children IDs of. + * @param {number} jointIndex - Integer number of the model joint to get the children IDs of. + * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID + * entity, overlay, or avatar at the jointIndex joint. Does not include children's children, etc. The + * array is empty if no children can be found or parentID cannot be found. + * @example Report the children of your avatar's right hand. + * function createEntity(description, position, parent) { + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: position, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * print(description + ": " + entity); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * var root = createEntity("Root", position, Uuid.NULL); + * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); + * + * Entities.editEntity(root, { + * parentID: MyAvatar.sessionUUID, + * parentJointIndex: MyAvatar.getJointIndex("RightHand") + * }); + * + * var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, MyAvatar.getJointIndex("RightHand")); + * print("Children of hand: " + JSON.stringify(children)); // Only the root entity. + */ Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); + + /**jsdoc + * Check whether an entity or overlay has an entity as an ancestor (parent, parent's parent, etc.). + * @function Entities.isChildOfParent + * @param {Uuid} childID - The ID of the child entity or overlay to test for being a child, grandchild, etc. + * @param {Uuid} parentID - The ID of the parent entity to test for being a parent, grandparent, etc. + * @returns {boolean} true if the childID entity or overlay has the parentID entity + * as a parent or grandparent etc., otherwise false. + * @example Check that a grandchild entity is a child of its grandparent. + * function createEntity(description, position, parent) { + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: position, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * print(description + ": " + entity); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * var root = createEntity("Root", position, Uuid.NULL); + * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); + * var grandChild = createEntity("Grandchild", Vec3.sum(position, { x: 0, y: -2, z: 0 }), child); + * + * print("grandChild has root as parent: " + Entities.isChildOfParent(grandChild, root)); // true + */ Q_INVOKABLE bool isChildOfParent(QUuid childID, QUuid parentID); - Q_INVOKABLE QString getNestableType(QUuid id); + /**jsdoc + * Get the type — entity, overlay, or avatar — of an in-world item. + * @function Entities.getNestableType + * @param {Uuid} entityID - The ID of the item to get the type of. + * @returns {string} The type of the item: "entity" if the item is an entity, "overlay" if the + * the item is an overlay, "avatar" if the item is an avatar; otherwise "unknown" if the item + * cannot be found. + * @example Print some nestable types. + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 1, z: -2 })), + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * print(Entities.getNestableType(entity)); // "entity" + * print(Entities.getNestableType(Uuid.generate())); // "unknown" + */ + Q_INVOKABLE QString getNestableType(QUuid entityID); + /**jsdoc + * Get the ID of the {@link Entities.EntityType|Web} entity that has keyboard focus. + * @function Entities.getKeyboardFocusEntity + * @returns {Uuid} The ID of the {@link Entities.EntityType|Web} entity that has focus, if any, otherwise null. + */ Q_INVOKABLE QUuid getKeyboardFocusEntity() const; + + /**jsdoc + * Set the {@link Entities.EntityType|Web} entity that has keyboard focus. + * @function Entities.setKeyboardFocusEntity + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity to set keyboard focus to. Use + * null or {@link Uuid|Uuid.NULL} to unset keyboard focus from an entity. + */ Q_INVOKABLE void setKeyboardFocusEntity(const EntityItemID& id); + + /**jsdoc + * Emit a {@link Entities.mousePressOnEntity|mousePressOnEntity} event. + * @function Entities.sendMousePressOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendMousePressOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.mouseMoveOnEntity|mouseMoveOnEntity} event. + * @function Entities.sendMouseMoveOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendMouseMoveOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.mouseReleaseOnEntity|mouseReleaseOnEntity} event. + * @function Entities.sendMouseReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendMouseReleaseOnEntity(const EntityItemID& id, const PointerEvent& event); + /**jsdoc + * Emit a {@link Entities.clickDownOnEntity|clickDownOnEntity} event. + * @function Entities.sendClickDownOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendClickDownOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.holdingClickOnEntity|holdingClickOnEntity} event. + * @function Entities.sendHoldingClickOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoldingClickOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.clickReleaseOnEntity|clickReleaseOnEntity} event. + * @function Entities.sendClickReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendClickReleaseOnEntity(const EntityItemID& id, const PointerEvent& event); + /**jsdoc + * Emit a {@link Entities.hoverEnterEntity|hoverEnterEntity} event. + * @function Entities.sendHoverEnterEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoverEnterEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.hoverOverEntity|hoverOverEntity} event. + * @function Entities.sendHoverOverEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoverOverEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.hoverLeaveEntity|hoverLeaveEntity} event. + * @function Entities.sendHoverLeaveEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoverLeaveEntity(const EntityItemID& id, const PointerEvent& event); + /**jsdoc + * Check whether an entity wants hand controller pointer events. For example, a {@link Entities.EntityType|Web} entity does + * but a {@link Entities.EntityType|Shape} entity doesn't. + * @function Entities.wantsHandControllerPointerEvents + * @param {Uuid} entityID - The ID of the entity. + * @returns {boolean} true if the entity can be found and it wants hand controller pointer events, otherwise + * false. + */ Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id); + /**jsdoc + * Send a script event over a {@link Entities.EntityType|Web} entity's EventBridge to the Web page's scripts. + * @function Entities.emitScriptEvent + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity. + * @param {string} message - The message to send. + * @todo This function is currently not implemented. + */ Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message); + /**jsdoc + * Check whether an axis-aligned box and a capsule intersect. + * @function Entities.AABoxIntersectsCapsule + * @param {Vec3} brn - The bottom right near (minimum axes values) corner of the AA box. + * @param {Vec3} dimensions - The dimensions of the AA box. + * @param {Vec3} start - One end of the capsule. + * @param {Vec3} end - The other end of the capsule. + * @param {number} radius - The radiues of the capsule. + * @returns {boolean} true if the AA box and capsule intersect, otherwise false. + */ Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); + /**jsdoc + * Get the meshes in a {@link Entities.EntityType|Model} or {@link Entities.EntityType|PolyVox} entity. + * @function Entities.getMeshes + * @param {Uuid} entityID - The ID of the Model or PolyVox entity to get the meshes of. + * @param {Entities~getMeshesCallback} callback - The function to call upon completion. + * @deprecated Use the {@link Graphics} API instead. + */ + /**jsdoc + * Called when {@link Entities.getMeshes} is complete. + * @callback Entities~getMeshesCallback + * @param {MeshProxy[]} meshes - If success< is true, a {@link MeshProxy} per mesh in the + * Model or PolyVox entity; otherwise undefined. + * @param {boolean} success - true if the {@link Entities.getMeshes} call was successful, false + * otherwise. The call may be unsuccessful if the requested entity could not be found. + * @deprecated Use the {@link Graphics} API instead. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); /**jsdoc - * Returns object to world transform, excluding scale - * + * Get the object to world transform, excluding scale, of an entity. * @function Entities.getEntityTransform - * @param {EntityID} entityID The ID of the entity whose transform is to be returned - * @return {Mat4} Entity's object to world transform, excluding scale + * @param {Uuid} entityID - The ID of the entity. + * @returns {Mat4} The entity's object to world transform excluding scale (i.e., translation and rotation, with scale of 1) + * if the entity can be found, otherwise a transform with zero translation and rotation and a scale of 1. + * @example Position and rotation in an entity's world transform. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 1, z: -2 })); + * var orientation = MyAvatar.orientation; + * print("Position: " + JSON.stringify(position)); + * print("Orientation: " + JSON.stringify(orientation)); + * + * var entityID = Entities.addEntity({ + * type: "Sphere", + * position: position, + * rotation: orientation, + * dimensions: Vec3.HALF, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var transform = Entities.getEntityTransform(entityID); + * print("Transform: " + JSON.stringify(transform)); + * print("Translation: " + JSON.stringify(Mat4.extractTranslation(transform))); // Same as position. + * print("Rotation: " + JSON.stringify(Mat4.extractRotation(transform))); // Same as orientation. + * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ Q_INVOKABLE glm::mat4 getEntityTransform(const QUuid& entityID); - /**jsdoc - * Returns object to world transform, excluding scale - * + * Get the object to parent transform, excluding scale, of an entity. * @function Entities.getEntityLocalTransform - * @param {EntityID} entityID The ID of the entity whose local transform is to be returned - * @return {Mat4} Entity's object to parent transform, excluding scale - */ + * @param {Uuid} entityID - The ID of the entity. + * @returns {Mat4} The entity's object to parent transform excluding scale (i.e., translation and rotation, with scale of + * 1) if the entity can be found, otherwise a transform with zero translation and rotation and a scale of 1. + * @example Position and rotation in an entity's local transform. + * function createEntity(position, rotation, parent) { + * var entity = Entities.addEntity({ + * type: "Box", + * position: position, + * rotation: rotation, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * + * var parent = createEntity(position, MyAvatar.orientation, Uuid.NULL); + * + * var childTranslation = { x: 0, y: -1.5, z: 0 }; + * var childRotation = Quat.fromPitchYawRollDegrees(0, 45, 0); + * var child = createEntity(Vec3.sum(position, childTranslation), Quat.multiply(childRotation, MyAvatar.orientation), parent); + * + * var transform = Entities.getEntityLocalTransform(child); + * print("Transform: " + JSON.stringify(transform)); + * print("Translation: " + JSON.stringify(Mat4.extractTranslation(transform))); // childTranslation + * print("Rotation: " + JSON.stringify(Quat.safeEulerAngles(Mat4.extractRotation(transform)))); // childRotation + * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); - /**jsdoc - * Return the Static Certificate JSON for the specified {EntityID}. - * @return {QByteArray} The Static Certificate JSON for the specified entity. + * Get the static certificate for an entity. The static certificate contains static properties of the item which cannot + * be altered. + * @function Entities.getStaticCertificateJSON + * @param {Uuid} entityID - The ID of the entity to get the static certificate for. + * @returns {string} The entity's static certificate as a JSON string if the entity can be found, otherwise an empty string. */ Q_INVOKABLE QString getStaticCertificateJSON(const QUuid& entityID); + + /**jsdoc + * Verify the entity's proof of provenance, i.e., that the entity's certificateID property was produced by + * High Fidelity signing the entity's static certificate JSON. + * @function Entities.verifyStaticCertificateProperties + * @param {Uuid} entityID - The ID of the entity to verify. + * @returns {boolean} true if the entity can be found an its certificateID property is present + * and its value matches the entity's static certificate JSON; otherwise false. + */ Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID); signals: + /**jsdoc + * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered + * for a collision with an avatar. + * @function Entities.collisionWithEntity + * @param {Uuid} idA - The ID of one entity in the collision. For an entity script, this is the ID of the entity containing + * the script. + * @param {Uuid} idB - The ID of the other entity in the collision. + * @param {Collision} collision - The details of the collision. + * @returns {Signal} + * @example Change the color of an entity when it collides with another entity. + * var entityScript = (function () { + * function randomInteger(min, max) { + * return Math.floor(Math.random() * (max - min + 1)) + min; + * } + * + * this.collisionWithEntity = function (myID, otherID, collision) { + * Entities.editEntity(myID, { + * color: { + * red: randomInteger(128, 255), + * green: randomInteger(128, 255), + * blue: randomInteger(128, 255) + * } + * }); + * }; + * }); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 128, green: 128, blue: 128 }, + * gravity: { x: 0, y: -9.8, z: 0 }, + * velocity: { x: 0, y: 0.1, z: 0 }, // Kick off physics. + * dynamic: true, + * collisionless: false, // So that collision events are generated. + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); + /**jsdoc + * Triggered when your ability to change the locked property of entities changes. + * @function Entities.canAdjustLocksChanged + * @param {boolean} canAdjustLocks - true if the script can change the locked property of an + * entity, otherwise false. + * @returns {Signal} + * @example Report when your ability to change locks changes. + * function onCanAdjustLocksChanged(canAdjustLocks) { + * print("You can adjust entity locks: " + canAdjustLocks); + * } + * Entities.canAdjustLocksChanged.connect(onCanAdjustLocksChanged); + */ void canAdjustLocksChanged(bool canAdjustLocks); + + /**jsdoc + * Triggered when your ability to rez (create) entities changes. + * @function Entities.canRezChanged + * @param {boolean} canRez - true if the script can rez (create) entities, otherwise false. + * @returns {Signal} + */ void canRezChanged(bool canRez); - void canRezTmpChanged(bool canRez); - void canRezCertifiedChanged(bool canRez); - void canRezTmpCertifiedChanged(bool canRez); + + /**jsdoc + * Triggered when your ability to rez (create) temporary entities changes. Temporary entities are entities with a finite + * lifetime property value set. + * @function Entities.canRezTmpChanged + * @param {boolean} canRezTmp - true if the script can rez (create) temporary entities, otherwise + * false. + * @returns {Signal} + */ + void canRezTmpChanged(bool canRezTmp); + + /**jsdoc + * Triggered when your ability to rez (create) certified entities changes. Certified entities are entities that have PoP + * certificates. + * @function Entities.canRezCertifiedChanged + * @param {boolean} canRezCertified - true if the script can rez (create) certified entities, otherwise + * false. + * @returns {Signal} + */ + void canRezCertifiedChanged(bool canRezCertified); + + /**jsdoc + * Triggered when your ability to rez (create) temporary certified entities changes. Temporary entities are entities with a + * finite lifetime property value set. Certified entities are entities that have PoP certificates. + * @function Entities.canRezTmpCertifiedChanged + * @param {boolean} canRezTmpCertified - true if the script can rez (create) temporary certified entities, + * otherwise false. + * @returns {Signal} + */ + void canRezTmpCertifiedChanged(bool canRezTmpCertified); + + /**jsdoc + * Triggered when your ability to make changes to the asset server's assets changes. + * @function Entities.canWriteAssetsChanged + * @param {boolean} canWriteAssets - true if the script can change the ? property of an entity, + * otherwise false. + * @returns {Signal} + */ void canWriteAssetsChanged(bool canWriteAssets); + + /**jsdoc + * Triggered when a mouse button is clicked while the mouse cursor is on an entity, or a controller trigger is fully + * pressed while its laser is on an entity. + * @function Entities.mousePressOnEntity + * @param {Uuid} entityID - The ID of the entity that was pressed. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + * @example Report when an entity is clicked with the mouse or laser. + * function onMousePressOnEntity(entityID, event) { + * print("Clicked on entity: " + entityID); + * } + * + * Entities.mousePressOnEntity.connect(onMousePressOnEntity); + */ void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is double-clicked while the mouse cursor is on an entity. + * @function Entities.mousePressOnEntity + * @param {Uuid} entityID - The ID of the entity that was double-pressed. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseDoublePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Repeatedly triggered while the mouse cursor or controller laser moves on an entity. + * @function Entities.mouseMoveOnEntity + * @param {Uuid} entityID - The ID of the entity that was moved on. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is released after clicking on an entity or the controller trigger is partly or fully + * released after pressing on an entity, even if the mouse pointer or controller laser has moved off the entity. + * @function Entities.mouseReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity that was originally pressed. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is clicked while the mouse cursor is not on an entity. + * @function Entities.mousePressOffEntity + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mousePressOffEntity(); + + /**jsdoc + * Triggered when a mouse button is double-clicked while the mouse cursor is not on an entity. + * @function Entities.mouseDoublePressOffEntity + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseDoublePressOffEntity(); + + /**jsdoc + * Triggered when a mouse button is clicked while the mouse cursor is on an entity. Note: Not triggered by controller. + * @function Entities.clickDownOnEntity + * @param {Uuid} entityID - The ID of the entity that was clicked. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Repeatedly triggered while a mouse button continues to be held after clicking an entity, even if the mouse cursor has + * moved off the entity. Note: Not triggered by controller. + * @function Entities.holdingClickOnEntity + * @param {Uuid} entityID - The ID of the entity that was originally clicked. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is released after clicking on an entity, even if the mouse cursor has moved off the + * entity. Note: Not triggered by controller. + * @function Entities.clickReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity that was originally clicked. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void clickReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + /**jsdoc + * Triggered when the mouse cursor or controller laser starts hovering on an entity. + * @function Entities.hoverEnterEntity + * @param {Uuid} entityID - The ID of the entity that is being hovered. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void hoverEnterEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Repeatedly triggered when the mouse cursor or controller laser moves while hovering over an entity. + * @function Entities.hoverOverEntity + * @param {Uuid} entityID - The ID of the entity that is being hovered. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void hoverOverEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when the mouse cursor or controller laser stops hovering over an entity. + * @function Entities.hoverLeaveEntity + * @param {Uuid} entityID - The ID of the entity that was being hovered. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void hoverLeaveEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when an avatar enters an entity. + * @function Entities.enterEntity + * @param {Uuid} entityID - The ID of the entity that the avatar entered. + * @returns {Signal} + * @example Change the color of an entity when an avatar enters or leaves. + * var entityScript = (function () { + * this.enterEntity = function (entityID) { + * print("Enter entity"); + * Entities.editEntity(entityID, { + * color: { red: 255, green: 64, blue: 64 }, + * }); + * }; + * this.leaveEntity = function (entityID) { + * print("Leave entity"); + * Entities.editEntity(entityID, { + * color: { red: 128, green: 128, blue: 128 }, + * }); + * }; + * }); + * + * var entityID = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 3, y: 3, z: 3 }, + * color: { red: 128, green: 128, blue: 128 }, + * collisionless: true, // So that avatar can walk through entity. + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ void enterEntity(const EntityItemID& entityItemID); + + /**jsdoc + * Triggered when an avatar leaves an entity. + * @function Entities.leaveEntity + * @param {Uuid} entityID - The ID of the entity that the avatar left. + * @returns {Signal} + */ void leaveEntity(const EntityItemID& entityItemID); - void deletingEntity(const EntityItemID& entityID); - void addingEntity(const EntityItemID& entityID); - void clearingEntities(); - void debitEnergySource(float value); + /**jsdoc + * Triggered when an entity is deleted. + * @function Entities.deletingEntity + * @param {Uuid} entityID - The ID of the entity deleted. + * @returns {Signal} + * @example Report when an entity is deleted. + * Entities.deletingEntity.connect(function (entityID) { + * print("Deleted entity: " + entityID); + * }); + */ + void deletingEntity(const EntityItemID& entityID); + + /**jsdoc + * Triggered when an entity is added to Interface's local in-memory tree of entities it knows about. This may occur when + * entities are loaded upon visiting a domain, when the user rotates their view so that more entities become visible, and + * when a domain or client-only entity is added (e.g., by {@Entities.addEntity|addEntity}). + * @function Entities.addingEntity + * @param {Uuid} entityID - The ID of the entity added. + * @returns {Signal} + * @example Report when an entity is added. + * Entities.addingEntity.connect(function (entityID) { + * print("Added entity: " + entityID); + * }); + */ + void addingEntity(const EntityItemID& entityID); + + /**jsdoc + * Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities it knows about + * is cleared. + * @function Entities.clearingEntities + * @returns {Signal} + * @example Report when Interfaces's entity tree is cleared. + * Entities.clearingEntities.connect(function () { + * print("Entities cleared"); + * }); + */ + void clearingEntities(); + + /**jsdoc + * Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the + * script's EventBridge. + * @function Entities.webEventReceived + * @param {Uuid} entityID - The ID of the entity that event was received from. + * @param {string} message - The message received. + * @returns {Signal} + */ void webEventReceived(const EntityItemID& entityItemID, const QVariant& message); protected: @@ -502,14 +1869,8 @@ private: QSharedPointer _entitiesScriptEngine; bool _bidOnSimulationOwnership { false }; - float _currentAvatarEnergy = { FLT_MAX }; - float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } - void setCurrentAvatarEnergy(float energy); ActivityTracking _activityTracking; - float costMultiplier = { 0.01f }; - float getCostMultiplier(); - void setCostMultiplier(float value); }; #endif // hifi_EntityScriptingInterface_h diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 36b0d8ab2d..d034ddedbe 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -20,7 +20,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { if (_entityTree && _entityTree != tree) { _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); @@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { void EntitySimulation::updateEntities() { QMutexLocker lock(&_mutex); - quint64 now = usecTimestampNow(); + uint64_t now = usecTimestampNow(); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); @@ -40,18 +40,14 @@ void EntitySimulation::updateEntities() { sortEntitiesThatMoved(); } -void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { +void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) { QMutexLocker lock(&_mutex); - for (auto entity : _entitiesToDelete) { - // push this entity onto the external list - entitiesToDelete.push_back(entity); - } - _entitiesToDelete.clear(); + entitiesToDelete.swap(_deadEntities); + _deadEntities.clear(); } void EntitySimulation::removeEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - // remove from all internal lists except _entitiesToDelete + // remove from all internal lists except _deadEntities _mortalEntities.remove(entity); _entitiesToUpdate.remove(entity); _entitiesToSort.remove(entity); @@ -67,42 +63,23 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { QMutexLocker lock(&_mutex); entity->clearActions(getThisPointer()); removeEntityInternal(entity); - _entitiesToDelete.insert(entity); - } -} - -void EntitySimulation::addEntityInternal(EntityItemPointer entity) { - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - QMutexLocker lock(&_mutex); - _simpleKinematicEntities.insert(entity); - entity->setLastSimulated(usecTimestampNow()); - } -} - -void EntitySimulation::changeEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - int numKinematicEntities = _simpleKinematicEntities.size(); - _simpleKinematicEntities.insert(entity); - if (numKinematicEntities != _simpleKinematicEntities.size()) { - entity->setLastSimulated(usecTimestampNow()); + if (entity->getElement()) { + _deadEntities.insert(entity); } - } else { - _simpleKinematicEntities.remove(entity); } } // protected -void EntitySimulation::expireMortalEntities(const quint64& now) { +void EntitySimulation::expireMortalEntities(uint64_t now) { if (now > _nextExpiry) { PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _mortalEntities.begin(); while (itemItr != _mortalEntities.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < now) { itemItr = _mortalEntities.erase(itemItr); entity->die(); @@ -122,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { } // protected -void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { +void EntitySimulation::callUpdateOnEntitiesThatNeedIt(uint64_t now) { PerformanceTimer perfTimer("updatingEntities"); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); @@ -176,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { entity->deserializeActions(); if (entity->isMortal()) { _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < _nextExpiry) { _nextExpiry = expiry; } @@ -207,7 +184,6 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. - bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & Simulation::DIRTY_POSITION) { AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); @@ -217,50 +193,45 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; entity->die(); prepareEntityForDelete(entity); - wasRemoved = true; + return; } } - if (!wasRemoved) { - if (dirtyFlags & Simulation::DIRTY_LIFETIME) { - if (entity->isMortal()) { - _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); - if (expiry < _nextExpiry) { - _nextExpiry = expiry; - } - } else { - _mortalEntities.remove(entity); + + if (dirtyFlags & Simulation::DIRTY_LIFETIME) { + if (entity->isMortal()) { + _mortalEntities.insert(entity); + uint64_t expiry = entity->getExpiry(); + if (expiry < _nextExpiry) { + _nextExpiry = expiry; } - entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME); - } - if (entity->needsToCallUpdate()) { - _entitiesToUpdate.insert(entity); } else { - _entitiesToUpdate.remove(entity); + _mortalEntities.remove(entity); } - changeEntityInternal(entity); + entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME); } + if (entity->needsToCallUpdate()) { + _entitiesToUpdate.insert(entity); + } else { + _entitiesToUpdate.remove(entity); + } + changeEntityInternal(entity); } void EntitySimulation::clearEntities() { QMutexLocker lock(&_mutex); _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); clearEntitiesInternal(); - for (auto entity : _allEntities) { - entity->setSimulated(false); - entity->die(); - } _allEntities.clear(); - _entitiesToDelete.clear(); + _deadEntities.clear(); } -void EntitySimulation::moveSimpleKinematics(const quint64& now) { +void EntitySimulation::moveSimpleKinematics(uint64_t now) { PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 1c633aa9dc..b19e1c33d3 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -12,6 +12,8 @@ #ifndef hifi_EntitySimulation_h #define hifi_EntitySimulation_h +#include + #include #include #include @@ -43,9 +45,8 @@ const int DIRTY_SIMULATION_FLAGS = Simulation::DIRTY_SIMULATOR_ID; class EntitySimulation : public QObject, public std::enable_shared_from_this { -Q_OBJECT public: - EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { } + EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits::max()) { } virtual ~EntitySimulation() { setEntityTree(NULL); } inline EntitySimulationPointer getThisPointer() const { @@ -57,8 +58,6 @@ public: void updateEntities(); -// friend class EntityTree; - virtual void addDynamic(EntityDynamicPointer dynamic); virtual void removeDynamic(const QUuid dynamicID); virtual void removeDynamics(QList dynamicIDsToRemove); @@ -74,29 +73,26 @@ public: void clearEntities(); - void moveSimpleKinematics(const quint64& now); + void moveSimpleKinematics(uint64_t now); EntityTreePointer getEntityTree() { return _entityTree; } - virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete); + virtual void takeDeadEntities(SetOfEntities& entitiesToDelete); /// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others. virtual void prepareEntityForDelete(EntityItemPointer entity); -signals: - void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); - protected: // These pure virtual methods are protected because they are not to be called will-nilly. The base class // calls them in the right places. - virtual void updateEntitiesInternal(const quint64& now) = 0; - virtual void addEntityInternal(EntityItemPointer entity); - virtual void removeEntityInternal(EntityItemPointer entity) = 0; - virtual void changeEntityInternal(EntityItemPointer entity); + virtual void updateEntitiesInternal(uint64_t now) = 0; + virtual void addEntityInternal(EntityItemPointer entity) = 0; + virtual void removeEntityInternal(EntityItemPointer entity); + virtual void changeEntityInternal(EntityItemPointer entity) = 0; virtual void clearEntitiesInternal() = 0; - void expireMortalEntities(const quint64& now); - void callUpdateOnEntitiesThatNeedIt(const quint64& now); + void expireMortalEntities(uint64_t now); + void callUpdateOnEntitiesThatNeedIt(uint64_t now); virtual void sortEntitiesThatMoved(); QMutex _mutex{ QMutex::Recursive }; @@ -108,7 +104,7 @@ protected: QMutex _dynamicsMutex { QMutex::Recursive }; protected: - SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) + SetOfEntities _deadEntities; private: void moveSimpleKinematics(); @@ -120,11 +116,10 @@ private: // An entity may be in more than one list. SetOfEntities _allEntities; // tracks all entities added the simulation SetOfEntities _mortalEntities; // entities that have an expiry - quint64 _nextExpiry; + uint64_t _nextExpiry; SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() - }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index c3b4716ccf..0d363d236f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); - // this would be a good place to clean up our entities... if (_simulation) { _simulation->clearEntities(); } @@ -260,7 +259,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { return; } } - + // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); @@ -371,12 +370,18 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti simulationBlocked = false; } } + if (!simulationBlocked) { + entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); + } } else { // the entire update is suspect --> ignore it return false; } } else if (simulationBlocked) { simulationBlocked = senderID != entity->getSimulatorID(); + if (!simulationBlocked) { + entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); + } } if (simulationBlocked) { // squash ownership and physics-related changes. @@ -425,8 +430,8 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti if (!childEntity) { continue; } - EntityTreeElementPointer containingElement = childEntity->getElement(); - if (!containingElement) { + EntityTreeElementPointer childContainingElement = childEntity->getElement(); + if (!childContainingElement) { continue; } @@ -440,7 +445,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti addToNeedsParentFixupList(childEntity); } - UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube); + UpdateEntityOperator theChildOperator(getThisPointer(), childContainingElement, childEntity, queryCube); recurseTreeWithOperator(&theChildOperator); foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { if (childChild && childChild->getNestableType() == NestableType::Entity) { @@ -453,12 +458,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { - if (_simulation) { + if (entity->isSimulated()) { + assert((bool)_simulation); if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->changeEntity(entity); } } else { - // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly + // normally the _simulation clears ALL dirtyFlags, but when not possible we do it explicitly entity->clearDirtyFlags(); } } @@ -469,7 +475,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti if (entityScriptBefore != entityScriptAfter || reload) { emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed } - } + } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). if (!entity->getElement()) { @@ -493,7 +499,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti if (!properties.getClientOnly() && getIsClient() && !nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() && - !nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified()) { + !nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain) { return nullptr; } @@ -551,8 +557,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) { assert(simulation->getEntityTree().get() == this); } if (_simulation && _simulation != simulation) { - // It's important to clearEntities() on the simulation since taht will update each - // EntityItem::_simulationState correctly so as to not confuse the next _simulation. _simulation->clearEntities(); } _simulation = simulation; @@ -650,7 +654,7 @@ void EntityTree::deleteEntities(QSet entityIDs, bool force, bool i emit deletingEntityPointer(existingEntity.get()); } - if (theOperator.getEntities().size() > 0) { + if (!theOperator.getEntities().empty()) { recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; @@ -692,7 +696,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) trackDeletedEntity(theEntity->getEntityItemID()); } - if (_simulation) { + if (theEntity->isSimulated()) { _simulation->prepareEntityForDelete(theEntity); } } @@ -1509,7 +1513,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) { - // if a node can't change locks, don't allow them to create an already-locked entity + // if a node can't change locks, don't allow it to create an already-locked entity -- automatically + // clear the locked property and allow the unlocked entity to be created. properties.setLocked(false); bumpTimestamp(properties); } @@ -1687,7 +1692,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod } void EntityTree::entityChanged(EntityItemPointer entity) { - if (_simulation) { + if (entity->isSimulated()) { _simulation->changeEntity(entity); } } @@ -1801,13 +1806,13 @@ void EntityTree::update(bool simulate) { _simulation->updateEntities(); { PROFILE_RANGE(simulation_physics, "Deletes"); - VectorOfEntities pendingDeletes; - _simulation->takeEntitiesToDelete(pendingDeletes); - if (pendingDeletes.size() > 0) { + SetOfEntities deadEntities; + _simulation->takeDeadEntities(deadEntities); + if (!deadEntities.empty()) { // translate into list of ID's QSet idsToDelete; - for (auto entity : pendingDeletes) { + for (auto entity : deadEntities) { idsToDelete.insert(entity->getEntityItemID()); } @@ -2181,23 +2186,25 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen localTree->recurseTreeWithOperator(&moveOperator); } - // send add-entity packets to the server - i = map.begin(); - while (i != map.end()) { - EntityItemID newID = i.value(); - EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); - if (entity) { - // queue the packet to send to the server - entity->updateQueryAACube(); - EntityItemProperties properties = entity->getProperties(); - properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity - packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties); - i++; - } else { - i = map.erase(i); + if (!_serverlessDomain) { + // send add-entity packets to the server + i = map.begin(); + while (i != map.end()) { + EntityItemID newID = i.value(); + EntityItemPointer entity = localTree->findEntityByEntityItemID(newID); + if (entity) { + // queue the packet to send to the server + entity->updateQueryAACube(); + EntityItemProperties properties = entity->getProperties(); + properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity + packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties); + i++; + } else { + i = map.erase(i); + } } + packetSender->releaseQueuedMessages(); } - packetSender->releaseQueuedMessages(); return map.values().toVector(); } @@ -2375,6 +2382,30 @@ bool EntityTree::readFromMap(QVariantMap& map) { } } + // Convert old materials so that they use materialData instead of userData + if (contentVersion < (int)EntityVersion::MaterialData && properties.getType() == EntityTypes::EntityType::Material) { + if (properties.getMaterialURL().startsWith("userData")) { + QString materialURL = properties.getMaterialURL(); + properties.setMaterialURL(materialURL.replace("userData", "materialData")); + + QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); + QJsonObject materialData; + QJsonValue materialVersion = userData["materialVersion"]; + if (!materialVersion.isNull()) { + materialData.insert("materialVersion", materialVersion); + userData.remove("materialVersion"); + } + QJsonValue materials = userData["materials"]; + if (!materials.isNull()) { + materialData.insert("materials", materials); + userData.remove("materials"); + } + + properties.setMaterialData(QJsonDocument(materialData).toJson()); + properties.setUserData(QJsonDocument(userData).toJson()); + } + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index cb3d6d57e4..5f69714432 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -283,6 +283,9 @@ public: void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } + void setIsServerlessMode(bool value) { _serverlessDomain = value; } + bool isServerlessMode() const { return _serverlessDomain; } + static void setAddMaterialToEntityOperator(std::function addMaterialToEntityOperator) { _addMaterialToEntityOperator = addMaterialToEntityOperator; } static void setRemoveMaterialFromEntityOperator(std::function removeMaterialFromEntityOperator) { _removeMaterialFromEntityOperator = removeMaterialFromEntityOperator; } static bool addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName); @@ -325,7 +328,7 @@ protected: void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode); bool isScriptInWhitelist(const QString& scriptURL); - + QReadWriteLock _newlyCreatedHooksLock; QVector _newlyCreatedHooks; @@ -412,6 +415,8 @@ private: static std::function _removeMaterialFromAvatarOperator; static std::function _addMaterialToOverlayOperator; static std::function _removeMaterialFromOverlayOperator; + + bool _serverlessDomain { false }; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 9e32bc3346..1ae55bc333 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -594,17 +594,15 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { - keepSearching = true; // assume that we will continue searching after this. - EntityItemID result; float distanceToElementCube = std::numeric_limits::max(); - float distanceToElementDetails = distance; BoxFace localFace; glm::vec3 localSurfaceNormal; - QVariantMap localExtraInfo; - // if the ray doesn't intersect with our cube, we can stop searching! - if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) { + // if the ray doesn't intersect with our cube OR the distance to element is less than current best distance + // we can stop searching! + bool hit = _cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal); + if (!hit || (!_cube.contains(origin) && distanceToElementCube > distance)) { keepSearching = false; // no point in continuing to search return result; // we did not intersect } @@ -616,52 +614,46 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con // if the distance to the element cube is not less than the current best distance, then it's not possible // for any details inside the cube to be closer so we don't need to consider them. - if (_cube.contains(origin) || distanceToElementCube < distance) { - - EntityItemID entityID = findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails, - face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, - localExtraInfo, precisionPicking, distanceToElementCube); - if (!entityID.isNull()) { - if (distanceToElementDetails < distance) { - distance = distanceToElementDetails; - face = localFace; - surfaceNormal = localSurfaceNormal; - extraInfo = localExtraInfo; - result = entityID; - } - } + QVariantMap localExtraInfo; + float distanceToElementDetails = distance; + EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails, + face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localExtraInfo, precisionPicking); + if (!entityID.isNull() && distanceToElementDetails < distance) { + distance = distanceToElementDetails; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + result = entityID; } return result; } -EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, +EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, - bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube) { + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... int entityNumber = 0; EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { - if ( (visibleOnly && !entity->isVisible()) || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) - || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) - || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { - return; - } - + // use simple line-sphere for broadphase check + // (this is faster and more likely to cull results than the filter check below so we do it first) bool success; AABox entityBox = entity->getAABox(success); if (!success) { return; } + if (!entityBox.rayHitsBoundingSphere(origin, direction)) { + return; + } - float localDistance; - BoxFace localFace; - glm::vec3 localSurfaceNormal; - QVariantMap localExtraInfo; - - // if the ray doesn't intersect with our cube, we can stop searching! - if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace, localSurfaceNormal)) { + // check RayPick filter settings + if ((visibleOnly && !entity->isVisible()) + || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) + || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) + || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { return; } @@ -682,14 +674,17 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame // and testing intersection there. + float localDistance; + BoxFace localFace; + glm::vec3 localSurfaceNormal; if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace, localSurfaceNormal)) { if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) { // now ask the entity if we actually intersect if (entity->supportsDetailedRayIntersection()) { - if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance, - localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { - + QVariantMap localExtraInfo; + if (entity->findDetailedRayIntersection(origin, direction, element, localDistance, + localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { if (localDistance < distance) { distance = localDistance; face = localFace; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 2313bde0c4..a56af5d03f 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -152,10 +152,10 @@ public: const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube); + QVariantMap& extraInfo, bool precisionPicking); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; @@ -243,14 +243,10 @@ public: return std::static_pointer_cast(shared_from_this()); } - void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); } - uint64_t getLastChangedContent() const { return _lastChangedContent; } - protected: virtual void init(unsigned char * octalCode) override; EntityTreePointer _myTree; EntityItems _entityItems; - uint64_t _lastChangedContent { 0 }; }; #endif // hifi_EntityTreeElement_h diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 62011c6e26..0e2fca8180 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -35,6 +35,56 @@ typedef EntityItemPointer (*EntityTypeFactory)(const EntityItemID& entityID, con class EntityTypes { public: + /**jsdoc + *

An entity may be one of the following types:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescriptionProperties
"Box"A rectangular prism. This is a synonym of "Shape" for the case + * where the entity's shape property value is "Cube".
+ * If an entity is created with its type + * set to "Box" it will always be created with a shape property value of + * "Cube". If an entity of type Shape or Sphere has its shape set + * to "Cube" then its type will be reported as "Box". + *
{@link Entities.EntityProperties-Box|EntityProperties-Box}
"Light"A local lighting effect.{@link Entities.EntityProperties-Light|EntityProperties-Light}
"Line"A sequence of one or more simple straight lines.{@link Entities.EntityProperties-Line|EntityProperties-Line}
"Material"Modifies the existing materials on Model entities, Shape entities (albedo + * only), {@link Overlays.OverlayType|model overlays}, and avatars.{@link Entities.EntityProperties-Material|EntityProperties-Material}
"Model"A mesh model from an FBX or OBJ file.{@link Entities.EntityProperties-Model|EntityProperties-Model}
"ParticleEffect"A particle system that can be used to simulate things such as fire, + * smoke, snow, magic spells, etc.{@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect}
"PolyLine"A sequence of one or more textured straight lines.{@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine}
"PolyVox"A set of textured voxels.{@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox}
"Shape"A basic entity such as a cube. + * See also, the "Box" and "Sphere" entity types.{@link Entities.EntityProperties-Shape|EntityProperties-Shape}
"Sphere"A sphere. This is a synonym of "Shape" for the case + * where the entity's shape property value is "Sphere".
+ * If an entity is created with its type + * set to "Sphere" it will always be created with a shape property value of + * "Sphere". If an entity of type Box or Shape has its shape set + * to "Sphere" then its type will be reported as "Sphere". + *
{@link Entities.EntityProperties-Sphere|EntityProperties-Sphere}
"Text"A pane of text oriented in space.{@link Entities.EntityProperties-Text|EntityProperties-Text}
"Web"A browsable Web page.{@link Entities.EntityProperties-Web|EntityProperties-Web}
"Zone"A volume of lighting effects and avatar permissions.{@link Entities.EntityProperties-Zone|EntityProperties-Zone}
+ * @typedef {string} Entities.EntityType + */ typedef enum EntityType_t { Unknown, Model, diff --git a/libraries/entities/src/HazePropertyGroup.h b/libraries/entities/src/HazePropertyGroup.h index 939391caf9..e992aefbf3 100644 --- a/libraries/entities/src/HazePropertyGroup.h +++ b/libraries/entities/src/HazePropertyGroup.h @@ -40,6 +40,35 @@ static const float INITIAL_HAZE_BACKGROUND_BLEND{ 0.0f }; static const float INITIAL_KEY_LIGHT_RANGE{ 1000.0f }; static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; +// FIXME: Document hazeAttenuationKeyLight, hazeKeyLightRange, and hazeKeyLightAltitude once they're working and are provided +// in the Create app's UI. +/**jsdoc + * Haze is defined by the following properties. + * @typedef {object} Entities.Haze + * + * @property {number} hazeRange=1000 - The horizontal distance at which visibility is reduced to 95%; i.e., 95% of each pixel's + * color is haze. + * @property {Color} hazeColor=128,154,179 - The color of the haze when looking away from the key light. + * @property {boolean} hazeEnableGlare=false - If true then the haze is colored with glare from the key light; + * hazeGlareColor and hazeGlareAngle are used. + * @property {Color} hazeGlareColor=255,299,179 - The color of the haze when looking towards the key light. + * @property {number} hazeGlareAngle=20 - The angle in degrees across the circle around the key light that the glare color and + * haze color are blended 50/50. + * + * @property {boolean} hazeAltitudeEffect=false - If true then haze decreases with altitude as defined by the + * entity's local coordinate system; hazeBaseRef and
hazeCeiling
are used. + * @property {number} hazeBaseRef=0 - The y-axis value in the entity's local coordinate system at which the haze density starts + * reducing with altitude. + * @property {number} hazeCeiling=200 - The y-axis value in the entity's local coordinate system at which the haze density has + * reduced to 5%. + * + * @property {number} hazeBackgroundBlend=0 - The proportion of the skybox image to show through the haze: 0.0 + * displays no skybox image; 1.0 displays no haze. + * + * @property {boolean} hazeAttenuateKeyLight=false - Currently not supported. + * @property {number} hazeKeyLightRange=1000 - Currently not supported. + * @property {number} hazeKeyLightAltitude=200 - Currently not supported. + */ class HazePropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index c476b4c23c..e0b6ae6e96 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -21,6 +21,7 @@ const xColor KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR = { 255, 255, 255 }; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY = 1.0f; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY = 0.5f; const glm::vec3 KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION = { 0.0f, -1.0f, 0.0f }; +const bool KeyLightPropertyGroup::DEFAULT_KEYLIGHT_CAST_SHADOWS { false }; void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { @@ -28,23 +29,27 @@ void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desired COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, KeyLight, keyLight, Direction, direction); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_CAST_SHADOW, KeyLight, keyLight, CastShadows, castShadows); } void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, color, xColor, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, intensity, float, setIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, glmVec3, setDirection); - + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, castShadows, bool, setCastShadows); + // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightColor, xColor, setColor, getColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightIntensity, float, setIntensity, getIntensity); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightDirection, glmVec3, setDirection, getDirection); + COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightCastShadows, bool, setCastShadows, getCastShadows); } void KeyLightPropertyGroup::merge(const KeyLightPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(intensity); COPY_PROPERTY_IF_CHANGED(direction); + COPY_PROPERTY_IF_CHANGED(castShadows); } void KeyLightPropertyGroup::debugDump() const { @@ -52,6 +57,7 @@ void KeyLightPropertyGroup::debugDump() const { qCDebug(entities) << " color:" << getColor(); // << "," << getColor()[1] << "," << getColor()[2]; qCDebug(entities) << " intensity:" << getIntensity(); qCDebug(entities) << " direction:" << getDirection(); + qCDebug(entities) << " castShadows:" << getCastShadows(); } void KeyLightPropertyGroup::listChangedProperties(QList& out) { @@ -64,6 +70,9 @@ void KeyLightPropertyGroup::listChangedProperties(QList& out) { if (directionChanged()) { out << "keyLight-direction"; } + if (castShadowsChanged()) { + out << "keyLight-castShadows"; + } } bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, @@ -78,12 +87,13 @@ bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, getCastShadows()); + return true; } bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, - int& processedBytes) { + int& processedBytes) { int bytesRead = 0; bool overwriteLocalData = true; @@ -92,11 +102,13 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, bool, setCastShadows); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_COLOR, Color); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_INTENSITY, Intensity); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_DIRECTION, Direction); - + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_CAST_SHADOW, CastShadows); + processedBytes += bytesRead; Q_UNUSED(somethingChanged); @@ -108,6 +120,7 @@ void KeyLightPropertyGroup::markAllChanged() { _colorChanged = true; _intensityChanged = true; _directionChanged = true; + _castShadowsChanged = true; } EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { @@ -116,7 +129,8 @@ EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_INTENSITY, intensity); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, direction); - + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_CAST_SHADOW, castShadows); + return changedProperties; } @@ -124,6 +138,7 @@ void KeyLightPropertyGroup::getProperties(EntityItemProperties& properties) cons COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Color, getColor); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Intensity, getIntensity); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Direction, getDirection); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, CastShadows, getCastShadows); } bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -132,6 +147,7 @@ bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Color, color, setColor); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Intensity, intensity, setIntensity); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Direction, direction, setDirection); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, CastShadows, castShadows, setCastShadows); return somethingChanged; } @@ -142,6 +158,7 @@ EntityPropertyFlags KeyLightPropertyGroup::getEntityProperties(EncodeBitstreamPa requestedProperties += PROP_KEYLIGHT_COLOR; requestedProperties += PROP_KEYLIGHT_INTENSITY; requestedProperties += PROP_KEYLIGHT_DIRECTION; + requestedProperties += PROP_KEYLIGHT_CAST_SHADOW; return requestedProperties; } @@ -159,6 +176,7 @@ void KeyLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, Enc APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, getCastShadows()); } int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -172,6 +190,7 @@ int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, bool, setCastShadows); return bytesRead; } diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index f33ebb282d..2be33787de 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -27,6 +27,16 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +/**jsdoc + * A key light is defined by the following properties. + * @typedef {object} Entities.KeyLight + * @property {Color} color=255,255,255 - The color of the light. + * @property {number} intensity=1 - The intensity of the light. + * @property {Vec3} direction=0,-1,0 - The direction the light is shining. + * @property {boolean} castShadows=false - If true then shadows are cast. Shadows are cast by avatars, plus + * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities that have their + * {@link Entities.EntityProperties|canCastShadows} property set to true. + */ class KeyLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers @@ -78,10 +88,12 @@ public: static const float DEFAULT_KEYLIGHT_INTENSITY; static const float DEFAULT_KEYLIGHT_AMBIENT_INTENSITY; static const glm::vec3 DEFAULT_KEYLIGHT_DIRECTION; + static const bool DEFAULT_KEYLIGHT_CAST_SHADOWS; DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, Color, color, xColor, DEFAULT_KEYLIGHT_COLOR); DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, Intensity, intensity, float, DEFAULT_KEYLIGHT_INTENSITY); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, Direction, direction, glm::vec3, DEFAULT_KEYLIGHT_DIRECTION); + DEFINE_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, CastShadows, castShadows, bool, DEFAULT_KEYLIGHT_CAST_SHADOWS); }; #endif // hifi_KeyLightPropertyGroup_h diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 85edefa413..3f7fc5f799 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -298,7 +298,7 @@ void LightEntityItem::resetLightPropertiesChanged() { } bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 3be1d48aa5..4d0bde3718 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -86,7 +86,7 @@ public: virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 9f16807084..375453e0e9 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -61,7 +61,7 @@ class LineEntityItem : public EntityItem { // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 44ef34a3a4..6c040296a3 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -27,6 +27,10 @@ MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : Entit _type = EntityTypes::Material; } +MaterialEntityItem::~MaterialEntityItem() { + removeMaterial(); +} + EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); @@ -36,6 +40,7 @@ EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desir COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingPos, getMaterialMappingPos); COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingScale, getMaterialMappingScale); COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingRot, getMaterialMappingRot); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialData, getMaterialData); return properties; } @@ -49,6 +54,7 @@ bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingPos, setMaterialMappingPos); SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingScale, setMaterialMappingScale); SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingRot, setMaterialMappingRot); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialData, setMaterialData); if (somethingChanged) { bool wantDebug = false; @@ -78,6 +84,7 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, glm::vec2, setMaterialMappingPos); READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, glm::vec2, setMaterialMappingScale); READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); + READ_ENTITY_PROPERTY(PROP_MATERIAL_DATA, QString, setMaterialData); return bytesRead; } @@ -93,6 +100,7 @@ EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParam requestedProperties += PROP_MATERIAL_MAPPING_POS; requestedProperties += PROP_MATERIAL_MAPPING_SCALE; requestedProperties += PROP_MATERIAL_MAPPING_ROT; + requestedProperties += PROP_MATERIAL_DATA; return requestedProperties; } @@ -112,6 +120,7 @@ void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, Encode APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, getMaterialMappingPos()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, getMaterialMappingScale()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, getMaterialMappingRot()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, getMaterialData()); } void MaterialEntityItem::debugDump() const { @@ -145,9 +154,9 @@ std::shared_ptr MaterialEntityItem::getMaterial() const { } } -void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool userDataChanged) { - bool usingUserData = materialURLString.startsWith("userData"); - if (_materialURL != materialURLString || (usingUserData && userDataChanged)) { +void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool materialDataChanged) { + bool usingMaterialData = materialDataChanged || materialURLString.startsWith("materialData"); + if (_materialURL != materialURLString || (usingMaterialData && materialDataChanged)) { removeMaterial(); _materialURL = materialURLString; @@ -156,8 +165,8 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u _currentMaterialName = split.last().toStdString(); } - if (usingUserData) { - _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8())); + if (usingMaterialData) { + _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getMaterialData().toUtf8()), materialURLString); // Since our material changed, the current name might not be valid anymore, so we need to update setCurrentMaterialName(_currentMaterialName); @@ -191,11 +200,11 @@ void MaterialEntityItem::setCurrentMaterialName(const std::string& currentMateri } } -void MaterialEntityItem::setUserData(const QString& userData) { - if (_userData != userData) { - EntityItem::setUserData(userData); - if (_materialURL.startsWith("userData")) { - // Trigger material update when user data changes +void MaterialEntityItem::setMaterialData(const QString& materialData) { + if (_materialData != materialData) { + _materialData = materialData; + if (_materialURL.startsWith("materialData")) { + // Trigger material update when material data changes setMaterialURL(_materialURL, true); } } @@ -249,26 +258,13 @@ void MaterialEntityItem::setParentID(const QUuid& parentID) { } } -void MaterialEntityItem::setClientOnly(bool clientOnly) { - if (getClientOnly() != clientOnly) { - removeMaterial(); - EntityItem::setClientOnly(clientOnly); - applyMaterial(); - } -} - -void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) { - if (getOwningAvatarID() != owningAvatarID) { - removeMaterial(); - EntityItem::setOwningAvatarID(owningAvatarID); - applyMaterial(); - } -} - void MaterialEntityItem::removeMaterial() { graphics::MaterialPointer material = getMaterial(); - QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); - if (!material || parentID.isNull()) { + if (!material) { + return; + } + QUuid parentID = getParentID(); + if (parentID.isNull()) { return; } @@ -291,7 +287,7 @@ void MaterialEntityItem::removeMaterial() { void MaterialEntityItem::applyMaterial() { _retryApply = false; graphics::MaterialPointer material = getMaterial(); - QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); + QUuid parentID = getParentID(); if (!material || parentID.isNull()) { return; } @@ -325,15 +321,10 @@ void MaterialEntityItem::postParentFixup() { applyMaterial(); } -void MaterialEntityItem::preDelete() { - EntityItem::preDelete(); - removeMaterial(); -} - void MaterialEntityItem::update(const quint64& now) { if (_retryApply) { applyMaterial(); } EntityItem::update(now); -} \ No newline at end of file +} diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index f77077a782..969eb577ff 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -21,6 +21,7 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); MaterialEntityItem(const EntityItemID& entityItemID); + ~MaterialEntityItem(); ALLOW_INSTANTIATION // This class can be instantiated @@ -53,7 +54,7 @@ public: virtual void setUnscaledDimensions(const glm::vec3& value) override; QString getMaterialURL() const { return _materialURL; } - void setMaterialURL(const QString& materialURLString, bool userDataChanged = false); + void setMaterialURL(const QString& materialURLString, bool materialDataChanged = false); void setCurrentMaterialName(const std::string& currentMaterialName); @@ -73,21 +74,20 @@ public: float getMaterialMappingRot() const { return _materialMappingRot; } void setMaterialMappingRot(const float& materialMappingRot); + QString getMaterialData() const { return _materialData; } + void setMaterialData(const QString& materialData); + std::shared_ptr getMaterial() const; - void setUserData(const QString& userData) override; void setParentID(const QUuid& parentID) override; - void setClientOnly(bool clientOnly) override; - void setOwningAvatarID(const QUuid& owningAvatarID) override; void applyMaterial(); void removeMaterial(); void postParentFixup() override; - void preDelete() override; private: - // URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material. + // URL for this material. Currently, only JSON format is supported. Set to "materialData" to use the material data to live edit a material. // The following fields are supported in the JSON: // materialVersion: a uint for the version of this network material (currently, only 1 is supported) // materials, which is either an object or an array of objects, each with the following properties: @@ -117,6 +117,7 @@ private: glm::vec2 _materialMappingScale { 1, 1 }; // How much to rotate this material within its parent's UV-space (degrees) float _materialMappingRot { 0 }; + QString _materialData; NetworkMaterialResourcePointer _networkMaterial; NetworkMaterialResource::ParsedMaterials _parsedMaterials; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 8af2b26216..2dc8befe97 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -94,7 +94,7 @@ class PolyLineEntityItem : public EntityItem { // never have a ray intersection pick a PolyLineEntityItem. virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 47d2a4b4e1..0ddfe3e8cc 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -45,7 +45,7 @@ class PolyVoxEntityItem : public EntityItem { // never have a ray intersection pick a PolyVoxEntityItem. virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } @@ -59,6 +59,25 @@ class PolyVoxEntityItem : public EntityItem { virtual int getOnCount() const { return 0; } + /**jsdoc + *

A PolyVoxSurfaceStyle may be one of the following:

+ * + * + * + * + * + * + * + * + * + * + *
ValueTypeDescription
0Marching cubes.Chamfered edges. Open volume. + * Joins neighboring PolyVox entities reasonably well.
1Cubic.Square edges. Open volume. + * Joins neighboring PolyVox entities cleanly.
2Edged cubic.Square edges. Enclosed volume. + * Joins neighboring PolyVox entities cleanly.
3Edged marching cubes.Chamfered edges. Enclosed volume. + * Doesn't join neighboring PolyVox entities.
+ * @typedef {number} Entities.PolyVoxSurfaceStyle + */ enum PolyVoxSurfaceStyle { SURFACE_MARCHING_CUBES, SURFACE_CUBIC, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 2425208a87..99f18d2dac 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -20,6 +20,33 @@ #include "ShapeEntityItem.h" namespace entity { + + /**jsdoc + *

A Shape, Box, or Sphere {@link Entities.EntityType|EntityType} may display as + * one of the following geometrical shapes:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDimensionsNotes
"Circle"2DA circle oriented in 3D.
"Cube"3D
"Cone"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
+ * @typedef {string} Entities.Shape + */ static const std::array shapeStrings { { "Triangle", "Quad", @@ -32,7 +59,7 @@ namespace entity { "Octahedron", "Dodecahedron", "Icosahedron", - "Torus", + "Torus", // Not implemented yet. "Cone", "Cylinder" } }; @@ -90,8 +117,10 @@ ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - properties.setColor(getXColor()); properties.setShape(entity::stringFromShape(getShape())); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + return properties; } @@ -228,7 +257,7 @@ bool ShapeEntityItem::supportsDetailedRayIntersection() const { } bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { // determine the ray in the frame of the entity transformed from a unit sphere diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 7ad1b3c1c2..adc33b764b 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -92,7 +92,7 @@ public: bool supportsDetailedRayIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; @@ -105,7 +105,7 @@ public: protected: - float _alpha { 1 }; + float _alpha { 1.0f }; rgbColor _color; entity::Shape _shape { entity::Shape::Sphere }; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 3203c2968c..66f9b4111b 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -//#include - #include "SimpleEntitySimulation.h" #include @@ -18,7 +16,7 @@ #include "EntityItem.h" #include "EntitiesLogging.h" -const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; +const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { QMutexLocker lock(&_mutex); @@ -27,16 +25,13 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { EntityItemPointer entity = *itemItr; if (entity->getSimulatorID() == ownerID) { // the simulator has abandonded this object --> remove from owned list - qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID(); itemItr = _entitiesWithSimulationOwner.erase(itemItr); if (entity->getDynamic() && entity->hasLocalVelocity()) { // it is still moving dynamically --> add to orphaned list _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); } // remove ownership and dirty all the tree elements that contain the it @@ -50,77 +45,60 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { } } -void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { - if (now > _nextOwnerlessExpiry) { - // search for ownerless objects that have expired - QMutexLocker lock(&_mutex); - _nextOwnerlessExpiry = -1; - SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); - while (itemItr != _entitiesThatNeedSimulationOwner.end()) { - EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < now) { - // no simulators have volunteered ownership --> remove from list - itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); - - if (entity->getSimulatorID().isNull() && entity->getDynamic() && entity->hasLocalVelocity()) { - // zero the derivatives - entity->setVelocity(Vectors::ZERO); - entity->setAngularVelocity(Vectors::ZERO); - entity->setAcceleration(Vectors::ZERO); - - // dirty all the tree elements that contain it - entity->markAsChangedOnServer(); - DirtyOctreeElementOperator op(entity->getElement()); - getEntityTree()->recurseTreeWithOperator(&op); - } - } else { - ++itemItr; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } - } - } - } +void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) { + expireStaleOwnerships(now); + stopOwnerlessEntities(now); } void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { - EntitySimulation::addEntityInternal(entity); + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + QMutexLocker lock(&_mutex); + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } if (!entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); } else if (entity->getDynamic() && entity->hasLocalVelocity()) { QMutexLocker lock(&_mutex); _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); } } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.remove(entity); _entitiesThatNeedSimulationOwner.remove(entity); } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { - EntitySimulation::changeEntityInternal(entity); + { + QMutexLocker lock(&_mutex); + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + int numKinematicEntities = _simpleKinematicEntities.size(); + _simpleKinematicEntities.insert(entity); + if (numKinematicEntities != _simpleKinematicEntities.size()) { + entity->setLastSimulated(usecTimestampNow()); + } + } else { + _simpleKinematicEntities.remove(entity); + } + } if (entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.remove(entity); if (entity->getDynamic() && entity->hasLocalVelocity()) { _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); } } else { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); _entitiesThatNeedSimulationOwner.remove(entity); } entity->clearDirtyFlags(); @@ -141,3 +119,58 @@ void SimpleEntitySimulation::sortEntitiesThatMoved() { } EntitySimulation::sortEntitiesThatMoved(); } + +void SimpleEntitySimulation::expireStaleOwnerships(uint64_t now) { + if (now > _nextStaleOwnershipExpiry) { + _nextStaleOwnershipExpiry = (uint64_t)(-1); + SetOfEntities::iterator itemItr = _entitiesWithSimulationOwner.begin(); + while (itemItr != _entitiesWithSimulationOwner.end()) { + EntityItemPointer entity = *itemItr; + uint64_t expiry = entity->getSimulationOwnershipExpiry(); + if (now > expiry) { + itemItr = _entitiesWithSimulationOwner.erase(itemItr); + + // remove ownership and dirty all the tree elements that contain the it + entity->clearSimulationOwnership(); + entity->markAsChangedOnServer(); + DirtyOctreeElementOperator op(entity->getElement()); + getEntityTree()->recurseTreeWithOperator(&op); + } else { + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, expiry); + ++itemItr; + } + } + } +} + +void SimpleEntitySimulation::stopOwnerlessEntities(uint64_t now) { + if (now > _nextOwnerlessExpiry) { + // search for ownerless objects that have expired + QMutexLocker lock(&_mutex); + _nextOwnerlessExpiry = (uint64_t)(-1); + SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); + while (itemItr != _entitiesThatNeedSimulationOwner.end()) { + EntityItemPointer entity = *itemItr; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + if (expiry < now) { + // no simulators have volunteered ownership --> remove from list + itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); + + if (entity->getSimulatorID().isNull() && entity->getDynamic() && entity->hasLocalVelocity()) { + // zero the derivatives + entity->setVelocity(Vectors::ZERO); + entity->setAngularVelocity(Vectors::ZERO); + entity->setAcceleration(Vectors::ZERO); + + // dirty all the tree elements that contain it + entity->markAsChangedOnServer(); + DirtyOctreeElementOperator op(entity->getElement()); + getEntityTree()->recurseTreeWithOperator(&op); + } + } else { + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); + ++itemItr; + } + } + } +} diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 9c7c9a374e..8ac9b69e93 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -23,22 +23,26 @@ using SimpleEntitySimulationPointer = std::shared_ptr; class SimpleEntitySimulation : public EntitySimulation { public: SimpleEntitySimulation() : EntitySimulation() { } - virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); } + ~SimpleEntitySimulation() { clearEntitiesInternal(); } void clearOwnership(const QUuid& ownerID); protected: - virtual void updateEntitiesInternal(const quint64& now) override; - virtual void addEntityInternal(EntityItemPointer entity) override; - virtual void removeEntityInternal(EntityItemPointer entity) override; - virtual void changeEntityInternal(EntityItemPointer entity) override; - virtual void clearEntitiesInternal() override; + void updateEntitiesInternal(uint64_t now) override; + void addEntityInternal(EntityItemPointer entity) override; + void removeEntityInternal(EntityItemPointer entity) override; + void changeEntityInternal(EntityItemPointer entity) override; + void clearEntitiesInternal() override; - virtual void sortEntitiesThatMoved() override; + void sortEntitiesThatMoved() override; + + void expireStaleOwnerships(uint64_t now); + void stopOwnerlessEntities(uint64_t now); SetOfEntities _entitiesWithSimulationOwner; SetOfEntities _entitiesThatNeedSimulationOwner; - quint64 _nextOwnerlessExpiry { 0 }; + uint64_t _nextOwnerlessExpiry { 0 }; + uint64_t _nextStaleOwnershipExpiry { (uint64_t)(-1) }; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index 4398582673..b312c6deb9 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -16,9 +16,9 @@ #include -const quint8 PENDING_STATE_NOTHING = 0; -const quint8 PENDING_STATE_TAKE = 1; -const quint8 PENDING_STATE_RELEASE = 2; +const uint8_t PENDING_STATE_NOTHING = 0; +const uint8_t PENDING_STATE_TAKE = 1; +const uint8_t PENDING_STATE_RELEASE = 2; // static const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; @@ -33,7 +33,7 @@ SimulationOwner::SimulationOwner() : { } -SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : +SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) : _id(id), _expiry(0), _pendingBidTimestamp(0), @@ -67,11 +67,11 @@ void SimulationOwner::clear() { _pendingState = PENDING_STATE_NOTHING; } -void SimulationOwner::setPriority(quint8 priority) { +void SimulationOwner::setPriority(uint8_t priority) { _priority = priority; } -void SimulationOwner::promotePriority(quint8 priority) { +void SimulationOwner::promotePriority(uint8_t priority) { if (priority > _priority) { _priority = priority; } @@ -89,7 +89,7 @@ bool SimulationOwner::setID(const QUuid& id) { return false; } -bool SimulationOwner::set(const QUuid& id, quint8 priority) { +bool SimulationOwner::set(const QUuid& id, uint8_t priority) { uint8_t oldPriority = _priority; setPriority(priority); return setID(id) || oldPriority != _priority; @@ -101,22 +101,22 @@ bool SimulationOwner::set(const SimulationOwner& owner) { return setID(owner._id) || oldPriority != _priority; } -void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { +void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) { _pendingBidPriority = priority; _pendingBidTimestamp = timestamp; _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; } void SimulationOwner::updateExpiry() { - const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5; + const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC; _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; } -bool SimulationOwner::pendingRelease(const quint64& timestamp) { +bool SimulationOwner::pendingRelease(uint64_t timestamp) { return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; } -bool SimulationOwner::pendingTake(const quint64& timestamp) { +bool SimulationOwner::pendingTake(uint64_t timestamp) { return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; } @@ -142,7 +142,7 @@ void SimulationOwner::test() { { // test set constructor QUuid id = QUuid::createUuid(); - quint8 priority = 128; + uint8_t priority = 128; SimulationOwner simOwner(id, priority); if (simOwner.isNull()) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; @@ -164,7 +164,7 @@ void SimulationOwner::test() { { // test set() QUuid id = QUuid::createUuid(); - quint8 priority = 1; + uint8_t priority = 1; SimulationOwner simOwner; simOwner.set(id, priority); if (simOwner.isNull()) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 94ed1d9a08..cc2069dcc8 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -18,20 +18,88 @@ #include #include -// Simulation observers will bid to simulate unowned active objects at the lowest possible priority -// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it -// to RECRUIT priority so that other volunteers don't accidentally take over. -const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; -const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; +// HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions +// of the same world. When portions overlap only one participant is allowed to be the authority for any +// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and +// their duty is to send transform/velocity updates for the entity to the central entity-server. +// The entity-server relays updates to other participants who apply them as "state synchronization" +// to their own simulation. +// +// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update: +// { +// "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, +// transform/velocity properties +// } +// +// The entity-server is the authority as to who owns what and may reject a bid. +// The rules for handling a bid are as follows: +// +// (1) A bid may be refused for special ownership restrictions, but otherwise... +// +// (2) A bid at higher priority is accepted +// +// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec) +// of the last ownership transition, otherwise it is accepted +// +// (4) The current owner is the only participant allowed to clear ownership (entity-server can override). +// +// (5) The current owner is the only participant allowed to adjust priority (entity-server can override). +// +// (6) If an owner does not update the transform or velocities of an owned entity within some period +// (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle +// the case when an owner drops off the network. +// +// The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules +// for bidding are as follows: +// +// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the +// simulation owner and priority as if they really did change but doesn't actually modify them +// locally. Thus, if the bid packet is lost the participant will re-send after some period. +// The participant only updates its knowledge of who owns what when it recieves an update from the +// entity-server. An exception is when the participant creates a moving entity: it assumes it starts +// off owning any moving entities it creates. +// +// (8) When an unowned entity becomes active in the physics simulation the participant will +// start a timer and if the entity is still unowned after some period (0.5 seconds) +// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER +// priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to +// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership +// when multiple participants (with variable ping-times to the server) bid simultaneously for a +// recently activated entity. +// +// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127) +// +// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE. +// +// (11) When a participant grabs an entity it will bid at priority = GRAB (=128). +// +// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will +// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger). +// +// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will +// send an update to: clear their ownerhsip, set priority to zero, and set the object's +// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership +// has been cleared until it hears from the entity-server. This, if the packet is lost the +// owner will re-send after some period. +// +// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it +// immediately at priority = VOLUNTEER. +// +// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority +// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it. +// +const uint8_t YIELD_SIMULATION_PRIORITY = 1; +const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1; +const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. -const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80; -const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; -const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; +const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128; +const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower -const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; +const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; class SimulationOwner { @@ -39,25 +107,25 @@ public: static const int NUM_BYTES_ENCODED; SimulationOwner(); - SimulationOwner(const QUuid& id, quint8 priority); + SimulationOwner(const QUuid& id, uint8_t priority); const QUuid& getID() const { return _id; } - const quint64& getExpiry() const { return _expiry; } - quint8 getPriority() const { return _priority; } + const uint64_t& getExpiry() const { return _expiry; } + uint8_t getPriority() const { return _priority; } QByteArray toByteArray() const; bool fromByteArray(const QByteArray& data); void clear(); - void setPriority(quint8 priority); - void promotePriority(quint8 priority); + void setPriority(uint8_t priority); + void promotePriority(uint8_t priority); // return true if id is changed bool setID(const QUuid& id); - bool set(const QUuid& id, quint8 priority); + bool set(const QUuid& id, uint8_t priority); bool set(const SimulationOwner& owner); - void setPendingPriority(quint8 priority, const quint64& timestamp); + void setPendingPriority(uint8_t priority, uint64_t timestamp); bool isNull() const { return _id.isNull(); } bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } @@ -67,11 +135,11 @@ public: bool hasExpired() const { return usecTimestampNow() > _expiry; } uint8_t getPendingPriority() const { return _pendingBidPriority; } - bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE - bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE + bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE + bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE void clearCurrentOwner(); - bool operator>=(quint8 priority) const { return _priority >= priority; } + bool operator>=(uint8_t priority) const { return _priority >= priority; } bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); } bool operator!=(const SimulationOwner& other); @@ -84,11 +152,11 @@ public: private: QUuid _id; // owner - quint64 _expiry; // time when ownership can transition at equal priority - quint64 _pendingBidTimestamp; // time when pending bid was set - quint8 _priority; // priority of current owner - quint8 _pendingBidPriority; // priority at which we'd like to own it - quint8 _pendingState; // NOTHING, TAKE, or RELEASE + uint64_t _expiry; // time when ownership can transition at equal priority + uint64_t _pendingBidTimestamp; // time when pending bid was set + uint8_t _priority; // priority of current owner + uint8_t _pendingBidPriority; // priority at which we'd like to own it + uint8_t _pendingState; // NOTHING, TAKE, or RELEASE }; diff --git a/libraries/entities/src/SkyboxPropertyGroup.h b/libraries/entities/src/SkyboxPropertyGroup.h index 8298a7b74e..d7b422bf11 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.h +++ b/libraries/entities/src/SkyboxPropertyGroup.h @@ -27,6 +27,13 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +/**jsdoc + * A skybox is defined by the following properties. + * @typedef {object} Entities.Skybox + * @property {Color} color=0,0,0 - Sets the color of the sky if url is "", otherwise modifies the + * color of the cube map image. + * @property {string} url="" - A cube map image that is used to render the sky. + */ class SkyboxPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 7b1089e6ed..7030a95562 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -129,7 +129,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits } bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 3ab743ecfd..06b377ee14 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -48,7 +48,7 @@ public: virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index fa7e5ca38f..32bd2f06ba 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -288,7 +288,7 @@ OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(const OctreeEle int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntityBox); if (childIndex == indexOfChildContainingNewEntity) { - return element->addChildAtIndex(childIndex);; + return element->addChildAtIndex(childIndex); } } } diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 91e7bca063..548bca3225 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -106,7 +106,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst } bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 7d8f37cd83..dab7cd5e22 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -47,7 +47,7 @@ public: virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 6083e5b8de..4ae020f966 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -296,7 +296,7 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) { } bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 95b6248fde..2c6b01fc69 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -105,7 +105,7 @@ public: virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; diff --git a/libraries/fbx/src/FBXWriter.cpp b/libraries/fbx/src/FBXWriter.cpp index 5029b489bc..511f253193 100644 --- a/libraries/fbx/src/FBXWriter.cpp +++ b/libraries/fbx/src/FBXWriter.cpp @@ -161,23 +161,19 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { case QMetaType::QString: { auto bytes = prop.toString().toUtf8(); - out << 'S'; - out << bytes.length(); - out << bytes; + out.device()->write("S", 1); out << (int32_t)bytes.size(); out.writeRawData(bytes, bytes.size()); break; } - case QMetaType::QByteArray: - { - auto bytes = prop.toByteArray(); - out.device()->write("S", 1); - out << (int32_t)bytes.size(); - out.writeRawData(bytes, bytes.size()); - break; - } - + { + auto bytes = prop.toByteArray(); + out.device()->write("S", 1); + out << (int32_t)bytes.size(); + out.writeRawData(bytes, bytes.size()); + break; + } default: { if (prop.canConvert>()) { diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 63fb93ae46..caac08f777 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -643,13 +643,13 @@ done: } -FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) { +FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); QBuffer buffer { &model }; buffer.open(QIODevice::ReadOnly); - FBXGeometry* geometryPtr = new FBXGeometry(); - FBXGeometry& geometry = *geometryPtr; + auto geometryPtr { std::make_shared() }; + FBXGeometry& geometry { *geometryPtr }; OBJTokenizer tokenizer { &buffer }; float scaleGuess = 1.0f; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index df356fada8..13ddc6e21c 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -85,7 +85,7 @@ public: QString currentMaterialName; QHash materials; - FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); + FBXGeometry::Pointer readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); private: QUrl _url; diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 1bde9e289e..4a2c5fd7f7 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -69,7 +69,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { qFatal("Offscreen surface is invalid"); } #endif - + if (gl::Context::enableDebugLogger()) { _context->makeCurrent(_offscreenSurface); QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); diff --git a/libraries/gpu-gl-common/CMakeLists.txt b/libraries/gpu-gl-common/CMakeLists.txt new file mode 100644 index 0000000000..2b6f8d4d40 --- /dev/null +++ b/libraries/gpu-gl-common/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME gpu-gl-common) +setup_hifi_library(Concurrent) +link_hifi_libraries(shared gl gpu) +GroupSources("src") +target_opengl() + diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp similarity index 93% rename from libraries/gpu-gles/src/gpu/gl/GLBackend.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index fc1bc39929..9bb02d678d 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -1,9 +1,9 @@ // // GLBackend.cpp -// libraries/gpu-gl-android/src/gpu/gl +// libraries/gpu/src/gpu // -// Created by Cristian Duarte & Gabriel Calero on 9/21/2016. -// Copyright 2016 High Fidelity, Inc. +// Created by Sam Gateau on 10/27/2014. +// Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -16,16 +16,11 @@ #include #include -#include "../gles/GLESBackend.h" - #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" #endif -#include #include -#include -#include #include "GLTexture.h" #include "GLShader.h" @@ -33,39 +28,6 @@ using namespace gpu; using namespace gpu::gl; -static GLBackend* INSTANCE{ nullptr }; - -BackendPointer GLBackend::createBackend() { - // FIXME provide a mechanism to override the backend for testing - // Where the gpuContext is initialized and where the TRUE Backend is created and assigned - //auto version = QOpenGLContextWrapper::currentContextVersion(); - std::shared_ptr result; - - qDebug() << "Using OpenGL ES backend"; - result = std::make_shared(); - - result->initInput(); - result->initTransform(); - result->initTextureManagementStage(); - - INSTANCE = result.get(); - void* voidInstance = &(*result); - qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance)); - return result; -} - -GLBackend& getBackend() { - if (!INSTANCE) { - INSTANCE = static_cast(qApp->property(hifi::properties::gl::BACKEND).value()); - } - return *INSTANCE; -} - -bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { - return GLShader::makeProgram(getBackend(), shader, slotBindings, handler); -} - - GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = { (&::gpu::gl::GLBackend::do_draw), @@ -93,12 +55,17 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_setUniformBuffer), (&::gpu::gl::GLBackend::do_setResourceBuffer), (&::gpu::gl::GLBackend::do_setResourceTexture), + (&::gpu::gl::GLBackend::do_setResourceTextureTable), + (&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture), (&::gpu::gl::GLBackend::do_setFramebuffer), + (&::gpu::gl::GLBackend::do_setFramebufferSwapChain), (&::gpu::gl::GLBackend::do_clearFramebuffer), (&::gpu::gl::GLBackend::do_blit), (&::gpu::gl::GLBackend::do_generateTextureMips), + (&::gpu::gl::GLBackend::do_advance), + (&::gpu::gl::GLBackend::do_beginQuery), (&::gpu::gl::GLBackend::do_endQuery), (&::gpu::gl::GLBackend::do_getQuery), @@ -147,6 +114,9 @@ void GLBackend::init() { qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; +#if !defined(USE_GLES) + qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF")); +#endif #if THREADED_TEXTURE_BUFFERING // This has to happen on the main thread in order to give the thread // pool a reasonable parent object @@ -224,7 +194,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { } { // Sync the transform buffers - PROFILE_RANGE(render_gpu_gl_detail, "transferGPUTransform"); + PROFILE_RANGE(render_gpu_gl_detail, "syncGPUTransform"); transferTransformState(batch); } @@ -292,7 +262,7 @@ void GLBackend::render(const Batch& batch) { #ifdef GPU_STEREO_DRAWCALL_INSTANCED if (_stereo.isStereo()) { - glEnable(GL_CLIP_DISTANCE0_EXT); + glEnable(GL_CLIP_DISTANCE0); } #endif { @@ -301,7 +271,7 @@ void GLBackend::render(const Batch& batch) { } #ifdef GPU_STEREO_DRAWCALL_INSTANCED if (_stereo.isStereo()) { - glDisable(GL_CLIP_DISTANCE0_EXT); + glDisable(GL_CLIP_DISTANCE0); } #endif @@ -749,9 +719,13 @@ void GLBackend::recycle() const { Texture::KtxStorage::releaseOpenKtxFiles(); } -void GLBackend::setCameraCorrection(const Mat4& correction) { +void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) { + auto invCorrection = glm::inverse(correction); + auto invPrevView = glm::inverse(prevRenderView); + _transform._correction.prevView = (reset ? Mat4() : prevRenderView); + _transform._correction.prevViewInverse = (reset ? Mat4() : invPrevView); _transform._correction.correction = correction; - _transform._correction.correctionInverse = glm::inverse(correction); + _transform._correction.correctionInverse = invCorrection; _pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction); _pipeline._cameraCorrectionBuffer._buffer->flush(); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h similarity index 92% rename from libraries/gpu-gl/src/gpu/gl/GLBackend.h rename to libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 18916ac18c..6355137a19 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -32,9 +32,12 @@ // Different versions for the stereo drawcall // Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only -//#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE +#if defined(USE_GLES) +#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE +#else //#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER #define GPU_STEREO_TECHNIQUE_INSTANCED +#endif // Let these be configured by the one define picked above @@ -68,7 +71,7 @@ public: virtual ~GLBackend(); - void setCameraCorrection(const Mat4& correction); + void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false); void render(const Batch& batch) final override; // This call synchronize the Full Backend cache with the current GLState @@ -98,6 +101,12 @@ public: static const int MAX_NUM_RESOURCE_TEXTURES = 16; size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } + // Texture Tables offers 2 dedicated slot (taken from the ubo slots) + static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2; + static const int RESOURCE_TABLE_TEXTURE_SLOT_OFFSET = TRANSFORM_CAMERA_SLOT + 1; + size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; } + + // Draw Stage virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; @@ -126,15 +135,20 @@ public: // Resource Stage virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final; + virtual void do_setResourceTextureTable(const Batch& batch, size_t paramOffset); + virtual void do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) final; // Pipeline Stage virtual void do_setPipeline(const Batch& batch, size_t paramOffset) final; // Output stage virtual void do_setFramebuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_setFramebufferSwapChain(const Batch& batch, size_t paramOffset) final; virtual void do_clearFramebuffer(const Batch& batch, size_t paramOffset) final; virtual void do_blit(const Batch& batch, size_t paramOffset) = 0; + virtual void do_advance(const Batch& batch, size_t paramOffset) final; + // Query section virtual void do_beginQuery(const Batch& batch, size_t paramOffset) final; virtual void do_endQuery(const Batch& batch, size_t paramOffset) final; @@ -222,6 +236,10 @@ protected: void recycle() const override; + // FIXME instead of a single flag, create a features struct similar to + // https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html + virtual bool supportsBindless() const { return false; } + static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass { false }; int32_t _uboAlignment { 0 }; @@ -245,6 +263,8 @@ protected: void setupStereoSide(int side); #endif + virtual void setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture); + virtual void setFramebuffer(const FramebufferPointer& framebuffer); virtual void initInput() final; virtual void killInput() final; virtual void syncInputStateCache() final; @@ -303,9 +323,12 @@ protected: // Allows for correction of the camera pose to account for changes // between the time when a was recorded and the time(s) when it is // executed + // Prev is the previous correction used at previous frame struct CameraCorrection { - Mat4 correction; - Mat4 correctionInverse; + mat4 correction; + mat4 correctionInverse; + mat4 prevView; + mat4 prevViewInverse; }; struct TransformStageState { @@ -376,6 +399,10 @@ protected: virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0; virtual void releaseResourceBuffer(uint32_t slot) = 0; + // Helper function that provides common code used by do_setResourceTexture and + // do_setResourceTextureTable (in non-bindless mode) + void bindResourceTexture(uint32_t slot, const TexturePointer& texture); + // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp similarity index 100% rename from libraries/gpu-gles/src/gpu/gl/GLBackendInput.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp similarity index 86% rename from libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index 358b90ed8b..2285b0e486 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -17,6 +17,11 @@ using namespace gpu; using namespace gpu::gl; +#if defined(USE_GLES) +#define GL_FRAMEBUFFER_SRGB GL_FRAMEBUFFER_SRGB_EXT +#define glClearDepth glClearDepthf +#endif + void GLBackend::syncOutputStateCache() { GLint currentFBO; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); @@ -37,6 +42,19 @@ void GLBackend::resetOutputStage() { void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + setFramebuffer(framebuffer); +} + +void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffset) { + auto swapChain = batch._swapChains.get(batch._params[paramOffset]._uint); + if (swapChain) { + auto index = batch._params[paramOffset + 1]._uint; + FramebufferPointer framebuffer = static_cast(swapChain.get())->get(index); + setFramebuffer(framebuffer); + } +} + +void GLBackend::setFramebuffer(const FramebufferPointer& framebuffer) { if (_output._framebuffer != framebuffer) { auto newFBO = getFramebufferID(framebuffer); if (_output._drawFBO != newFBO) { @@ -47,6 +65,13 @@ void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { } } +void GLBackend::do_advance(const Batch& batch, size_t paramOffset) { + auto ringbuffer = batch._swapChains.get(batch._params[paramOffset]._uint); + if (ringbuffer) { + ringbuffer->advance(); + } +} + void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) { qWarning("Clear without scissor in stereo mode"); @@ -68,7 +93,7 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { if (masks & Framebuffer::BUFFER_STENCIL) { glClearStencil(stencil); glmask |= GL_STENCIL_BUFFER_BIT; - + cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront(); if (cacheStencilMask != 0xFF) { restoreStencilMask = true; @@ -162,7 +187,11 @@ void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, co return; } +#if defined(USE_GLES) + GLenum format = GL_RGBA; +#else GLenum format = GL_BGRA; +#endif if (destImage.format() != QImage::Format_ARGB32) { qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; return; diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp similarity index 82% rename from libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index b80be8492f..58fcc51605 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" +#include + #include "GLShared.h" #include "GLPipeline.h" #include "GLShader.h" @@ -84,6 +86,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBufferIdentity._buffer); } glBindBufferRange(GL_UNIFORM_BUFFER, _pipeline._cameraCorrectionLocation, cameraCorrectionBuffer->_id, 0, sizeof(CameraCorrection)); + } (void) CHECK_GL_ERROR(); _pipeline._invalidProgram = false; @@ -208,6 +211,7 @@ void GLBackend::resetResourceStage() { } } + void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 1]._uint; if (slot >= (GLuint)MAX_NUM_RESOURCE_BUFFERS) { @@ -245,12 +249,40 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { return; } - TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + bindResourceTexture(slot, resourceTexture); +} +void GLBackend::bindResourceTexture(uint32_t slot, const TexturePointer& resourceTexture) { if (!resourceTexture) { releaseResourceTexture(slot); return; } + setResourceTexture(slot, resourceTexture); +} + +void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) { + GLuint slot = batch._params[paramOffset + 1]._uint; + if (slot >= (GLuint)MAX_NUM_RESOURCE_TEXTURES) { + qCDebug(gpugllogging) << "GLBackend::do_setResourceFramebufferSwapChainTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); + return; + } + + SwapChainPointer swapChain = batch._swapChains.get(batch._params[paramOffset + 0]._uint); + + if (!swapChain) { + releaseResourceTexture(slot); + return; + } + auto index = batch._params[paramOffset + 2]._uint; + auto renderBufferSlot = batch._params[paramOffset + 3]._uint; + FramebufferPointer resourceFramebuffer = static_cast(swapChain.get())->get(index); + TexturePointer resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); + + setResourceTexture(slot, resourceTexture); +} + +void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture) { // check cache before thinking if (_resource._textures[slot] == resourceTexture) { return; @@ -267,11 +299,11 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(target, to); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _resource._textures[slot] = resourceTexture; - _stats._RSAmountTextureMemoryBounded += (int) object->size(); + _stats._RSAmountTextureMemoryBounded += (int)object->size(); } else { releaseResourceTexture(slot); @@ -279,6 +311,19 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { } } +void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) { + const auto& textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint); + if (!textureTablePointer) { + return; + } + + const auto& textureTable = *textureTablePointer; + const auto& textures = textureTable.getTextures(); + for (GLuint slot = 0; slot < textures.size(); ++slot) { + bindResourceTexture(slot, textures[slot]); + } +} + int GLBackend::ResourceStageState::findEmptyTextureSlot() const { // start from the end of the slots, try to find an empty one that can be used for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) { @@ -288,4 +333,3 @@ int GLBackend::ResourceStageState::findEmptyTextureSlot() const { } return -1; } - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp similarity index 97% rename from libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp index bdea67a99a..b64804fe7c 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp @@ -25,6 +25,7 @@ static bool timeElapsed = false; #endif void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { +#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -43,9 +44,11 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth; (void)CHECK_GL_ERROR(); } +#endif } void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { +#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -66,9 +69,11 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { (void)CHECK_GL_ERROR(); } +#endif } void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { +#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -90,6 +95,7 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { (void)CHECK_GL_ERROR(); } } +#endif } void GLBackend::resetQueryStage() { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp similarity index 62% rename from libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp index 93c9b0d2ff..0df228ddc4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp @@ -14,15 +14,32 @@ using namespace gpu::gl; // GLSL version std::string GLBackend::getBackendShaderHeader() const { - return std::string("#version 410 core"); + +#if defined(USE_GLES) + static const std::string header( +R"SHADER(#version 310 es +#extension GL_EXT_texture_buffer : enable +precision lowp float; // check precision 2 +precision lowp samplerBuffer; +precision lowp sampler2DShadow; +)SHADER"); +#else + static const std::string header( +R"SHADER(#version 410 core +)SHADER"); +#endif + + return header; } + // Shader domain static const size_t NUM_SHADER_DOMAINS = 3; +static_assert(Shader::Type::NUM_DOMAINS == NUM_SHADER_DOMAINS, "GL shader domains must equal defined GPU shader domains"); // GL Shader type enums // Must match the order of type specified in gpu::Shader::Type -static const std::array SHADER_DOMAINS { { +static const std::array SHADER_DOMAINS{ { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, @@ -30,26 +47,42 @@ static const std::array SHADER_DOMAINS { { // Domain specific defines // Must match the order of type specified in gpu::Shader::Type -static const std::array DOMAIN_DEFINES { { +static const std::array DOMAIN_DEFINES{ { "#define GPU_VERTEX_SHADER", "#define GPU_PIXEL_SHADER", "#define GPU_GEOMETRY_SHADER", } }; // Stereo specific defines -static const std::string stereoVersion { +static const std::string stereoVersion{ #ifdef GPU_STEREO_DRAWCALL_INSTANCED - "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN" +R"SHADER( +#define GPU_TRANSFORM_IS_STEREO +#define GPU_TRANSFORM_STEREO_CAMERA +#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED +#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN +)SHADER" #endif #ifdef GPU_STEREO_DRAWCALL_DOUBLED #ifdef GPU_STEREO_CAMERA_BUFFER - "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED" +R"SHADER( +#define GPU_TRANSFORM_IS_STEREO +#define GPU_TRANSFORM_STEREO_CAMERA +#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED +)SHADER" #else - "#define GPU_TRANSFORM_IS_STEREO" +R"SHADER( +#define GPU_TRANSFORM_IS_STEREO +)SHADER" #endif #endif }; +// TextureTable specific defines +static const std::string textureTableVersion { + "#extension GL_ARB_bindless_texture : require\n#define GPU_TEXTURE_TABLE_BINDLESS\n" +}; + // Versions specific of the shader static const std::array VERSION_DEFINES { { "", @@ -67,7 +100,10 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co for (int version = 0; version < GLShader::NumVersions; version++) { auto& shaderObject = shaderObjects[version]; - std::string shaderDefines = getBackendShaderHeader() + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version]; + std::string shaderDefines = getBackendShaderHeader() + "\n" + + (supportsBindless() ? textureTableVersion : "\n") + + DOMAIN_DEFINES[shader.getType()] + "\n" + + VERSION_DEFINES[version]; if (handler) { bool retest = true; std::string currentSrc = shaderSource; @@ -89,7 +125,7 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co compilationLogs[version].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, compilationLogs[version].message); } - if (!compilationLogs[version].compiled) { + if (!compilationLogs[version].compiled) { qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[version].message.c_str(); shader.setCompilationLogs(compilationLogs); return nullptr; @@ -154,149 +190,173 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: return object; } + GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) { switch (gltype) { - case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - /* - case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - */ - case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_FLOAT: + return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC2: + return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC3: + return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC4: + return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); -#if defined(Q_OS_WIN) - case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_INT: + return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC2: + return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC3: + return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC4: + return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); + + case GL_UNSIGNED_INT: + return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC2: + return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC3: + return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC4: + return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); + + case GL_BOOL: + return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC2: + return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC3: + return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC4: + return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); + + case GL_FLOAT_MAT2: + return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT3: + return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT4: + return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + + case GL_SAMPLER_2D: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); + case GL_SAMPLER_3D: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); + case GL_SAMPLER_CUBE: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); + case GL_SAMPLER_2D_MULTISAMPLE: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_SAMPLER_2D_ARRAY: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + case GL_SAMPLER_2D_SHADOW: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); + case GL_SAMPLER_CUBE_SHADOW: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); + case GL_SAMPLER_2D_ARRAY_SHADOW: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); + case GL_SAMPLER_BUFFER: + return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER); + case GL_INT_SAMPLER_2D: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_2D_MULTISAMPLE: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_3D: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); + case GL_INT_SAMPLER_CUBE: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); + case GL_INT_SAMPLER_2D_ARRAY: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_3D: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); + case GL_UNSIGNED_INT_SAMPLER_CUBE: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + +#if !defined(USE_GLES) + case GL_SAMPLER_1D: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); + case GL_SAMPLER_1D_ARRAY: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_INT_SAMPLER_1D: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); + case GL_INT_SAMPLER_1D_ARRAY: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_1D: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); #endif - case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); - - - case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - - /* {GL_FLOAT_MAT2x3 mat2x3}, - {GL_FLOAT_MAT2x4 mat2x4}, - {GL_FLOAT_MAT3x2 mat3x2}, - {GL_FLOAT_MAT3x4 mat3x4}, - {GL_FLOAT_MAT4x2 mat4x2}, - {GL_FLOAT_MAT4x3 mat4x3}, - {GL_DOUBLE_MAT2 dmat2}, - {GL_DOUBLE_MAT3 dmat3}, - {GL_DOUBLE_MAT4 dmat4}, - {GL_DOUBLE_MAT2x3 dmat2x3}, - {GL_DOUBLE_MAT2x4 dmat2x4}, - {GL_DOUBLE_MAT3x2 dmat3x2}, - {GL_DOUBLE_MAT3x4 dmat3x4}, - {GL_DOUBLE_MAT4x2 dmat4x2}, - {GL_DOUBLE_MAT4x3 dmat4x3}, - */ - - case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); - case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); - - case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); - case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); - -#if defined(Q_OS_WIN) - case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif - - case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); -#if defined(Q_OS_WIN) - case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); - - case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); -#endif - - // {GL_SAMPLER_1D_SHADOW sampler1DShadow}, - // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, - - case GL_SAMPLER_BUFFER: return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER); - - // {GL_SAMPLER_2D_RECT sampler2DRect}, - // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, - -#if defined(Q_OS_WIN) - case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); - case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); - case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - - // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, - // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, - - case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); - case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); - case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif - // {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, - // {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, - /* - {GL_IMAGE_1D image1D}, - {GL_IMAGE_2D image2D}, - {GL_IMAGE_3D image3D}, - {GL_IMAGE_2D_RECT image2DRect}, - {GL_IMAGE_CUBE imageCube}, - {GL_IMAGE_BUFFER imageBuffer}, - {GL_IMAGE_1D_ARRAY image1DArray}, - {GL_IMAGE_2D_ARRAY image2DArray}, - {GL_IMAGE_2D_MULTISAMPLE image2DMS}, - {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, - {GL_INT_IMAGE_1D iimage1D}, - {GL_INT_IMAGE_2D iimage2D}, - {GL_INT_IMAGE_3D iimage3D}, - {GL_INT_IMAGE_2D_RECT iimage2DRect}, - {GL_INT_IMAGE_CUBE iimageCube}, - {GL_INT_IMAGE_BUFFER iimageBuffer}, - {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, - {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, - {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, - {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, - {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, - {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, - {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, - {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, - {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot - - {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, - {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, - {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} - */ default: return ElementResource(Element(), Resource::BUFFER); } + // Non-covered types + //{GL_FLOAT_MAT2x3 mat2x3}, + //{GL_FLOAT_MAT2x4 mat2x4}, + //{GL_FLOAT_MAT3x2 mat3x2}, + //{GL_FLOAT_MAT3x4 mat3x4}, + //{GL_FLOAT_MAT4x2 mat4x2}, + //{GL_FLOAT_MAT4x3 mat4x3}, + //{GL_DOUBLE_MAT2 dmat2}, + //{GL_DOUBLE_MAT3 dmat3}, + //{GL_DOUBLE_MAT4 dmat4}, + //{GL_DOUBLE_MAT2x3 dmat2x3}, + //{GL_DOUBLE_MAT2x4 dmat2x4}, + //{GL_DOUBLE_MAT3x2 dmat3x2}, + //{GL_DOUBLE_MAT3x4 dmat3x4}, + //{GL_DOUBLE_MAT4x2 dmat4x2}, + //{GL_DOUBLE_MAT4x3 dmat4x3}, + //{GL_SAMPLER_1D_SHADOW sampler1DShadow}, + //{GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, + //{GL_SAMPLER_2D_RECT sampler2DRect}, + //{GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, + //{GL_INT_SAMPLER_BUFFER isamplerBuffer}, + //{GL_INT_SAMPLER_2D_RECT isampler2DRect}, + //{GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, + //{GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, + //{GL_IMAGE_1D image1D}, + //{GL_IMAGE_2D image2D}, + //{GL_IMAGE_3D image3D}, + //{GL_IMAGE_2D_RECT image2DRect}, + //{GL_IMAGE_CUBE imageCube}, + //{GL_IMAGE_BUFFER imageBuffer}, + //{GL_IMAGE_1D_ARRAY image1DArray}, + //{GL_IMAGE_2D_ARRAY image2DArray}, + //{GL_IMAGE_2D_MULTISAMPLE image2DMS}, + //{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, + //{GL_INT_IMAGE_1D iimage1D}, + //{GL_INT_IMAGE_2D iimage2D}, + //{GL_INT_IMAGE_3D iimage3D}, + //{GL_INT_IMAGE_2D_RECT iimage2DRect}, + //{GL_INT_IMAGE_CUBE iimageCube}, + //{GL_INT_IMAGE_BUFFER iimageBuffer}, + //{GL_INT_IMAGE_1D_ARRAY iimage1DArray}, + //{GL_INT_IMAGE_2D_ARRAY iimage2DArray}, + //{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, + //{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, + //{GL_UNSIGNED_INT_IMAGE_1D uimage1D}, + //{GL_UNSIGNED_INT_IMAGE_2D uimage2D}, + //{GL_UNSIGNED_INT_IMAGE_3D uimage3D}, + //{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, + //{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube}, + //{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, + //{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, + //{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, + //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, + //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, + //{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} + + }; int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, @@ -550,4 +610,3 @@ void GLBackend::makeProgramBindings(ShaderObject& shaderObject) { qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; } } - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp similarity index 97% rename from libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp index 24f90395d7..8363af9b00 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp @@ -29,18 +29,19 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) { } } + // Default line width accross the board + glLineWidth(1.0f); +#if !defined(USE_GLES) // force a few states regardless glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Point size is always on - // FIXME CORE //glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glEnable(GL_PROGRAM_POINT_SIZE_EXT); glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); - // Default line width accross the board - glLineWidth(1.0f); glEnable(GL_LINE_SMOOTH); +#endif } @@ -48,17 +49,19 @@ void GLBackend::syncPipelineStateCache() { State::Data state; // force a few states regardless - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); - - // Point size is always on - // FIXME CORE - //glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); - glEnable(GL_PROGRAM_POINT_SIZE_EXT); - glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); // Default line width accross the board glLineWidth(1.0f); + +#if !defined(USE_GLES) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + // Point size is always on + //glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glEnable(GL_PROGRAM_POINT_SIZE_EXT); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glEnable(GL_LINE_SMOOTH); +#endif getCurrentGLState(state); State::Signature signature = State::evalSignature(state); @@ -70,11 +73,13 @@ void GLBackend::syncPipelineStateCache() { void GLBackend::do_setStateFillMode(int32 mode) { if (_pipeline._stateCache.fillMode != mode) { +#if !defined(USE_GLES) static GLenum GL_FILL_MODES[] = { GL_POINT, GL_LINE, GL_FILL }; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); (void)CHECK_GL_ERROR(); _pipeline._stateCache.fillMode = State::FillMode(mode); +#endif } } @@ -106,14 +111,15 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { void GLBackend::do_setStateDepthClampEnable(bool enable) { if (_pipeline._stateCache.depthClampEnable != enable) { +#if !defined(USE_GLES) if (enable) { glEnable(GL_DEPTH_CLAMP); } else { glDisable(GL_DEPTH_CLAMP); } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.depthClampEnable = enable; +#endif } } @@ -132,6 +138,7 @@ void GLBackend::do_setStateScissorEnable(bool enable) { void GLBackend::do_setStateMultisampleEnable(bool enable) { if (_pipeline._stateCache.multisampleEnable != enable) { +#if !defined(USE_GLES) if (enable) { glEnable(GL_MULTISAMPLE); } else { @@ -140,11 +147,13 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { (void)CHECK_GL_ERROR(); _pipeline._stateCache.multisampleEnable = enable; +#endif } } void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { if (_pipeline._stateCache.antialisedLineEnable != enable) { +#if !defined(USE_GLES) if (enable) { glEnable(GL_LINE_SMOOTH); } else { @@ -153,6 +162,7 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { (void)CHECK_GL_ERROR(); _pipeline._stateCache.antialisedLineEnable = enable; +#endif } } @@ -160,13 +170,17 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) { if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { if ((bias.x != 0.0f) || (bias.y != 0.0f)) { glEnable(GL_POLYGON_OFFSET_FILL); +#if !defined(USE_GLES) glEnable(GL_POLYGON_OFFSET_LINE); glEnable(GL_POLYGON_OFFSET_POINT); +#endif glPolygonOffset(bias.x, bias.y); } else { glDisable(GL_POLYGON_OFFSET_FILL); +#if !defined(USE_GLES) glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_POLYGON_OFFSET_POINT); +#endif } (void)CHECK_GL_ERROR(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp similarity index 98% rename from libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index f286a5cca9..72aaa5aa66 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -105,7 +105,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo if (_viewIsCamera && (_viewCorrectionEnabled && _correction.correction != glm::mat4())) { // FIXME should I switch to using the camera correction buffer in Transform.slf and leave this out? Transform result; - _view.mult(result, _view, _correction.correction); + _view.mult(result, _view, _correction.correctionInverse); if (_skybox) { result.setTranslation(vec3()); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLBuffer.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLBuffer.h rename to libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLFramebuffer.cpp similarity index 96% rename from libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLFramebuffer.cpp index 2ac7e9d060..8d5fb6b2ef 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLFramebuffer.cpp @@ -33,14 +33,18 @@ bool GLFramebuffer::checkStatus() const { case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; break; + case GL_FRAMEBUFFER_UNSUPPORTED: + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + break; +#if !defined(USE_GLES) case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; break; - case GL_FRAMEBUFFER_UNSUPPORTED: - qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; +#endif + default: break; } return false; diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLFramebuffer.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h rename to libraries/gpu-gl-common/src/gpu/gl/GLFramebuffer.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLInputFormat.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLInputFormat.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLInputFormat.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLInputFormat.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLInputFormat.h b/libraries/gpu-gl-common/src/gpu/gl/GLInputFormat.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLInputFormat.h rename to libraries/gpu-gl-common/src/gpu/gl/GLInputFormat.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.h b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLPipeline.h rename to libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLQuery.h b/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLQuery.h rename to libraries/gpu-gl-common/src/gpu/gl/GLQuery.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLShader.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.h b/libraries/gpu-gl-common/src/gpu/gl/GLShader.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLShader.h rename to libraries/gpu-gl-common/src/gpu/gl/GLShader.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp similarity index 97% rename from libraries/gpu-gl/src/gpu/gl/GLShared.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp index dc1c8036b0..9ba85b8418 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp @@ -24,6 +24,7 @@ namespace gpu { namespace gl { gpu::Size getFreeDedicatedMemory() { Size result { 0 }; +#if !defined(USE_GLES) static bool nvidiaMemorySupported { true }; static bool atiMemorySupported { true }; if (nvidiaMemorySupported) { @@ -45,6 +46,7 @@ gpu::Size getFreeDedicatedMemory() { atiMemorySupported = false; } } +#endif return result; } @@ -144,6 +146,9 @@ State::BlendArg blendArgFromGL(GLenum blendArg) { void getCurrentGLState(State::Data& state) { { +#if defined(USE_GLES) + state.fillMode = State::FILL_FACE; +#else GLint modes[2]; glGetIntegerv(GL_POLYGON_MODE, modes); if (modes[0] == GL_FILL) { @@ -155,6 +160,7 @@ void getCurrentGLState(State::Data& state) { state.fillMode = State::FILL_POINT; } } +#endif } { if (glIsEnabled(GL_CULL_FACE)) { @@ -169,10 +175,16 @@ void getCurrentGLState(State::Data& state) { GLint winding; glGetIntegerv(GL_FRONT_FACE, &winding); state.frontFaceClockwise = (winding == GL_CW); - state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); - state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); +#if defined(USE_GLES) + state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE_EXT); + state.antialisedLineEnable = false; + state.depthClampEnable = false; +#else state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); + state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); +#endif + state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); } { if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { @@ -269,6 +281,7 @@ void getCurrentGLState(State::Data& state) { (void)CHECK_GL_ERROR(); } + void serverWait() { auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); assert(fence); diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl-common/src/gpu/gl/GLShared.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLShared.h rename to libraries/gpu-gl-common/src/gpu/gl/GLShared.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLState.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLState.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLState.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLState.h b/libraries/gpu-gl-common/src/gpu/gl/GLState.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLState.h rename to libraries/gpu-gl-common/src/gpu/gl/GLState.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp similarity index 71% rename from libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp index 1873034ac5..4d94f8d8e7 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp @@ -11,15 +11,81 @@ using namespace gpu; using namespace gpu::gl; +#if defined(USE_GLES) +// Missing GL formats +#define GL_R16 GL_R16_EXT +#define GL_R16_SNORM GL_R16_SNORM_EXT +#define GL_RG16 GL_RG16_EXT +#define GL_RG16_SNORM GL_RG16_SNORM_EXT +#define GL_RGBA2 GL_RGBA8 +#define GL_RGBA16 GL_RGBA16_EXT +#define GL_RGBA16_SNORM GL_RGBA16_SNORM_EXT +#define GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT32_OES +#define GL_SLUMINANCE8_EXT GL_SLUMINANCE8_NV +// Missing GL compressed formats +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#endif + bool GLTexelFormat::isCompressed() const { switch (internalFormat) { case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + +#if defined(USE_GLES) + case GL_COMPRESSED_RGBA_ASTC_4x4: + case GL_COMPRESSED_RGBA_ASTC_5x4: + case GL_COMPRESSED_RGBA_ASTC_5x5: + case GL_COMPRESSED_RGBA_ASTC_6x5: + case GL_COMPRESSED_RGBA_ASTC_6x6: + case GL_COMPRESSED_RGBA_ASTC_8x5: + case GL_COMPRESSED_RGBA_ASTC_8x6: + case GL_COMPRESSED_RGBA_ASTC_8x8: + case GL_COMPRESSED_RGBA_ASTC_10x5: + case GL_COMPRESSED_RGBA_ASTC_10x6: + case GL_COMPRESSED_RGBA_ASTC_10x8: + case GL_COMPRESSED_RGBA_ASTC_10x10: + case GL_COMPRESSED_RGBA_ASTC_12x10: + case GL_COMPRESSED_RGBA_ASTC_12x12: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12: +#endif + + return true; default: return false; @@ -146,7 +212,51 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::RGB: case gpu::RGBA: case gpu::XY: - result = GL_RG8; + switch (dstFormat.getType()) { + case gpu::UINT32: + result = GL_RG32UI; + break; + case gpu::INT32: + result = GL_RG32I; + break; + case gpu::FLOAT: + result = GL_RG32F; + break; + case gpu::UINT16: + result = GL_RG16UI; + break; + case gpu::INT16: + result = GL_RG16I; + break; + case gpu::NUINT16: + result = GL_RG16; + break; + case gpu::NINT16: + result = GL_RG16_SNORM; + break; + case gpu::HALF: + result = GL_RG16F; + break; + case gpu::UINT8: + result = GL_RG8UI; + break; + case gpu::INT8: + result = GL_RG8I; + break; + case gpu::NUINT8: + result = GL_RG8; + break; + case gpu::NINT8: + result = GL_RG8_SNORM; + break; + case gpu::NUINT32: + case gpu::NINT32: + case gpu::NUINT2: + case gpu::NINT2_10_10_10: + case gpu::COMPRESSED: + case gpu::NUM_TYPES: // quiet compiler + Q_UNREACHABLE(); + } break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; @@ -260,7 +370,36 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::COMPRESSED_BC7_SRGBA: result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; - + case gpu::COMPRESSED_ETC2_RGB: + result = GL_COMPRESSED_RGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB: + result = GL_COMPRESSED_SRGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + result = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + result = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGBA: + result = GL_COMPRESSED_RGBA8_ETC2_EAC; + break; + case gpu::COMPRESSED_ETC2_SRGBA: + result = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + break; + case gpu::COMPRESSED_EAC_RED: + result = GL_COMPRESSED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_RED_SIGNED: + result = GL_COMPRESSED_SIGNED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_XY: + result = GL_COMPRESSED_RG11_EAC; + break; + case gpu::COMPRESSED_EAC_XY_SIGNED: + result = GL_COMPRESSED_SIGNED_RG11_EAC; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } @@ -346,7 +485,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (srcFormat.getSemantic()) { case gpu::BGRA: case gpu::SBGRA: +#if defined(USE_GLES) + texel.format = GL_RGBA; +#else texel.format = GL_BGRA; +#endif break; case gpu::RGB: case gpu::RGBA: @@ -383,8 +526,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (srcFormat.getSemantic()) { case gpu::BGRA: case gpu::SBGRA: +#if !defined(USE_GLES) texel.format = GL_BGRA; break; +#endif case gpu::RGB: case gpu::RGBA: case gpu::SRGB: @@ -415,6 +560,36 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; + case gpu::COMPRESSED_ETC2_RGB: + texel.internalFormat = GL_COMPRESSED_RGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB: + texel.internalFormat = GL_COMPRESSED_SRGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGBA: + texel.internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; + break; + case gpu::COMPRESSED_ETC2_SRGBA: + texel.internalFormat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + break; + case gpu::COMPRESSED_EAC_RED: + texel.internalFormat = GL_COMPRESSED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_RED_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_XY: + texel.internalFormat = GL_COMPRESSED_RG11_EAC; + break; + case gpu::COMPRESSED_EAC_XY_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_RG11_EAC; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } @@ -434,6 +609,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { + case gpu::RED: case gpu::RGB: case gpu::RGBA: @@ -581,7 +757,52 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::RGB: case gpu::RGBA: case gpu::XY: - texel.internalFormat = GL_RG8; + switch (dstFormat.getType()) { + case gpu::UINT32: + texel.internalFormat = GL_RG32UI; + break; + case gpu::INT32: + texel.internalFormat = GL_RG32I; + break; + case gpu::FLOAT: + texel.internalFormat = GL_RG32F; + break; + case gpu::UINT16: + texel.internalFormat = GL_RG16UI; + break; + case gpu::INT16: + texel.internalFormat = GL_RG16I; + break; + case gpu::NUINT16: + texel.internalFormat = GL_RG16; + break; + case gpu::NINT16: + texel.internalFormat = GL_RG16_SNORM; + break; + case gpu::HALF: + texel.type = GL_FLOAT; + texel.internalFormat = GL_RG16F; + break; + case gpu::UINT8: + texel.internalFormat = GL_RG8UI; + break; + case gpu::INT8: + texel.internalFormat = GL_RG8I; + break; + case gpu::NUINT8: + texel.internalFormat = GL_RG8; + break; + case gpu::NINT8: + texel.internalFormat = GL_RG8_SNORM; + break; + case gpu::NUINT32: + case gpu::NINT32: + case gpu::NUINT2: + case gpu::NINT2_10_10_10: + case gpu::COMPRESSED: + case gpu::NUM_TYPES: // quiet compiler + Q_UNREACHABLE(); + } break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; @@ -699,8 +920,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (srcFormat.getSemantic()) { case gpu::BGRA: case gpu::SBGRA: +#if !defined(USE_GLES) texel.format = GL_BGRA; break; +#endif case gpu::RGB: case gpu::RGBA: case gpu::SRGB: @@ -731,7 +954,36 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; - + case gpu::COMPRESSED_ETC2_RGB: + texel.internalFormat = GL_COMPRESSED_RGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB: + texel.internalFormat = GL_COMPRESSED_SRGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGBA: + texel.internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; + break; + case gpu::COMPRESSED_ETC2_SRGBA: + texel.internalFormat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + break; + case gpu::COMPRESSED_EAC_RED: + texel.internalFormat = GL_COMPRESSED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_RED_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_XY: + texel.internalFormat = GL_COMPRESSED_RG11_EAC; + break; + case gpu::COMPRESSED_EAC_XY_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_RG11_EAC; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h rename to libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.h diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLTexture.cpp rename to libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl-common/src/gpu/gl/GLTexture.h similarity index 100% rename from libraries/gpu-gl/src/gpu/gl/GLTexture.h rename to libraries/gpu-gl-common/src/gpu/gl/GLTexture.h diff --git a/libraries/gpu-gl/CMakeLists.txt b/libraries/gpu-gl/CMakeLists.txt index dc744e73f2..faddab8531 100644 --- a/libraries/gpu-gl/CMakeLists.txt +++ b/libraries/gpu-gl/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu-gl) setup_hifi_library(Concurrent) -link_hifi_libraries(shared gl gpu) +link_hifi_libraries(shared gl gpu gpu-gl-common) if (UNIX) target_link_libraries(${TARGET_NAME} pthread) endif(UNIX) diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp deleted file mode 100644 index 08bd20be66..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ /dev/null @@ -1,764 +0,0 @@ -// -// GLBackend.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 10/27/2014. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" - -#include -#include -#include -#include -#include - -#include "../gl41/GL41Backend.h" -#include "../gl45/GL45Backend.h" - -#if defined(NSIGHT_FOUND) -#include "nvToolsExt.h" -#endif - -#include -#include -#include -#include - -#include "GLTexture.h" -#include "GLShader.h" - -using namespace gpu; -using namespace gpu::gl; - -static const QString DEBUG_FLAG("HIFI_DISABLE_OPENGL_45"); -static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); - -static GLBackend* INSTANCE{ nullptr }; - -BackendPointer GLBackend::createBackend() { - // FIXME provide a mechanism to override the backend for testing - // Where the gpuContext is initialized and where the TRUE Backend is created and assigned - auto version = QOpenGLContextWrapper::currentContextVersion(); - std::shared_ptr result; - if (!disableOpenGL45 && version >= 0x0405) { - qCDebug(gpugllogging) << "Using OpenGL 4.5 backend"; - result = std::make_shared(); - } else { - qCDebug(gpugllogging) << "Using OpenGL 4.1 backend"; - result = std::make_shared(); - } - result->initInput(); - result->initTransform(); - result->initTextureManagementStage(); - - INSTANCE = result.get(); - void* voidInstance = &(*result); - qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance)); - return result; -} - -GLBackend& getBackend() { - if (!INSTANCE) { - INSTANCE = static_cast(qApp->property(hifi::properties::gl::BACKEND).value()); - } - return *INSTANCE; -} - -bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { - return GLShader::makeProgram(getBackend(), shader, slotBindings, handler); -} - -GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = -{ - (&::gpu::gl::GLBackend::do_draw), - (&::gpu::gl::GLBackend::do_drawIndexed), - (&::gpu::gl::GLBackend::do_drawInstanced), - (&::gpu::gl::GLBackend::do_drawIndexedInstanced), - (&::gpu::gl::GLBackend::do_multiDrawIndirect), - (&::gpu::gl::GLBackend::do_multiDrawIndexedIndirect), - - (&::gpu::gl::GLBackend::do_setInputFormat), - (&::gpu::gl::GLBackend::do_setInputBuffer), - (&::gpu::gl::GLBackend::do_setIndexBuffer), - (&::gpu::gl::GLBackend::do_setIndirectBuffer), - - (&::gpu::gl::GLBackend::do_setModelTransform), - (&::gpu::gl::GLBackend::do_setViewTransform), - (&::gpu::gl::GLBackend::do_setProjectionTransform), - (&::gpu::gl::GLBackend::do_setViewportTransform), - (&::gpu::gl::GLBackend::do_setDepthRangeTransform), - - (&::gpu::gl::GLBackend::do_setPipeline), - (&::gpu::gl::GLBackend::do_setStateBlendFactor), - (&::gpu::gl::GLBackend::do_setStateScissorRect), - - (&::gpu::gl::GLBackend::do_setUniformBuffer), - (&::gpu::gl::GLBackend::do_setResourceBuffer), - (&::gpu::gl::GLBackend::do_setResourceTexture), - - (&::gpu::gl::GLBackend::do_setFramebuffer), - (&::gpu::gl::GLBackend::do_clearFramebuffer), - (&::gpu::gl::GLBackend::do_blit), - (&::gpu::gl::GLBackend::do_generateTextureMips), - - (&::gpu::gl::GLBackend::do_beginQuery), - (&::gpu::gl::GLBackend::do_endQuery), - (&::gpu::gl::GLBackend::do_getQuery), - - (&::gpu::gl::GLBackend::do_resetStages), - - (&::gpu::gl::GLBackend::do_disableContextViewCorrection), - (&::gpu::gl::GLBackend::do_restoreContextViewCorrection), - (&::gpu::gl::GLBackend::do_disableContextStereo), - (&::gpu::gl::GLBackend::do_restoreContextStereo), - - (&::gpu::gl::GLBackend::do_runLambda), - - (&::gpu::gl::GLBackend::do_startNamedCall), - (&::gpu::gl::GLBackend::do_stopNamedCall), - - (&::gpu::gl::GLBackend::do_glUniform1i), - (&::gpu::gl::GLBackend::do_glUniform1f), - (&::gpu::gl::GLBackend::do_glUniform2f), - (&::gpu::gl::GLBackend::do_glUniform3f), - (&::gpu::gl::GLBackend::do_glUniform4f), - (&::gpu::gl::GLBackend::do_glUniform3fv), - (&::gpu::gl::GLBackend::do_glUniform4fv), - (&::gpu::gl::GLBackend::do_glUniform4iv), - (&::gpu::gl::GLBackend::do_glUniformMatrix3fv), - (&::gpu::gl::GLBackend::do_glUniformMatrix4fv), - - (&::gpu::gl::GLBackend::do_glColor4f), - - (&::gpu::gl::GLBackend::do_pushProfileRange), - (&::gpu::gl::GLBackend::do_popProfileRange), -}; - -void GLBackend::init() { - static std::once_flag once; - std::call_once(once, [] { - QString vendor{ (const char*)glGetString(GL_VENDOR) }; - QString renderer{ (const char*)glGetString(GL_RENDERER) }; - qCDebug(gpugllogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); - qCDebug(gpugllogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); - qCDebug(gpugllogging) << "GL Vendor: " << vendor; - qCDebug(gpugllogging) << "GL Renderer: " << renderer; - GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer); - // From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers. - qCDebug(gpugllogging) << "GPU:"; - qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); - qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); - qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; - qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF")); -#if THREADED_TEXTURE_BUFFERING - // This has to happen on the main thread in order to give the thread - // pool a reasonable parent object - GLVariableAllocationSupport::TransferJob::startBufferingThread(); -#endif - }); -} - -GLBackend::GLBackend() { - _pipeline._cameraCorrectionBuffer._buffer->flush(); - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment); -} - - -GLBackend::~GLBackend() { - killInput(); - killTransform(); -} - -void GLBackend::renderPassTransfer(const Batch& batch) { - const size_t numCommands = batch.getCommands().size(); - const Batch::Commands::value_type* command = batch.getCommands().data(); - const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); - - _inRenderTransferPass = true; - { // Sync all the buffers - PROFILE_RANGE(render_gpu_gl_detail, "syncGPUBuffer"); - - for (auto& cached : batch._buffers._items) { - if (cached._data) { - syncGPUObject(*cached._data); - } - } - } - - { // Sync all the transform states - PROFILE_RANGE(render_gpu_gl_detail, "syncCPUTransform"); - _transform._cameras.clear(); - _transform._cameraOffsets.clear(); - - for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { - switch (*command) { - case Batch::COMMAND_draw: - case Batch::COMMAND_drawIndexed: - case Batch::COMMAND_drawInstanced: - case Batch::COMMAND_drawIndexedInstanced: - case Batch::COMMAND_multiDrawIndirect: - case Batch::COMMAND_multiDrawIndexedIndirect: - _transform.preUpdate(_commandIndex, _stereo); - break; - - case Batch::COMMAND_disableContextStereo: - _stereo._contextDisable = true; - break; - - case Batch::COMMAND_restoreContextStereo: - _stereo._contextDisable = false; - break; - - case Batch::COMMAND_setViewportTransform: - case Batch::COMMAND_setViewTransform: - case Batch::COMMAND_setProjectionTransform: { - CommandCall call = _commandCalls[(*command)]; - (this->*(call))(batch, *offset); - break; - } - - default: - break; - } - command++; - offset++; - } - } - - { // Sync the transform buffers - PROFILE_RANGE(render_gpu_gl_detail, "syncGPUTransform"); - transferTransformState(batch); - } - - _inRenderTransferPass = false; -} - -void GLBackend::renderPassDraw(const Batch& batch) { - _currentDraw = -1; - _transform._camerasItr = _transform._cameraOffsets.begin(); - const size_t numCommands = batch.getCommands().size(); - const Batch::Commands::value_type* command = batch.getCommands().data(); - const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); - for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { - switch (*command) { - // Ignore these commands on this pass, taken care of in the transfer pass - // Note we allow COMMAND_setViewportTransform to occur in both passes - // as it both updates the transform object (and thus the uniforms in the - // UBO) as well as executes the actual viewport call - case Batch::COMMAND_setModelTransform: - case Batch::COMMAND_setViewTransform: - case Batch::COMMAND_setProjectionTransform: - break; - - case Batch::COMMAND_draw: - case Batch::COMMAND_drawIndexed: - case Batch::COMMAND_drawInstanced: - case Batch::COMMAND_drawIndexedInstanced: - case Batch::COMMAND_multiDrawIndirect: - case Batch::COMMAND_multiDrawIndexedIndirect: { - // updates for draw calls - ++_currentDraw; - updateInput(); - updateTransform(batch); - updatePipeline(); - - CommandCall call = _commandCalls[(*command)]; - (this->*(call))(batch, *offset); - break; - } - default: { - CommandCall call = _commandCalls[(*command)]; - (this->*(call))(batch, *offset); - break; - } - } - - command++; - offset++; - } -} - -void GLBackend::render(const Batch& batch) { - _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); - // Allow the batch to override the rendering stereo settings - // for things like full framebuffer copy operations (deferred lighting passes) - bool savedStereo = _stereo._enable; - if (!batch.isStereoEnabled()) { - _stereo._enable = false; - } - - { - PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); - renderPassTransfer(batch); - } - -#ifdef GPU_STEREO_DRAWCALL_INSTANCED - if (_stereo.isStereo()) { - glEnable(GL_CLIP_DISTANCE0); - } -#endif - { - PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render"); - renderPassDraw(batch); - } -#ifdef GPU_STEREO_DRAWCALL_INSTANCED - if (_stereo.isStereo()) { - glDisable(GL_CLIP_DISTANCE0); - } -#endif - // Restore the saved stereo state for the next batch - _stereo._enable = savedStereo; -} - - -void GLBackend::syncCache() { - PROFILE_RANGE(render_gpu_gl_detail, __FUNCTION__); - - syncTransformStateCache(); - syncPipelineStateCache(); - syncInputStateCache(); - syncOutputStateCache(); -} - -#ifdef GPU_STEREO_DRAWCALL_DOUBLED -void GLBackend::setupStereoSide(int side) { - ivec4 vp = _transform._viewport; - vp.z /= 2; - glViewport(vp.x + side * vp.z, vp.y, vp.z, vp.w); - - -#ifdef GPU_STEREO_CAMERA_BUFFER -#ifdef GPU_STEREO_DRAWCALL_DOUBLED - glVertexAttribI1i(14, side); -#endif -#else - _transform.bindCurrentCamera(side); -#endif - -} -#else -#endif - -void GLBackend::do_resetStages(const Batch& batch, size_t paramOffset) { - resetStages(); -} - -void GLBackend::do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) { - _transform._viewCorrectionEnabled = false; -} - -void GLBackend::do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) { - _transform._viewCorrectionEnabled = true; -} - -void GLBackend::do_disableContextStereo(const Batch& batch, size_t paramOffset) { - -} - -void GLBackend::do_restoreContextStereo(const Batch& batch, size_t paramOffset) { - -} - -void GLBackend::do_runLambda(const Batch& batch, size_t paramOffset) { - std::function f = batch._lambdas.get(batch._params[paramOffset]._uint); - f(); -} - -void GLBackend::do_startNamedCall(const Batch& batch, size_t paramOffset) { - batch._currentNamedCall = batch._names.get(batch._params[paramOffset]._uint); - _currentDraw = -1; -} - -void GLBackend::do_stopNamedCall(const Batch& batch, size_t paramOffset) { - batch._currentNamedCall.clear(); -} - -void GLBackend::resetStages() { - resetInputStage(); - resetPipelineStage(); - resetTransformStage(); - resetUniformStage(); - resetResourceStage(); - resetOutputStage(); - resetQueryStage(); - - (void) CHECK_GL_ERROR(); -} - - -void GLBackend::do_pushProfileRange(const Batch& batch, size_t paramOffset) { - if (trace_render_gpu_gl_detail().isDebugEnabled()) { - auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); - profileRanges.push_back(name); -#if defined(NSIGHT_FOUND) - nvtxRangePush(name.c_str()); -#endif - } -} - -void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) { - if (trace_render_gpu_gl_detail().isDebugEnabled()) { - profileRanges.pop_back(); -#if defined(NSIGHT_FOUND) - nvtxRangePop(); -#endif - } -} - -// TODO: As long as we have gl calls explicitely issued from interface -// code, we need to be able to record and batch these calls. THe long -// term strategy is to get rid of any GL calls in favor of the HIFI GPU API - -// As long as we don;t use several versions of shaders we can avoid this more complex code path -#ifdef GPU_STEREO_CAMERA_BUFFER -#define GET_UNIFORM_LOCATION(shaderUniformLoc) ((_pipeline._programShader) ? _pipeline._programShader->getUniformLocation(shaderUniformLoc, (GLShader::Version) isStereo()) : -1) -#else -#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc -#endif - -void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - - glUniform1i( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), - batch._params[paramOffset + 0]._int); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform1f(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - - glUniform1f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), - batch._params[paramOffset + 0]._float); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform2f(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - glUniform2f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 0]._float); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform3f(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - glUniform3f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), - batch._params[paramOffset + 2]._float, - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 0]._float); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform4f(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - glUniform4f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 4]._int), - batch._params[paramOffset + 3]._float, - batch._params[paramOffset + 2]._float, - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 0]._float); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform3fv(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - glUniform3fv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), - batch._params[paramOffset + 1]._uint, - (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint)); - - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform4fv(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - - GLint location = GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int); - GLsizei count = batch._params[paramOffset + 1]._uint; - const GLfloat* value = (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint); - glUniform4fv(location, count, value); - - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniform4iv(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - glUniform4iv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), - batch._params[paramOffset + 1]._uint, - (const GLint*)batch.readData(batch._params[paramOffset + 0]._uint)); - - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniformMatrix3fv(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - - glUniformMatrix3fv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), - batch._params[paramOffset + 2]._uint, - batch._params[paramOffset + 1]._uint, - (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint)); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glUniformMatrix4fv(const Batch& batch, size_t paramOffset) { - if (_pipeline._program == 0) { - // We should call updatePipeline() to bind the program but we are not doing that - // because these uniform setters are deprecated and we don;t want to create side effect - return; - } - updatePipeline(); - - glUniformMatrix4fv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), - batch._params[paramOffset + 2]._uint, - batch._params[paramOffset + 1]._uint, - (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint)); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { - - glm::vec4 newColor( - batch._params[paramOffset + 3]._float, - batch._params[paramOffset + 2]._float, - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 0]._float); - - if (_input._colorAttribute != newColor) { - _input._colorAttribute = newColor; - glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r); - // Color has been changed and is not white. To prevent colors from bleeding - // between different objects, we need to set the _hadColorAttribute flag - // as if a previous render call had potential colors - _input._hadColorAttribute = (newColor != glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - } - (void)CHECK_GL_ERROR(); -} - -void GLBackend::releaseBuffer(GLuint id, Size size) const { - Lock lock(_trashMutex); - _buffersTrash.push_back({ id, size }); -} - -void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const { - Lock lock(_trashMutex); - _externalTexturesTrash.push_back({ id, recycler }); -} - -void GLBackend::releaseTexture(GLuint id, Size size) const { - Lock lock(_trashMutex); - _texturesTrash.push_back({ id, size }); -} - -void GLBackend::releaseFramebuffer(GLuint id) const { - Lock lock(_trashMutex); - _framebuffersTrash.push_back(id); -} - -void GLBackend::releaseShader(GLuint id) const { - Lock lock(_trashMutex); - _shadersTrash.push_back(id); -} - -void GLBackend::releaseProgram(GLuint id) const { - Lock lock(_trashMutex); - _programsTrash.push_back(id); -} - -void GLBackend::releaseQuery(GLuint id) const { - Lock lock(_trashMutex); - _queriesTrash.push_back(id); -} - -void GLBackend::queueLambda(const std::function lambda) const { - Lock lock(_trashMutex); - _lambdaQueue.push_back(lambda); -} - -void GLBackend::recycle() const { - PROFILE_RANGE(render_gpu_gl, __FUNCTION__) - { - std::list> lamdbasTrash; - { - Lock lock(_trashMutex); - std::swap(_lambdaQueue, lamdbasTrash); - } - for (auto lambda : lamdbasTrash) { - lambda(); - } - } - - { - std::vector ids; - std::list> buffersTrash; - { - Lock lock(_trashMutex); - std::swap(_buffersTrash, buffersTrash); - } - ids.reserve(buffersTrash.size()); - for (auto pair : buffersTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteBuffers((GLsizei)ids.size(), ids.data()); - } - } - - { - std::vector ids; - std::list framebuffersTrash; - { - Lock lock(_trashMutex); - std::swap(_framebuffersTrash, framebuffersTrash); - } - ids.reserve(framebuffersTrash.size()); - for (auto id : framebuffersTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); - } - } - - { - std::vector ids; - std::list> texturesTrash; - { - Lock lock(_trashMutex); - std::swap(_texturesTrash, texturesTrash); - } - ids.reserve(texturesTrash.size()); - for (auto pair : texturesTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteTextures((GLsizei)ids.size(), ids.data()); - } - } - - { - std::list> externalTexturesTrash; - { - Lock lock(_trashMutex); - std::swap(_externalTexturesTrash, externalTexturesTrash); - } - if (!externalTexturesTrash.empty()) { - std::vector fences; - fences.resize(externalTexturesTrash.size()); - for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { - fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - // External texture fences will be read in another thread/context, so we need a flush - glFlush(); - size_t index = 0; - for (auto pair : externalTexturesTrash) { - auto fence = fences[index++]; - pair.second(pair.first, fence); - } - } - } - - { - std::list programsTrash; - { - Lock lock(_trashMutex); - std::swap(_programsTrash, programsTrash); - } - for (auto id : programsTrash) { - glDeleteProgram(id); - } - } - - { - std::list shadersTrash; - { - Lock lock(_trashMutex); - std::swap(_shadersTrash, shadersTrash); - } - for (auto id : shadersTrash) { - glDeleteShader(id); - } - } - - { - std::vector ids; - std::list queriesTrash; - { - Lock lock(_trashMutex); - std::swap(_queriesTrash, queriesTrash); - } - ids.reserve(queriesTrash.size()); - for (auto id : queriesTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteQueries((GLsizei)ids.size(), ids.data()); - } - } - - GLVariableAllocationSupport::manageMemory(); - GLVariableAllocationSupport::_frameTexturesCreated = 0; - Texture::KtxStorage::releaseOpenKtxFiles(); -} - -void GLBackend::setCameraCorrection(const Mat4& correction) { - _transform._correction.correction = correction; - _transform._correction.correctionInverse = glm::inverse(correction); - _pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction); - _pipeline._cameraCorrectionBuffer._buffer->flush(); -} diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp deleted file mode 100644 index cac214b01e..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// -// GLBackendInput.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 3/8/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLShared.h" -#include "GLInputFormat.h" - -using namespace gpu; -using namespace gpu::gl; - -void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { - Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint); - if (format != _input._format) { - _input._format = format; - if (format) { - auto inputFormat = GLInputFormat::sync((*format)); - assert(inputFormat); - if (_input._formatKey != inputFormat->key) { - _input._formatKey = inputFormat->key; - _input._invalidFormat = true; - } - } else { - _input._formatKey.clear(); - _input._invalidFormat = true; - } - } -} - -void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { - Offset stride = batch._params[paramOffset + 0]._uint; - Offset offset = batch._params[paramOffset + 1]._uint; - BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); - uint32 channel = batch._params[paramOffset + 3]._uint; - - if (channel < getNumInputBuffers()) { - bool isModified = false; - if (_input._buffers[channel] != buffer) { - _input._buffers[channel] = buffer; - - GLuint vbo = 0; - if (buffer) { - vbo = getBufferID((*buffer)); - } - _input._bufferVBOs[channel] = vbo; - - isModified = true; - } - - if (_input._bufferOffsets[channel] != offset) { - _input._bufferOffsets[channel] = offset; - isModified = true; - } - - if (_input._bufferStrides[channel] != stride) { - _input._bufferStrides[channel] = stride; - isModified = true; - } - - if (isModified) { - _input._invalidBuffers.set(channel); - } - } -} - -void GLBackend::initInput() { - if(!_input._defaultVAO) { - glGenVertexArrays(1, &_input._defaultVAO); - } - glBindVertexArray(_input._defaultVAO); - (void) CHECK_GL_ERROR(); -} - -void GLBackend::killInput() { - glBindVertexArray(0); - if(_input._defaultVAO) { - glDeleteVertexArrays(1, &_input._defaultVAO); - } - (void) CHECK_GL_ERROR(); -} - -void GLBackend::syncInputStateCache() { - for (uint32_t i = 0; i < _input._attributeActivation.size(); i++) { - GLint active = 0; - glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &active); - _input._attributeActivation[i] = active; - } - //_input._defaultVAO - glBindVertexArray(_input._defaultVAO); -} - -void GLBackend::resetInputStage() { - // Reset index buffer - _input._indexBufferType = UINT32; - _input._indexBufferOffset = 0; - _input._indexBuffer.reset(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - (void) CHECK_GL_ERROR(); - - // Reset vertex buffer and format - _input._format.reset(); - _input._formatKey.clear(); - _input._invalidFormat = false; - _input._attributeActivation.reset(); - - for (uint32_t i = 0; i < _input._buffers.size(); i++) { - _input._buffers[i].reset(); - _input._bufferOffsets[i] = 0; - _input._bufferStrides[i] = 0; - _input._bufferVBOs[i] = 0; - } - _input._invalidBuffers.reset(); - - // THe vertex array binding MUST be reset in the specific Backend versions as they use different techniques -} - -void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) { - _input._indexBufferType = (Type)batch._params[paramOffset + 2]._uint; - _input._indexBufferOffset = batch._params[paramOffset + 0]._uint; - - BufferPointer indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); - if (indexBuffer != _input._indexBuffer) { - _input._indexBuffer = indexBuffer; - if (indexBuffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferID(*indexBuffer)); - } else { - // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - } - (void) CHECK_GL_ERROR(); -} - -void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { - _input._indirectBufferOffset = batch._params[paramOffset + 1]._uint; - _input._indirectBufferStride = batch._params[paramOffset + 2]._uint; - - BufferPointer buffer = batch._buffers.get(batch._params[paramOffset]._uint); - if (buffer != _input._indirectBuffer) { - _input._indirectBuffer = buffer; - if (buffer) { - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferID(*buffer)); - } else { - // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); - } - } - - (void)CHECK_GL_ERROR(); -} - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp deleted file mode 100644 index 7ef64343ea..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ /dev/null @@ -1,293 +0,0 @@ -// -// GLBackendPipeline.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 3/8/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLShared.h" -#include "GLPipeline.h" -#include "GLShader.h" -#include "GLState.h" -#include "GLBuffer.h" -#include "GLTexture.h" - -using namespace gpu; -using namespace gpu::gl; - -void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { - PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); - - if (_pipeline._pipeline == pipeline) { - return; - } - - // A true new Pipeline - _stats._PSNumSetPipelines++; - - // null pipeline == reset - if (!pipeline) { - _pipeline._pipeline.reset(); - - _pipeline._program = 0; - _pipeline._cameraCorrectionLocation = -1; - _pipeline._programShader = nullptr; - _pipeline._invalidProgram = true; - - _pipeline._state = nullptr; - _pipeline._invalidState = true; - } else { - auto pipelineObject = GLPipeline::sync(*this, *pipeline); - if (!pipelineObject) { - return; - } - - // check the program cache - // pick the program version - // check the program cache - // pick the program version -#ifdef GPU_STEREO_CAMERA_BUFFER - GLuint glprogram = pipelineObject->_program->getProgram((GLShader::Version) isStereo()); -#else - GLuint glprogram = pipelineObject->_program->getProgram(); -#endif - - if (_pipeline._program != glprogram) { - _pipeline._program = glprogram; - _pipeline._programShader = pipelineObject->_program; - _pipeline._invalidProgram = true; - _pipeline._cameraCorrectionLocation = pipelineObject->_cameraCorrection; - } - - // Now for the state - if (_pipeline._state != pipelineObject->_state) { - _pipeline._state = pipelineObject->_state; - _pipeline._invalidState = true; - } - - // Remember the new pipeline - _pipeline._pipeline = pipeline; - } - - // THis should be done on Pipeline::update... - if (_pipeline._invalidProgram) { - glUseProgram(_pipeline._program); - if (_pipeline._cameraCorrectionLocation != -1) { - gl::GLBuffer* cameraCorrectionBuffer = nullptr; - if (_transform._viewCorrectionEnabled) { - cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBuffer._buffer); - } else { - cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBufferIdentity._buffer); - } - glBindBufferRange(GL_UNIFORM_BUFFER, _pipeline._cameraCorrectionLocation, cameraCorrectionBuffer->_id, 0, sizeof(CameraCorrection)); - - } - (void) CHECK_GL_ERROR(); - _pipeline._invalidProgram = false; - } -} - -void GLBackend::updatePipeline() { - if (_pipeline._invalidProgram) { - // doing it here is aproblem for calls to glUniform.... so will do it on assing... - glUseProgram(_pipeline._program); - (void) CHECK_GL_ERROR(); - _pipeline._invalidProgram = false; - } - - if (_pipeline._invalidState) { - if (_pipeline._state) { - // first reset to default what should be - // the fields which were not to default and are default now - resetPipelineState(_pipeline._state->_signature); - - // Update the signature cache with what's going to be touched - _pipeline._stateSignatureCache |= _pipeline._state->_signature; - - // And perform - for (auto command: _pipeline._state->_commands) { - command->run(this); - } - } else { - // No state ? anyway just reset everything - resetPipelineState(0); - } - _pipeline._invalidState = false; - } -} - -void GLBackend::resetPipelineStage() { - // First reset State to default - State::Signature resetSignature(0); - resetPipelineState(resetSignature); - _pipeline._state = nullptr; - _pipeline._invalidState = false; - - // Second the shader side - _pipeline._invalidProgram = false; - _pipeline._program = 0; - _pipeline._programShader = nullptr; - _pipeline._pipeline.reset(); - glUseProgram(0); -} - -void GLBackend::releaseUniformBuffer(uint32_t slot) { - auto& buf = _uniform._buffers[slot]; - if (buf) { - auto* object = Backend::getGPUObject(*buf); - if (object) { - glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE - (void) CHECK_GL_ERROR(); - } - buf.reset(); - } -} - -void GLBackend::resetUniformStage() { - for (uint32_t i = 0; i < _uniform._buffers.size(); i++) { - releaseUniformBuffer(i); - } -} - -void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { - GLuint slot = batch._params[paramOffset + 3]._uint; - if (slot >(GLuint)MAX_NUM_UNIFORM_BUFFERS) { - qCDebug(gpugllogging) << "GLBackend::do_setUniformBuffer: Trying to set a uniform Buffer at slot #" << slot << " which doesn't exist. MaxNumUniformBuffers = " << getMaxNumUniformBuffers(); - return; - } - BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); - GLintptr rangeStart = batch._params[paramOffset + 1]._uint; - GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; - - if (!uniformBuffer) { - releaseUniformBuffer(slot); - return; - } - - // check cache before thinking - if (_uniform._buffers[slot] == uniformBuffer) { - return; - } - - // Sync BufferObject - auto* object = syncGPUObject(*uniformBuffer); - if (object) { - glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, rangeStart, rangeSize); - - _uniform._buffers[slot] = uniformBuffer; - (void) CHECK_GL_ERROR(); - } else { - releaseUniformBuffer(slot); - return; - } -} - -void GLBackend::releaseResourceTexture(uint32_t slot) { - auto& tex = _resource._textures[slot]; - if (tex) { - auto* object = Backend::getGPUObject(*tex); - if (object) { - GLuint target = object->_target; - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, 0); // RELEASE - (void) CHECK_GL_ERROR(); - } - tex.reset(); - } -} - -void GLBackend::resetResourceStage() { - for (uint32_t i = 0; i < _resource._buffers.size(); i++) { - releaseResourceBuffer(i); - } - for (uint32_t i = 0; i < _resource._textures.size(); i++) { - releaseResourceTexture(i); - } -} - - -void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { - GLuint slot = batch._params[paramOffset + 1]._uint; - if (slot >= (GLuint)MAX_NUM_RESOURCE_BUFFERS) { - qCDebug(gpugllogging) << "GLBackend::do_setResourceBuffer: Trying to set a resource Buffer at slot #" << slot << " which doesn't exist. MaxNumResourceBuffers = " << getMaxNumResourceBuffers(); - return; - } - - auto resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint); - - if (!resourceBuffer) { - releaseResourceBuffer(slot); - return; - } - // check cache before thinking - if (_resource._buffers[slot] == resourceBuffer) { - return; - } - - // One more True Buffer bound - _stats._RSNumResourceBufferBounded++; - - // If successful bind then cache it - if (bindResourceBuffer(slot, resourceBuffer)) { - _resource._buffers[slot] = resourceBuffer; - } else { // else clear slot and cache - releaseResourceBuffer(slot); - return; - } -} - -void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { - GLuint slot = batch._params[paramOffset + 1]._uint; - if (slot >= (GLuint) MAX_NUM_RESOURCE_TEXTURES) { - qCDebug(gpugllogging) << "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); - return; - } - - TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); - - if (!resourceTexture) { - releaseResourceTexture(slot); - return; - } - // check cache before thinking - if (_resource._textures[slot] == resourceTexture) { - return; - } - - // One more True texture bound - _stats._RSNumTextureBounded++; - - // Always make sure the GLObject is in sync - GLTexture* object = syncGPUObject(resourceTexture); - if (object) { - GLuint to = object->_texture; - GLuint target = object->_target; - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, to); - - (void) CHECK_GL_ERROR(); - - _resource._textures[slot] = resourceTexture; - - _stats._RSAmountTextureMemoryBounded += (int) object->size(); - - } else { - releaseResourceTexture(slot); - return; - } -} - -int GLBackend::ResourceStageState::findEmptyTextureSlot() const { - // start from the end of the slots, try to find an empty one that can be used - for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) { - if (!_textures[i]) { - return i; - } - } - return -1; -} - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp deleted file mode 100644 index f286a5cca9..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp +++ /dev/null @@ -1,171 +0,0 @@ -// -// GLBackendTransform.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 3/8/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - -// Transform Stage -void GLBackend::do_setModelTransform(const Batch& batch, size_t paramOffset) { -} - -void GLBackend::do_setViewTransform(const Batch& batch, size_t paramOffset) { - _transform._view = batch._transforms.get(batch._params[paramOffset]._uint); - _transform._viewIsCamera = batch._params[paramOffset + 1]._uint != 0; - _transform._invalidView = true; -} - -void GLBackend::do_setProjectionTransform(const Batch& batch, size_t paramOffset) { - memcpy(&_transform._projection, batch.readData(batch._params[paramOffset]._uint), sizeof(Mat4)); - _transform._invalidProj = true; -} - -void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset) { - memcpy(&_transform._viewport, batch.readData(batch._params[paramOffset]._uint), sizeof(Vec4i)); - -#ifdef GPU_STEREO_DRAWCALL_INSTANCED - { - ivec4& vp = _transform._viewport; - glViewport(vp.x, vp.y, vp.z, vp.w); - - // Where we assign the GL viewport - if (_stereo.isStereo()) { - vp.z /= 2; - if (_stereo._pass) { - vp.x += vp.z; - } - } - } -#else - if (!_inRenderTransferPass && !isStereo()) { - ivec4& vp = _transform._viewport; - glViewport(vp.x, vp.y, vp.z, vp.w); - } -#endif - - // The Viewport is tagged invalid because the CameraTransformUBO is not up to date and will need update on next drawcall - _transform._invalidViewport = true; -} - -void GLBackend::do_setDepthRangeTransform(const Batch& batch, size_t paramOffset) { - - Vec2 depthRange(batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - - if ((depthRange.x != _transform._depthRange.x) || (depthRange.y != _transform._depthRange.y)) { - _transform._depthRange = depthRange; - - glDepthRangef(depthRange.x, depthRange.y); - } -} - -void GLBackend::killTransform() { - glDeleteBuffers(1, &_transform._objectBuffer); - glDeleteBuffers(1, &_transform._cameraBuffer); - glDeleteBuffers(1, &_transform._drawCallInfoBuffer); - glDeleteTextures(1, &_transform._objectBufferTexture); -} - -void GLBackend::syncTransformStateCache() { - _transform._invalidViewport = true; - _transform._invalidProj = true; - _transform._invalidView = true; - - glGetIntegerv(GL_VIEWPORT, (GLint*) &_transform._viewport); - - glGetFloatv(GL_DEPTH_RANGE, (GLfloat*)&_transform._depthRange); - - Mat4 modelView; - auto modelViewInv = glm::inverse(modelView); - _transform._view.evalFromRawMatrix(modelViewInv); - - glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); - _transform._enabledDrawcallInfoBuffer = false; -} - -void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo) { - // Check all the dirty flags and update the state accordingly - if (_invalidViewport) { - _camera._viewport = glm::vec4(_viewport); - } - - if (_invalidProj) { - _camera._projection = _projection; - } - - if (_invalidView) { - // Apply the correction - if (_viewIsCamera && (_viewCorrectionEnabled && _correction.correction != glm::mat4())) { - // FIXME should I switch to using the camera correction buffer in Transform.slf and leave this out? - Transform result; - _view.mult(result, _view, _correction.correction); - if (_skybox) { - result.setTranslation(vec3()); - } - _view = result; - } - // This is when the _view matrix gets assigned - _view.getInverseMatrix(_camera._view); - } - - if (_invalidView || _invalidProj || _invalidViewport) { - size_t offset = _cameraUboSize * _cameras.size(); - _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); - - if (stereo.isStereo()) { -#ifdef GPU_STEREO_CAMERA_BUFFER - _cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view), _camera.getEyeCamera(1, stereo, _view))); -#else - _cameras.push_back((_camera.getEyeCamera(0, stereo, _view))); - _cameras.push_back((_camera.getEyeCamera(1, stereo, _view))); -#endif - } else { -#ifdef GPU_STEREO_CAMERA_BUFFER - _cameras.push_back(CameraBufferElement(_camera.recomputeDerived(_view))); -#else - _cameras.push_back((_camera.recomputeDerived(_view))); -#endif - } - } - - // Flags are clean - _invalidView = _invalidProj = _invalidViewport = false; -} - -void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const { - size_t offset = INVALID_OFFSET; - while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { - offset = (*_camerasItr).second; - _currentCameraOffset = offset; - ++_camerasItr; - } - - if (offset != INVALID_OFFSET) { -#ifdef GPU_STEREO_CAMERA_BUFFER - bindCurrentCamera(0); -#else - if (!stereo.isStereo()) { - bindCurrentCamera(0); - } -#endif - } - (void)CHECK_GL_ERROR(); -} - -void GLBackend::TransformStageState::bindCurrentCamera(int eye) const { - if (_currentCameraOffset != INVALID_OFFSET) { - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, _cameraBuffer, _currentCameraOffset + eye * _cameraUboSize, sizeof(CameraBufferElement)); - } -} - -void GLBackend::resetTransformStage() { - glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); - _transform._enabledDrawcallInfoBuffer = false; -} diff --git a/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp new file mode 100644 index 0000000000..72a76f8f90 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp @@ -0,0 +1,63 @@ +// +// GLBackend.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 10/27/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include + +#include + +#include + +#include +#include +#include + +#include "../gl41/GL41Backend.h" +#include "../gl45/GL45Backend.h" + +using namespace gpu; +using namespace gpu::gl; + +static const QString DEBUG_FLAG("HIFI_DISABLE_OPENGL_45"); +static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + +static GLBackend* INSTANCE{ nullptr }; + +BackendPointer GLBackend::createBackend() { + // FIXME provide a mechanism to override the backend for testing + // Where the gpuContext is initialized and where the TRUE Backend is created and assigned + auto version = QOpenGLContextWrapper::currentContextVersion(); + std::shared_ptr result; + if (!disableOpenGL45 && version >= 0x0405) { + qCDebug(gpugllogging) << "Using OpenGL 4.5 backend"; + result = std::make_shared(); + } else { + qCDebug(gpugllogging) << "Using OpenGL 4.1 backend"; + result = std::make_shared(); + } + result->initInput(); + result->initTransform(); + result->initTextureManagementStage(); + + INSTANCE = result.get(); + void* voidInstance = &(*result); + qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance)); + return result; +} + +GLBackend& getBackend() { + if (!INSTANCE) { + INSTANCE = static_cast(qApp->property(hifi::properties::gl::BACKEND).value()); + } + return *INSTANCE; +} + +bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { + return GLShader::makeProgram(getBackend(), shader, slotBindings, handler); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 42926fdb1c..9479321747 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -13,8 +13,8 @@ #include -#include "../gl/GLBackend.h" -#include "../gl/GLTexture.h" +#include +#include #define GPU_CORE_41 410 #define GPU_CORE_43 430 @@ -173,6 +173,8 @@ protected: void makeProgramBindings(ShaderObject& shaderObject) override; int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override; + static bool supportedTextureFormat(const gpu::Element& format); + }; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index eb66cc4544..62ade673b4 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GL41Backend.h" -#include "../gl/GLBuffer.h" +#include namespace gpu { namespace gl41 { diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp index 195b155bf3..a5ef2d92e1 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp @@ -12,8 +12,8 @@ #include -#include "../gl/GLFramebuffer.h" -#include "../gl/GLTexture.h" +#include +#include namespace gpu { namespace gl41 { diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp index f712550973..0f75c29f38 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp @@ -10,7 +10,7 @@ // #include "GL41Backend.h" -#include "../gl/GLQuery.h" +#include using namespace gpu; using namespace gpu::gl; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp index ff9ddaae63..35bfafdc50 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GL41Backend.h" -#include "../gl/GLShader.h" +#include using namespace gpu; using namespace gpu::gl; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 2834a8463c..0298b8b892 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -13,12 +13,30 @@ #include #include -#include "../gl/GLTexelFormat.h" +#include using namespace gpu; using namespace gpu::gl; using namespace gpu::gl41; +bool GL41Backend::supportedTextureFormat(const gpu::Element& format) { + switch (format.getSemantic()) { + case gpu::Semantic::COMPRESSED_ETC2_RGB: + case gpu::Semantic::COMPRESSED_ETC2_SRGB: + case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_RGBA: + case gpu::Semantic::COMPRESSED_ETC2_SRGBA: + case gpu::Semantic::COMPRESSED_EAC_RED: + case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED: + case gpu::Semantic::COMPRESSED_EAC_XY: + case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED: + return false; + default: + return true; + } +} + GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { if (!texturePointer) { return nullptr; @@ -34,6 +52,11 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { return nullptr; } + // Check whether the texture is in a format we can deal with + if (!supportedTextureFormat(texture.getTexelFormat())) { + return nullptr; + } + GL41Texture* object = Backend::getGPUObject(texture); if (!object) { switch (texture.getUsageType()) { @@ -167,7 +190,6 @@ void GL41Texture::syncSampler() const { glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); @@ -206,9 +228,6 @@ void GL41FixedAllocationTexture::allocateStorage() const { void GL41FixedAllocationTexture::syncSampler() const { Parent::syncSampler(); const Sampler& sampler = _gpuObject.getSampler(); - auto baseMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); - - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip); glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip())); } @@ -610,4 +629,3 @@ GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr& backend GL41ResourceTexture::~GL41ResourceTexture() { } - diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 1a4b63d35f..c23a83eaf9 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -12,12 +12,15 @@ #ifndef hifi_gpu_45_GL45Backend_h #define hifi_gpu_45_GL45Backend_h -#include "../gl/GLBackend.h" -#include "../gl/GLTexture.h" +#include +#include + #include +#include #define INCREMENTAL_TRANSFER 0 #define GPU_SSBO_TRANSFORM_OBJECT 1 +#define GPU_BINDLESS_TEXTURES 0 namespace gpu { namespace gl45 { @@ -30,6 +33,9 @@ class GL45Backend : public GLBackend { friend class Context; public: +#if GPU_BINDLESS_TEXTURES + virtual bool supportsBindless() const override { return true; } +#endif #ifdef GPU_SSBO_TRANSFORM_OBJECT static const GLint TRANSFORM_OBJECT_SLOT { 14 }; // SSBO binding slot @@ -57,8 +63,62 @@ public: void generateMips() const override; Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override; void syncSampler() const override; + +#if GPU_BINDLESS_TEXTURES + bool isBindless() const { + return _bindless.operator bool(); + } + + struct Bindless { + uint64_t handle{ 0 }; + uint32_t minMip{ 0 }; + uint32_t sampler{ 0 }; + + bool operator==(const Bindless& other) const { + return handle == other.handle && minMip == other.minMip && sampler == other.sampler; + } + + bool operator!=(const Bindless& other) const { + return !(*this == other); + } + + operator bool() const { + return handle != 0; + } + }; + + virtual const Bindless& getBindless() const; + void releaseBindless() const; + void recreateBindless() const; + private: + mutable Bindless _bindless; +#endif + + static Sampler getInvalidSampler(); + + // This stores the texture handle (64 bits) in xy, the min mip available in z, and the sampler ID in w + mutable Sampler _cachedSampler{ getInvalidSampler() }; }; +#if GPU_BINDLESS_TEXTURES + class GL45TextureTable : public GLObject { + static GLuint allocate(); + using Parent = GLObject; + public: + using BindlessArray = std::array; + + GL45TextureTable(const std::weak_ptr& backend, const TextureTable& texture); + ~GL45TextureTable(); + + void update(const BindlessArray& newHandles); + + // FIXME instead of making a buffer for each table, there should be a global buffer of all materials + // and we should store an offset into that buffer + BindlessArray _handles; + }; +#endif + + // // Textures that have fixed allocation sizes and cannot be managed at runtime // @@ -73,6 +133,7 @@ public: protected: Size size() const override { return _size; } + void allocateStorage() const; void syncSampler() const override; const Size _size { 0 }; @@ -103,7 +164,6 @@ public: friend class GL45Backend; using PromoteLambda = std::function; - protected: GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture); ~GL45VariableAllocationTexture(); @@ -113,6 +173,9 @@ public: Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override; void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) override; +#if GPU_BINDLESS_TEXTURES + virtual const Bindless& getBindless() const override; +#endif }; class GL45ResourceTexture : public GL45VariableAllocationTexture { @@ -181,6 +244,7 @@ protected: GLuint getQueryID(const QueryPointer& query) override; GLQuery* syncGPUObject(const Query& query) override; + // Draw Stage void do_draw(const Batch& batch, size_t paramOffset) override; void do_drawIndexed(const Batch& batch, size_t paramOffset) override; @@ -212,6 +276,12 @@ protected: // Texture Management Stage void initTextureManagementStage() override; + +#if GPU_BINDLESS_TEXTURES + GL45TextureTable* syncGPUObject(const TextureTablePointer& textureTable); + // Resource stage + void do_setResourceTextureTable(const Batch& batch, size_t paramOffset) override; +#endif }; } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp index b4a6410612..2afbea3876 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GL45Backend.h" -#include "../gl/GLBuffer.h" +#include namespace gpu { namespace gl45 { using namespace gpu::gl; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index 4a43fc988c..34bf6774f7 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GL45Backend.h" -#include "../gl/GLShared.h" +#include using namespace gpu; using namespace gpu::gl45; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp index 9648af9b21..ca53d6c624 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GL45Backend.h" -#include "../gl/GLFramebuffer.h" -#include "../gl/GLTexture.h" +#include +#include #include diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp index df81d7914e..62f87ed913 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendQuery.cpp @@ -10,7 +10,7 @@ // #include "GL45Backend.h" -#include "../gl/GLQuery.h" +#include namespace gpu { namespace gl45 { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp index c2490524ae..8de60a7921 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GL45Backend.h" -#include "../gl/GLShader.h" +#include //#include using namespace gpu; @@ -165,6 +165,11 @@ void GL45Backend::makeProgramBindings(ShaderObject& shaderObject) { shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT; } + loc = glGetUniformBlockIndex(glprogram, "gpu_resourceTextureTable0"); + if (loc >= 0) { + glUniformBlockBinding(glprogram, loc, RESOURCE_TABLE_TEXTURE_SLOT_OFFSET); + } + (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 1d415c7ca3..4d5ffefa67 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -20,14 +20,17 @@ #include #include -#include "../gl/GLTexelFormat.h" +#include +#include +#include using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -#define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f #define MAX_RESOURCE_TEXTURES_PER_FRAME 2 +#define FORCE_STRICT_TEXTURE 0 +#define ENABLE_SPARSE_TEXTURE 0 GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { if (!texturePointer) { @@ -51,14 +54,18 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { object = new GL45AttachmentTexture(shared_from_this(), texture); break; +#if FORCE_STRICT_TEXTURE + case TextureUsageType::RESOURCE: +#endif case TextureUsageType::STRICT_RESOURCE: qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str(); object = new GL45StrictResourceTexture(shared_from_this(), texture); break; +#if !FORCE_STRICT_TEXTURE case TextureUsageType::RESOURCE: { if (GL45VariableAllocationTexture::_frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME) { -#if 0 +#if ENABLE_SPARSE_TEXTURE if (isTextureManagementSparseEnabled() && GL45Texture::isSparseEligible(texture)) { object = new GL45SparseResourceTexture(shared_from_this(), texture); } else { @@ -76,7 +83,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { } break; } - +#endif default: Q_UNREACHABLE(); } @@ -114,6 +121,61 @@ void GL45Backend::initTextureManagementStage() { using GL45Texture = GL45Backend::GL45Texture; + +class GLSamplerCache { +public: + GLuint getGLSampler(const Sampler& sampler) { + if (0 == _samplerCache.count(sampler)) { + GLuint result = 0; + glGenSamplers(1, &result); + const auto& fm = GLTexture::FILTER_MODES[sampler.getFilter()]; + glSamplerParameteri(result, GL_TEXTURE_MIN_FILTER, fm.minFilter); + glSamplerParameteri(result, GL_TEXTURE_MAG_FILTER, fm.magFilter); + if (sampler.doComparison()) { + glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); + glSamplerParameteri(result, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); + } else { + glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + + glSamplerParameteri(result, GL_TEXTURE_WRAP_S, GLTexture::WRAP_MODES[sampler.getWrapModeU()]); + glSamplerParameteri(result, GL_TEXTURE_WRAP_T, GLTexture::WRAP_MODES[sampler.getWrapModeV()]); + glSamplerParameteri(result, GL_TEXTURE_WRAP_R, GLTexture::WRAP_MODES[sampler.getWrapModeW()]); + + glSamplerParameterf(result, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + glSamplerParameterfv(result, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + + glSamplerParameterf(result, GL_TEXTURE_MIN_LOD, sampler.getMinMip()); + glSamplerParameterf(result, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + _samplerCache[sampler] = result; + return result; + } + + return _samplerCache[sampler]; + } + + void releaseGLSampler(GLuint sampler) { + // NO OP + } + +private: + std::unordered_map _samplerCache; +}; + +static GLSamplerCache SAMPLER_CACHE; + + +Sampler GL45Texture::getInvalidSampler() { + static Sampler INVALID_SAMPLER; + static std::once_flag once; + std::call_once(once, [] { + Sampler::Desc invalidDesc; + invalidDesc._borderColor = vec4(-1.0f); + INVALID_SAMPLER = Sampler(invalidDesc); + }); + return INVALID_SAMPLER; +} + GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture) : GLTexture(backend, texture, allocate(texture)) { } @@ -121,10 +183,10 @@ GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& GLuint GL45Texture::allocate(const Texture& texture) { GLuint result; glCreateTextures(getGLTextureType(texture), 1, &result); -#ifdef DEBUG - auto source = texture.source(); - glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data()); -#endif + if (::gl::Context::enableDebugLogger()) { + auto source = texture.source(); + glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data()); + } return result; } @@ -144,6 +206,16 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -160,6 +232,16 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: if (glCompressedTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat, @@ -189,8 +271,50 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const return amountCopied; } +#if GPU_BINDLESS_TEXTURES +void GL45Texture::releaseBindless() const { + // Release the old handler + SAMPLER_CACHE.releaseGLSampler(_bindless.sampler); + glMakeTextureHandleNonResidentARB(_bindless.handle); + _bindless = Bindless(); +} + +void GL45Texture::recreateBindless() const { + if (isBindless()) { + releaseBindless(); + } else { + // Once a texture is about to become bindless, it's base mip level MUST be set to 0 + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); + } + + _bindless.sampler = SAMPLER_CACHE.getGLSampler(_cachedSampler); + _bindless.handle = glGetTextureSamplerHandleARB(_id, _bindless.sampler); + glMakeTextureHandleResidentARB(_bindless.handle); +} + +const GL45Texture::Bindless& GL45Texture::getBindless() const { + if (!_bindless) { + recreateBindless(); + } + return _bindless; +} +#endif + + void GL45Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); + if (_cachedSampler == sampler) { + return; + } + + _cachedSampler = sampler; + +#if GPU_BINDLESS_TEXTURES + if (isBindless()) { + recreateBindless(); + return; + } +#endif const auto& fm = FILTER_MODES[sampler.getFilter()]; glTextureParameteri(_id, GL_TEXTURE_MIN_FILTER, fm.minFilter); @@ -214,6 +338,8 @@ void GL45Texture::syncSampler() const { glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); } +// Fixed allocation textures, used for strict resources & framebuffer attachments + using GL45FixedAllocationTexture = GL45Backend::GL45FixedAllocationTexture; GL45FixedAllocationTexture::GL45FixedAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture), _size(texture.evalTotalSize()) { @@ -238,8 +364,6 @@ void GL45FixedAllocationTexture::allocateStorage() const { void GL45FixedAllocationTexture::syncSampler() const { Parent::syncSampler(); const Sampler& sampler = _gpuObject.getSampler(); - auto baseMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip); glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); } @@ -275,6 +399,9 @@ GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr& backend, const TextureTable& textureTable) + : Parent(backend, textureTable, allocate()){ + Backend::setGPUObject(textureTable, this); + // FIXME include these in overall buffer storage reporting + glNamedBufferStorage(_id, sizeof(uvec4) * TextureTable::COUNT, nullptr, GL_DYNAMIC_STORAGE_BIT); +} + +void GL45TextureTable::update(const BindlessArray& handles) { + if (_handles != handles) { + _handles = handles; + // FIXME include these in overall buffer storage reporting + // FIXME use a single shared buffer for bindless data + glNamedBufferSubData(_id, 0, sizeof(GL45Texture::Bindless) * TextureTable::COUNT, &_handles[0]); + } +} + +GL45TextureTable::~GL45TextureTable() { + if (_id) { + auto backend = _backend.lock(); + if (backend) { + // FIXME include these in overall buffer storage reporting + backend->releaseBuffer(_id, 0); + } + } +} + +GL45TextureTable* GL45Backend::syncGPUObject(const TextureTablePointer& textureTablePointer) { + const auto& textureTable = *textureTablePointer; + + // Find the target handles + auto defaultTextures = gpu::TextureTable::getDefault()->getTextures(); + auto textures = textureTable.getTextures(); + GL45TextureTable::BindlessArray handles{}; + for (size_t i = 0; i < textures.size(); ++i) { + auto texture = textures[i]; + if (!texture) { + texture = defaultTextures[i]; + } + if (!texture) { + continue; + } + // FIXME what if we have a non-transferrable texture here? + auto gltexture = (GL45Texture*)syncGPUObject(texture); + if (!gltexture) { + continue; + } + handles[i] = gltexture->getBindless(); + } + + // If the object hasn't been created, or the object definition is out of date, drop and re-create + GL45TextureTable* object = Backend::getGPUObject(textureTable); + + if (!object) { + object = new GL45TextureTable(shared_from_this(), textureTable); + } + + object->update(handles); + + return object; +} + +void GL45Backend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) { + auto textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint); + if (!textureTablePointer) { + return; + } + + auto slot = batch._params[paramOffset + 1]._uint; + GL45TextureTable* glTextureTable = syncGPUObject(textureTablePointer); + if (glTextureTable) { + glBindBufferBase(GL_UNIFORM_BUFFER, slot + GLBackend::RESOURCE_TABLE_TEXTURE_SLOT_OFFSET, glTextureTable->_id); + } +} +#endif diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 0f1de0f868..08d077605d 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -21,12 +21,13 @@ #include #include -#include "../gl/GLTexelFormat.h" +#include using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; +using GL45Texture = GL45Backend::GL45Texture; using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture; GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture) { @@ -40,6 +41,17 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() { Backend::textureResourcePopulatedGPUMemSize.update(_populatedSize, 0); } +#if GPU_BINDLESS_TEXTURES +const GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const { + // The parent call may re-initialize the _bindless member, so we need to call it first + const auto& result = Parent::getBindless(); + // Make sure the referenced structure has the correct minimum available mip value + _bindless.minMip = _populatedMip - _allocatedMip; + // Now return the result + return result; +} +#endif + Size GL45VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const { Size amountCopied = 0; amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer); @@ -127,7 +139,13 @@ Size GL45ResourceTexture::copyMipsFromTexture() { void GL45ResourceTexture::syncSampler() const { Parent::syncSampler(); +#if GPU_BINDLESS_TEXTURES + if (!isBindless()) { + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); + } +#else glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); +#endif } void GL45ResourceTexture::promote() { @@ -137,6 +155,13 @@ void GL45ResourceTexture::promote() { uint16_t targetAllocatedMip = _allocatedMip - std::min(_allocatedMip, 2); targetAllocatedMip = std::max(_minAllocatedMip, targetAllocatedMip); +#if GPU_BINDLESS_TEXTURES + bool bindless = isBindless(); + if (bindless) { + releaseBindless(); + } +#endif + GLuint oldId = _id; auto oldSize = _size; uint16_t oldAllocatedMip = _allocatedMip; @@ -150,10 +175,17 @@ void GL45ResourceTexture::promote() { // copy pre-existing mips copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); +#if GPU_BINDLESS_TEXTURES + if (bindless) { + getBindless(); + } +#endif + // destroy the old texture glDeleteTextures(1, &oldId); // Update sampler + _cachedSampler = getInvalidSampler(); syncSampler(); // update the memory usage @@ -170,6 +202,13 @@ void GL45ResourceTexture::demote() { auto oldSize = _size; auto oldPopulatedMip = _populatedMip; +#if GPU_BINDLESS_TEXTURES + bool bindless = isBindless(); + if (bindless) { + releaseBindless(); + } +#endif + // allocate new texture const_cast(_id) = allocate(_gpuObject); uint16_t oldAllocatedMip = _allocatedMip; @@ -179,10 +218,17 @@ void GL45ResourceTexture::demote() { // copy pre-existing mips copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); +#if GPU_BINDLESS_TEXTURES + if (bindless) { + getBindless(); + } +#endif + // destroy the old texture glDeleteTextures(1, &oldId); // Update sampler + _cachedSampler = getInvalidSampler(); syncSampler(); // update the memory usage diff --git a/libraries/gpu-gles/CMakeLists.txt b/libraries/gpu-gles/CMakeLists.txt index ea69919f6d..82bf670781 100644 --- a/libraries/gpu-gles/CMakeLists.txt +++ b/libraries/gpu-gles/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME gpu-gles) setup_hifi_library(Gui Concurrent) -link_hifi_libraries(shared gl gpu) +link_hifi_libraries(shared gl gpu gpu-gl-common) GroupSources("src") target_opengl() diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackend.h b/libraries/gpu-gles/src/gpu/gl/GLBackend.h deleted file mode 100644 index 3681fc0492..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBackend.h +++ /dev/null @@ -1,474 +0,0 @@ -// -// Created by Cristian Duarte & Gabriel Calero on 09/21/2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gles_Backend_h -#define hifi_gpu_gles_Backend_h - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -#include "GLShared.h" - - -// Different versions for the stereo drawcall -// Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only -#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE -//#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER -//#define GPU_STEREO_TECHNIQUE_INSTANCED - - -// Let these be configured by the one define picked above -#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE -#define GPU_STEREO_DRAWCALL_DOUBLED -#endif - -#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER -#define GPU_STEREO_DRAWCALL_DOUBLED -#define GPU_STEREO_CAMERA_BUFFER -#endif - -#ifdef GPU_STEREO_TECHNIQUE_INSTANCED -#define GPU_STEREO_DRAWCALL_INSTANCED -#define GPU_STEREO_CAMERA_BUFFER -#endif - -namespace gpu { namespace gl { - -class GLBackend : public Backend, public std::enable_shared_from_this { - // Context Backend static interface required - friend class gpu::Context; - static void init(); - static BackendPointer createBackend(); - -protected: - explicit GLBackend(bool syncCache); - GLBackend(); -public: - static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings = Shader::BindingSet(), const Shader::CompilationHandler& handler = nullptr); - - virtual ~GLBackend(); - - void setCameraCorrection(const Mat4& correction); - void render(const Batch& batch) final override; - - // This call synchronize the Full Backend cache with the current GLState - // THis is only intended to be used when mixing raw gl calls with the gpu api usage in order to sync - // the gpu::Backend state with the true gl state which has probably been messed up by these ugly naked gl calls - // Let's try to avoid to do that as much as possible! - void syncCache() final override; - - // This is the ugly "download the pixels to sysmem for taking a snapshot" - // Just avoid using it, it's ugly and will break performances - virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, - const Vec4i& region, QImage& destImage) final override; - - - // this is the maximum numeber of available input buffers - size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } - - // this is the maximum per shader stage on the low end apple - // TODO make it platform dependant at init time - static const int MAX_NUM_UNIFORM_BUFFERS = 12; - size_t getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; } - - // this is the maximum per shader stage on the low end apple - // TODO make it platform dependant at init time - static const int MAX_NUM_RESOURCE_BUFFERS = 16; - size_t getMaxNumResourceBuffers() const { return MAX_NUM_RESOURCE_BUFFERS; } - static const int MAX_NUM_RESOURCE_TEXTURES = 16; - size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } - - // Draw Stage - virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; - virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; - virtual void do_drawInstanced(const Batch& batch, size_t paramOffset) = 0; - virtual void do_drawIndexedInstanced(const Batch& batch, size_t paramOffset) = 0; - virtual void do_multiDrawIndirect(const Batch& batch, size_t paramOffset) = 0; - virtual void do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOffset) = 0; - - // Input Stage - virtual void do_setInputFormat(const Batch& batch, size_t paramOffset) final; - virtual void do_setInputBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_setIndexBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_setIndirectBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_generateTextureMips(const Batch& batch, size_t paramOffset) final; - - // Transform Stage - virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setViewTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setProjectionTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setViewportTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setDepthRangeTransform(const Batch& batch, size_t paramOffset) final; - - // Uniform Stage - virtual void do_setUniformBuffer(const Batch& batch, size_t paramOffset) final; - - // Resource Stage - virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final; - - // Pipeline Stage - virtual void do_setPipeline(const Batch& batch, size_t paramOffset) final; - - // Output stage - virtual void do_setFramebuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_clearFramebuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_blit(const Batch& batch, size_t paramOffset) = 0; - - // Query section - virtual void do_beginQuery(const Batch& batch, size_t paramOffset) final; - virtual void do_endQuery(const Batch& batch, size_t paramOffset) final; - virtual void do_getQuery(const Batch& batch, size_t paramOffset) final; - - // Reset stages - virtual void do_resetStages(const Batch& batch, size_t paramOffset) final; - - - virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final; - virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final; - - virtual void do_disableContextStereo(const Batch& batch, size_t paramOffset) final; - virtual void do_restoreContextStereo(const Batch& batch, size_t paramOffset) final; - - virtual void do_runLambda(const Batch& batch, size_t paramOffset) final; - - virtual void do_startNamedCall(const Batch& batch, size_t paramOffset) final; - virtual void do_stopNamedCall(const Batch& batch, size_t paramOffset) final; - - static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; - // The drawcall Info attribute channel is reserved and is the upper bound for the number of availables Input buffers - static const int MAX_NUM_INPUT_BUFFERS = Stream::DRAW_CALL_INFO; - - virtual void do_pushProfileRange(const Batch& batch, size_t paramOffset) final; - virtual void do_popProfileRange(const Batch& batch, size_t paramOffset) final; - - // TODO: As long as we have gl calls explicitely issued from interface - // code, we need to be able to record and batch these calls. THe long - // term strategy is to get rid of any GL calls in favor of the HIFI GPU API - virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform2f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform3f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform4f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform3fv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform4fv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform4iv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniformMatrix3fv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniformMatrix4fv(const Batch& batch, size_t paramOffset) final; - - virtual void do_glColor4f(const Batch& batch, size_t paramOffset) final; - - // The State setters called by the GLState::Commands when a new state is assigned - virtual void do_setStateFillMode(int32 mode) final; - virtual void do_setStateCullMode(int32 mode) final; - virtual void do_setStateFrontFaceClockwise(bool isClockwise) final; - virtual void do_setStateDepthClampEnable(bool enable) final; - virtual void do_setStateScissorEnable(bool enable) final; - virtual void do_setStateMultisampleEnable(bool enable) final; - virtual void do_setStateAntialiasedLineEnable(bool enable) final; - virtual void do_setStateDepthBias(Vec2 bias) final; - virtual void do_setStateDepthTest(State::DepthTest test) final; - virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; - virtual void do_setStateAlphaToCoverageEnable(bool enable) final; - virtual void do_setStateSampleMask(uint32 mask) final; - virtual void do_setStateBlend(State::BlendFunction blendFunction) final; - virtual void do_setStateColorWriteMask(uint32 mask) final; - virtual void do_setStateBlendFactor(const Batch& batch, size_t paramOffset) final; - virtual void do_setStateScissorRect(const Batch& batch, size_t paramOffset) final; - - virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; - virtual GLuint getTextureID(const TexturePointer& texture) final; - virtual GLuint getBufferID(const Buffer& buffer) = 0; - virtual GLuint getQueryID(const QueryPointer& query) = 0; - - virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; - virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; - virtual GLTexture* syncGPUObject(const TexturePointer& texture); - virtual GLQuery* syncGPUObject(const Query& query) = 0; - //virtual bool isTextureReady(const TexturePointer& texture); - - virtual void releaseBuffer(GLuint id, Size size) const; - virtual void releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const; - virtual void releaseTexture(GLuint id, Size size) const; - virtual void releaseFramebuffer(GLuint id) const; - virtual void releaseShader(GLuint id) const; - virtual void releaseProgram(GLuint id) const; - virtual void releaseQuery(GLuint id) const; - virtual void queueLambda(const std::function lambda) const; - - bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } - -protected: - - void recycle() const override; - - static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass { false }; - int32_t _uboAlignment { 0 }; - int _currentDraw { -1 }; - - std::list profileRanges; - mutable Mutex _trashMutex; - mutable std::list> _buffersTrash; - mutable std::list> _texturesTrash; - mutable std::list> _externalTexturesTrash; - mutable std::list _framebuffersTrash; - mutable std::list _shadersTrash; - mutable std::list _programsTrash; - mutable std::list _queriesTrash; - mutable std::list> _lambdaQueue; - - void renderPassTransfer(const Batch& batch); - void renderPassDraw(const Batch& batch); - -#ifdef GPU_STEREO_DRAWCALL_DOUBLED - void setupStereoSide(int side); -#endif - - virtual void initInput() final; - virtual void killInput() final; - virtual void syncInputStateCache() final; - virtual void resetInputStage(); - virtual void updateInput() = 0; - - struct InputStageState { - bool _invalidFormat { true }; - bool _hadColorAttribute{ true }; - Stream::FormatPointer _format; - std::string _formatKey; - - typedef std::bitset ActivationCache; - ActivationCache _attributeActivation { 0 }; - - typedef std::bitset BuffersState; - - BuffersState _invalidBuffers{ 0 }; - BuffersState _attribBindingBuffers{ 0 }; - - Buffers _buffers; - Offsets _bufferOffsets; - Offsets _bufferStrides; - std::vector _bufferVBOs; - - glm::vec4 _colorAttribute{ 0.0f }; - - BufferPointer _indexBuffer; - Offset _indexBufferOffset { 0 }; - Type _indexBufferType { UINT32 }; - - BufferPointer _indirectBuffer; - Offset _indirectBufferOffset{ 0 }; - Offset _indirectBufferStride{ 0 }; - - GLuint _defaultVAO { 0 }; - - InputStageState() : - _invalidFormat(true), - _format(0), - _formatKey(), - _attributeActivation(0), - _buffers(_invalidBuffers.size(), BufferPointer(0)), - _bufferOffsets(_invalidBuffers.size(), 0), - _bufferStrides(_invalidBuffers.size(), 0), - _bufferVBOs(_invalidBuffers.size(), 0) {} - } _input; - - virtual void initTransform() = 0; - void killTransform(); - // Synchronize the state cache of this Backend with the actual real state of the GL Context - void syncTransformStateCache(); - virtual void updateTransform(const Batch& batch) = 0; - virtual void resetTransformStage(); - - // Allows for correction of the camera pose to account for changes - // between the time when a was recorded and the time(s) when it is - // executed - struct CameraCorrection { - Mat4 correction; - Mat4 correctionInverse; - }; - - struct TransformStageState { -#ifdef GPU_STEREO_CAMERA_BUFFER - struct Cameras { - TransformCamera _cams[2]; - - Cameras() {}; - Cameras(const TransformCamera& cam) { memcpy(_cams, &cam, sizeof(TransformCamera)); }; - Cameras(const TransformCamera& camL, const TransformCamera& camR) { memcpy(_cams, &camL, sizeof(TransformCamera)); memcpy(_cams + 1, &camR, sizeof(TransformCamera)); }; - }; - - using CameraBufferElement = Cameras; -#else - using CameraBufferElement = TransformCamera; -#endif - using TransformCameras = std::vector; - - TransformCamera _camera; - TransformCameras _cameras; - - mutable std::map _drawCallInfoOffsets; - - GLuint _objectBuffer { 0 }; - GLuint _cameraBuffer { 0 }; - GLuint _drawCallInfoBuffer { 0 }; - GLuint _objectBufferTexture { 0 }; - size_t _cameraUboSize { 0 }; - bool _viewIsCamera{ false }; - bool _skybox { false }; - Transform _view; - CameraCorrection _correction; - bool _viewCorrectionEnabled{ true }; - - - Mat4 _projection; - Vec4i _viewport { 0, 0, 1, 1 }; - Vec2 _depthRange { 0.0f, 1.0f }; - bool _invalidView { false }; - bool _invalidProj { false }; - bool _invalidViewport { false }; - - bool _enabledDrawcallInfoBuffer{ false }; - - using Pair = std::pair; - using List = std::list; - List _cameraOffsets; - mutable List::const_iterator _camerasItr; - mutable size_t _currentCameraOffset{ INVALID_OFFSET }; - - void preUpdate(size_t commandIndex, const StereoState& stereo); - void update(size_t commandIndex, const StereoState& stereo) const; - void bindCurrentCamera(int stereoSide) const; - } _transform; - - virtual void transferTransformState(const Batch& batch) const = 0; - - struct UniformStageState { - std::array _buffers; - //Buffers _buffers { }; - } _uniform; - - void releaseUniformBuffer(uint32_t slot); - void resetUniformStage(); - - // update resource cache and do the gl bind/unbind call with the current gpu::Buffer cached at slot s - // This is using different gl object depending on the gl version - virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0; - virtual void releaseResourceBuffer(uint32_t slot) = 0; - - // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s - void releaseResourceTexture(uint32_t slot); - - void resetResourceStage(); - - struct ResourceStageState { - std::array _buffers; - std::array _textures; - //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; - int findEmptyTextureSlot() const; - } _resource; - - size_t _commandIndex{ 0 }; - - // Standard update pipeline check that the current Program and current State or good to go for a - void updatePipeline(); - // Force to reset all the state fields indicated by the 'toBeReset" signature - void resetPipelineState(State::Signature toBeReset); - // Synchronize the state cache of this Backend with the actual real state of the GL Context - void syncPipelineStateCache(); - void resetPipelineStage(); - - struct PipelineStageState { - PipelinePointer _pipeline; - - GLuint _program { 0 }; - GLint _cameraCorrectionLocation { -1 }; - GLShader* _programShader { nullptr }; - bool _invalidProgram { false }; - - BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; - BufferView _cameraCorrectionBufferIdentity { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; - - State::Data _stateCache{ State::DEFAULT }; - State::Signature _stateSignatureCache { 0 }; - - GLState* _state { nullptr }; - bool _invalidState { false }; - - PipelineStageState() { - _cameraCorrectionBuffer.edit() = CameraCorrection(); - _cameraCorrectionBufferIdentity.edit() = CameraCorrection(); - _cameraCorrectionBufferIdentity._buffer->flush(); - } - } _pipeline; - - // Backend dependant compilation of the shader - virtual GLShader* compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler); - virtual GLShader* compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler); - virtual std::string getBackendShaderHeader() const; - virtual void makeProgramBindings(ShaderObject& shaderObject); - class ElementResource { - public: - gpu::Element _element; - uint16 _resource; - ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} - }; - ElementResource getFormatFromGLUniform(GLenum gltype); - static const GLint UNUSED_SLOT {-1}; - static bool isUnusedSlot(GLint binding) { return (binding == UNUSED_SLOT); } - virtual int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers); - virtual int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers); - virtual int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& resourceBuffers) = 0; - virtual int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs); - virtual int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs); - - - // Synchronize the state cache of this Backend with the actual real state of the GL Context - void syncOutputStateCache(); - void resetOutputStage(); - - struct OutputStageState { - FramebufferPointer _framebuffer { nullptr }; - GLuint _drawFBO { 0 }; - } _output; - - void resetQueryStage(); - struct QueryStageState { - uint32_t _rangeQueryDepth { 0 }; - } _queryStage; - - void resetStages(); - - struct TextureManagementStageState { - bool _sparseCapable { false }; - } _textureManagement; - virtual void initTextureManagementStage() {} - - typedef void (GLBackend::*CommandCall)(const Batch&, size_t); - static CommandCall _commandCalls[Batch::NUM_COMMANDS]; - friend class GLState; - friend class GLTexture; - friend class GLShader; -}; - -} } - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp deleted file mode 100644 index 45c0de8ed7..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// -// GLBackendTexture.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 1/19/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLShared.h" -#include "GLFramebuffer.h" - -#include - -using namespace gpu; -using namespace gpu::gl; - -void GLBackend::syncOutputStateCache() { - GLint currentFBO; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); - - _output._drawFBO = currentFBO; - _output._framebuffer.reset(); -} - -void GLBackend::resetOutputStage() { - if (_output._framebuffer) { - _output._framebuffer.reset(); - _output._drawFBO = 0; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - glEnable(GL_FRAMEBUFFER_SRGB_EXT); -} - -void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { - auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); - if (_output._framebuffer != framebuffer) { - auto newFBO = getFramebufferID(framebuffer); - if (_output._drawFBO != newFBO) { - _output._drawFBO = newFBO; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newFBO); - } - _output._framebuffer = framebuffer; - } -} - -void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { - if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) { - qWarning("Clear without scissor in stereo mode"); - } - - uint32 masks = batch._params[paramOffset + 7]._uint; - Vec4 color; - color.x = batch._params[paramOffset + 6]._float; - color.y = batch._params[paramOffset + 5]._float; - color.z = batch._params[paramOffset + 4]._float; - color.w = batch._params[paramOffset + 3]._float; - float depth = batch._params[paramOffset + 2]._float; - int stencil = batch._params[paramOffset + 1]._int; - int useScissor = batch._params[paramOffset + 0]._int; - - GLuint glmask = 0; - bool restoreStencilMask = false; - uint8_t cacheStencilMask = 0xFF; - if (masks & Framebuffer::BUFFER_STENCIL) { - glClearStencil(stencil); - glmask |= GL_STENCIL_BUFFER_BIT; - cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront(); - if (cacheStencilMask != 0xFF) { - restoreStencilMask = true; - glStencilMask(0xFF); - } - } - - bool restoreDepthMask = false; - if (masks & Framebuffer::BUFFER_DEPTH) { - glClearDepthf(depth); - glmask |= GL_DEPTH_BUFFER_BIT; - - bool cacheDepthMask = _pipeline._stateCache.depthTest.getWriteMask(); - if (!cacheDepthMask) { - restoreDepthMask = true; - glDepthMask(GL_TRUE); - } - } - - std::vector drawBuffers; - if (masks & Framebuffer::BUFFER_COLORS) { - if (_output._framebuffer) { - for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) { - if (masks & (1 << i)) { - drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i); - } - } - - if (!drawBuffers.empty()) { - glDrawBuffers((GLsizei)drawBuffers.size(), drawBuffers.data()); - glClearColor(color.x, color.y, color.z, color.w); - glmask |= GL_COLOR_BUFFER_BIT; - - (void) CHECK_GL_ERROR(); - } - } else { - glClearColor(color.x, color.y, color.z, color.w); - glmask |= GL_COLOR_BUFFER_BIT; - } - - // Force the color mask cache to WRITE_ALL if not the case - do_setStateColorWriteMask(State::ColorMask::WRITE_ALL); - } - - // Apply scissor if needed and if not already on - bool doEnableScissor = (useScissor && (!_pipeline._stateCache.scissorEnable)); - if (doEnableScissor) { - glEnable(GL_SCISSOR_TEST); - } - - // Clear! - glClear(glmask); - - // Restore scissor if needed - if (doEnableScissor) { - glDisable(GL_SCISSOR_TEST); - } - - // Restore Stencil write mask - if (restoreStencilMask) { - glStencilMask(cacheStencilMask); - } - - // Restore write mask meaning turn back off - if (restoreDepthMask) { - glDepthMask(GL_FALSE); - } - - // Restore the color draw buffers only if a frmaebuffer is bound - if (_output._framebuffer && !drawBuffers.empty()) { - auto glFramebuffer = syncGPUObject(*_output._framebuffer); - if (glFramebuffer) { - glDrawBuffers((GLsizei)glFramebuffer->_colorBuffers.size(), glFramebuffer->_colorBuffers.data()); - } - } - - (void) CHECK_GL_ERROR(); -} - -void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) { - auto readFBO = getFramebufferID(srcFramebuffer); - if (srcFramebuffer && readFBO) { - if ((srcFramebuffer->getWidth() < (region.x + region.z)) || (srcFramebuffer->getHeight() < (region.y + region.w))) { - qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; - return; - } - } - - if ((destImage.width() < region.z) || (destImage.height() < region.w)) { - qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; - return; - } - - GLenum format = GL_RGBA; - if (destImage.format() != QImage::Format_ARGB32) { - qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; - return; - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, getFramebufferID(srcFramebuffer)); - glReadPixels(region.x, region.y, region.z, region.w, format, GL_UNSIGNED_BYTE, destImage.bits()); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - (void) CHECK_GL_ERROR(); -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp deleted file mode 100644 index 43c8f8f465..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// -// GLBackendQuery.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 7/7/2015. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLQuery.h" -#include "GLShared.h" - -using namespace gpu; -using namespace gpu::gl; - -// Eventually, we want to test with TIME_ELAPSED instead of TIMESTAMP -#ifdef Q_OS_MAC -//const uint32_t MAX_RANGE_QUERY_DEPTH = 1; -static bool timeElapsed = true; -#else -//const uint32_t MAX_RANGE_QUERY_DEPTH = 10000; -static bool timeElapsed = false; -#endif - -void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) - auto query = batch._queries.get(batch._params[paramOffset]._uint); - GLQuery* glquery = syncGPUObject(*query); - if (glquery) { - //glGetInteger64v(GL_TIMESTAMP_EXT, (GLint64*)&glquery->_batchElapsedTime); - glquery->_batchElapsedTime = 1; - if (timeElapsed) { - glBeginQuery(GL_TIME_ELAPSED_EXT, glquery->_endqo); - } else { - if (glQueryCounterEXT != NULL) { - glQueryCounterEXT(glquery->_beginqo, GL_TIMESTAMP_EXT); - } - } - glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth; - (void)CHECK_GL_ERROR(); - } -#endif -} - -void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) - auto query = batch._queries.get(batch._params[paramOffset]._uint); - GLQuery* glquery = syncGPUObject(*query); - if (glquery) { - if (timeElapsed) { - glEndQuery(GL_TIME_ELAPSED_EXT); - } else { - if (glQueryCounterEXT != NULL) { - glQueryCounterEXT(glquery->_endqo, GL_TIMESTAMP_EXT); - } - } - - --_queryStage._rangeQueryDepth; - GLint64 now; - //glGetInteger64v(GL_TIMESTAMP_EXT, &now); - //glquery->_batchElapsedTime = now - glquery->_batchElapsedTime; - now = 1; - glquery->_batchElapsedTime = 1; - - PROFILE_RANGE_END(render_gpu_gl, glquery->_profileRangeId); - - (void)CHECK_GL_ERROR(); - } -#endif -} - -void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) - auto query = batch._queries.get(batch._params[paramOffset]._uint); - if (glGetQueryObjectui64vEXT == NULL) - return; - GLQuery* glquery = syncGPUObject(*query); - if (glquery) { - glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); - if (glquery->_result == GL_TRUE) { - if (timeElapsed) { - glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT, &glquery->_result); - } else { - GLuint64 start, end; - glGetQueryObjectui64vEXT(glquery->_beginqo, GL_QUERY_RESULT, &start); - glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT, &end); - glquery->_result = end - start; - } - query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); - } - (void)CHECK_GL_ERROR(); - } -#endif -} - -void GLBackend::resetQueryStage() { -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendShader.cpp deleted file mode 100644 index 677bba97ca..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendShader.cpp +++ /dev/null @@ -1,583 +0,0 @@ -// -// Created by Gabriel Calero & Cristian Duarte on 2017/12/28 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLShader.h" -#include - -using namespace gpu; -using namespace gpu::gl; - -// GLSL version -std::string GLBackend::getBackendShaderHeader() const { - return std::string("#version 310 es"); -} - -// Shader domain -static const size_t NUM_SHADER_DOMAINS = 2; - -// GL Shader type enums -// Must match the order of type specified in gpu::Shader::Type -static const std::array SHADER_DOMAINS{ { - GL_VERTEX_SHADER, - GL_FRAGMENT_SHADER, - // GL_GEOMETRY_SHADER, -} }; - -// Domain specific defines -// Must match the order of type specified in gpu::Shader::Type -static const std::array DOMAIN_DEFINES{ { - "#define GPU_VERTEX_SHADER", - "#define GPU_PIXEL_SHADER", - // "#define GPU_GEOMETRY_SHADER", -} }; - -// Stereo specific defines -static const std::string stereoVersion{ -#ifdef GPU_STEREO_DRAWCALL_INSTANCED - "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN" -#endif -#ifdef GPU_STEREO_DRAWCALL_DOUBLED -#ifdef GPU_STEREO_CAMERA_BUFFER - "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED" -#else - "#define GPU_TRANSFORM_IS_STEREO" -#endif -#endif -}; - -// Versions specific of the shader -static const std::array VERSION_DEFINES { { - "", - stereoVersion -} }; - -GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler) { - // Any GLSLprogram ? normally yes... - const std::string& shaderSource = shader.getSource().getCode(); - GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; - GLShader::ShaderObjects shaderObjects; - Shader::CompilationLogs compilationLogs(GLShader::NumVersions); - - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& shaderObject = shaderObjects[version]; - - std::string shaderDefines = getBackendShaderHeader() + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version] - + "\n#extension GL_EXT_texture_buffer : enable" - + "\nprecision lowp float; // check precision 2" - + "\nprecision lowp samplerBuffer;" - + "\nprecision lowp sampler2DShadow;"; - if (handler) { - bool retest = true; - std::string currentSrc = shaderSource; - // When a Handler is specified, we can try multiple times to build the shader and let the handler change the source if the compilation fails. - // The retest bool is set to false as soon as the compilation succeed to wexit the while loop. - // The handler tells us if we should retry or not while returning a modified version of the source. - while (retest) { - bool result = ::gl::compileShader(shaderDomain, currentSrc, shaderDefines, shaderObject.glshader, compilationLogs[version].message); - compilationLogs[version].compiled = result; - if (!result) { - std::string newSrc; - retest = handler(shader, currentSrc, compilationLogs[version], newSrc); - currentSrc = newSrc; - } else { - retest = false; - } - } - } else { - compilationLogs[version].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, compilationLogs[version].message); - } - - if (!compilationLogs[version].compiled) { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[version].message.c_str(); - shader.setCompilationLogs(compilationLogs); - return nullptr; - } - } - // Compilation feedback - shader.setCompilationLogs(compilationLogs); - - // So far so good, the shader is created successfully - GLShader* object = new GLShader(this->shared_from_this()); - object->_shaderObjects = shaderObjects; - - return object; -} - -GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler) { - if (!program.isProgram()) { - return nullptr; - } - - GLShader::ShaderObjects programObjects; - - Shader::CompilationLogs compilationLogs(GLShader::NumVersions); - - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& programObject = programObjects[version]; - - // Let's go through every shaders and make sure they are ready to go - std::vector< GLuint > shaderGLObjects; - for (auto subShader : program.getShaders()) { - auto object = GLShader::sync((*this), *subShader, handler); - if (object) { - shaderGLObjects.push_back(object->_shaderObjects[version].glshader); - } else { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - One of the shaders of the program is not compiled?"; - program.setCompilationLogs(compilationLogs); - return nullptr; - } - } - - GLuint glprogram = ::gl::compileProgram(shaderGLObjects, compilationLogs[version].message, compilationLogs[version].binary); - if (glprogram == 0) { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << compilationLogs[version].message.c_str(); - program.setCompilationLogs(compilationLogs); - return nullptr; - } - - programObject.glprogram = glprogram; - - makeProgramBindings(programObject); - } - // Compilation feedback - program.setCompilationLogs(compilationLogs); - - // So far so good, the program versions have all been created successfully - GLShader* object = new GLShader(this->shared_from_this()); - object->_shaderObjects = programObjects; - - return object; -} - -GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) { - switch (gltype) { - case GL_FLOAT: - return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC2: - return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC3: - return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC4: - return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - - case GL_INT: - return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC2: - return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC3: - return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC4: - return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); - - case GL_UNSIGNED_INT: - return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC2: - return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC3: - return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC4: - return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); - - case GL_BOOL: - return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC2: - return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC3: - return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC4: - return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); - - case GL_FLOAT_MAT2: - return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT3: - return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT4: - return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - - //{GL_FLOAT_MAT2x3 mat2x3}, - //{GL_FLOAT_MAT2x4 mat2x4}, - //{GL_FLOAT_MAT3x2 mat3x2}, - //{GL_FLOAT_MAT3x4 mat3x4}, - //{GL_FLOAT_MAT4x2 mat4x2}, - //{GL_FLOAT_MAT4x3 mat4x3}, - //{GL_DOUBLE_MAT2 dmat2}, - //{GL_DOUBLE_MAT3 dmat3}, - //{GL_DOUBLE_MAT4 dmat4}, - //{GL_DOUBLE_MAT2x3 dmat2x3}, - //{GL_DOUBLE_MAT2x4 dmat2x4}, - //{GL_DOUBLE_MAT3x2 dmat3x2}, - //{GL_DOUBLE_MAT3x4 dmat3x4}, - //{GL_DOUBLE_MAT4x2 dmat4x2}, - //{GL_DOUBLE_MAT4x3 dmat4x3}, - - case GL_SAMPLER_2D: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); - - case GL_SAMPLER_3D: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); - case GL_SAMPLER_CUBE: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_SAMPLER_2D_MULTISAMPLE: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_SAMPLER_2D_ARRAY: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - - case GL_SAMPLER_2D_SHADOW: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); - case GL_SAMPLER_CUBE_SHADOW: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); - - case GL_SAMPLER_2D_ARRAY_SHADOW: - return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); - - // {GL_SAMPLER_1D_SHADOW sampler1DShadow}, - // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, - - case GL_SAMPLER_BUFFER: - return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER); - - // {GL_SAMPLER_2D_RECT sampler2DRect}, - // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, - - case GL_INT_SAMPLER_2D: - return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_2D_MULTISAMPLE: - return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_3D: - return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); - case GL_INT_SAMPLER_CUBE: - return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_INT_SAMPLER_2D_ARRAY: - return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - - // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, - // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, - - case GL_UNSIGNED_INT_SAMPLER_2D: - return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: - return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_3D: - return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); - case GL_UNSIGNED_INT_SAMPLER_CUBE: - return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: - return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - //{GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, - //{GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, - - //{GL_IMAGE_1D image1D}, - //{GL_IMAGE_2D image2D}, - //{GL_IMAGE_3D image3D}, - //{GL_IMAGE_2D_RECT image2DRect}, - //{GL_IMAGE_CUBE imageCube}, - //{GL_IMAGE_BUFFER imageBuffer}, - //{GL_IMAGE_1D_ARRAY image1DArray}, - //{GL_IMAGE_2D_ARRAY image2DArray}, - //{GL_IMAGE_2D_MULTISAMPLE image2DMS}, - //{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, - //{GL_INT_IMAGE_1D iimage1D}, - //{GL_INT_IMAGE_2D iimage2D}, - //{GL_INT_IMAGE_3D iimage3D}, - //{GL_INT_IMAGE_2D_RECT iimage2DRect}, - //{GL_INT_IMAGE_CUBE iimageCube}, - //{GL_INT_IMAGE_BUFFER iimageBuffer}, - //{GL_INT_IMAGE_1D_ARRAY iimage1DArray}, - //{GL_INT_IMAGE_2D_ARRAY iimage2DArray}, - //{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, - //{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, - //{GL_UNSIGNED_INT_IMAGE_1D uimage1D}, - //{GL_UNSIGNED_INT_IMAGE_2D uimage2D}, - //{GL_UNSIGNED_INT_IMAGE_3D uimage3D}, - //{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, - //{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot - - //{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, - //{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, - //{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, - //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, - //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, - //{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} -#if 0 - case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); - case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); - case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); -#endif - - default: - return ElementResource(Element(), Resource::BUFFER); - } -}; - -int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { - GLint uniformsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); - - for (int i = 0; i < uniformsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - GLint location = glGetUniformLocation(glprogram, name); - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - // Let's make sure the name doesn't contains an array element - std::string sname(name); - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - if (elementResource._resource == Resource::BUFFER) { - uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); - } else { - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - for (auto i = 0; i < size; i++) { - // If we are working with an array of textures, reserve for each elemet - glProgramUniform1i(glprogram, location + i, binding + i); - } - } - } - - textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } - } - } - - return uniformsCount; -} - -int GLBackend::makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { - GLint buffersCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumUniformBufferSlots = 0; - glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); - std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); - - struct UniformBlockInfo { - using Vector = std::vector; - const GLuint index{ 0 }; - const std::string name; - GLint binding{ -1 }; - GLint size{ 0 }; - - static std::string getName(GLuint glprogram, GLuint i) { - static const GLint NAME_LENGTH = 256; - GLint length = 0; - GLchar nameBuffer[NAME_LENGTH]; - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); - glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, nameBuffer); - return std::string(nameBuffer); - } - - UniformBlockInfo(GLuint glprogram, GLuint i) : index(i), name(getName(glprogram, i)) { - glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_BINDING, &binding); - glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - } - }; - - UniformBlockInfo::Vector uniformBlocks; - uniformBlocks.reserve(buffersCount); - for (int i = 0; i < buffersCount; i++) { - uniformBlocks.push_back(UniformBlockInfo(glprogram, i)); - } - - for (auto& info : uniformBlocks) { - auto requestedBinding = slotBindings.find(info.name); - if (requestedBinding != slotBindings.end()) { - info.binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, info.index, info.binding); - uniformBufferSlotMap[info.binding] = info.index; - } - } - - for (auto& info : uniformBlocks) { - if (slotBindings.count(info.name)) { - continue; - } - - // If the binding is 0, or the binding maps to an already used binding - if (info.binding == 0 || !isUnusedSlot(uniformBufferSlotMap[info.binding])) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), GLBackend::isUnusedSlot); - if (slotIt != uniformBufferSlotMap.end()) { - info.binding = slotIt - uniformBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, info.index, info.binding); - } else { - // This should neve happen, an active ubo cannot find an available slot among the max available?! - info.binding = -1; - } - } - - uniformBufferSlotMap[info.binding] = info.index; - } - - for (auto& info : uniformBlocks) { - static const Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); - buffers.insert(Shader::Slot(info.name, info.binding, element, Resource::BUFFER, info.size)); - } - return buffersCount; -} - -int GLBackend::makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { - GLint inputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - GLint binding = glGetAttribLocation(glprogram, name); - - auto elementResource = getFormatFromGLUniform(type); - inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); - } - - return inputsCount; -} - -int GLBackend::makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { - /* GLint outputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - auto element = getFormatFromGLUniform(type); - outputs.insert(Shader::Slot(name, i, element)); - } - */ - return 0; //inputsCount; -} - -void GLBackend::makeProgramBindings(ShaderObject& shaderObject) { - if (!shaderObject.glprogram) { - return; - } - GLuint glprogram = shaderObject.glprogram; - GLint loc = -1; - - //Check for gpu specific attribute slotBindings - loc = glGetAttribLocation(glprogram, "inPosition"); - if (loc >= 0 && loc != gpu::Stream::POSITION) { - glBindAttribLocation(glprogram, gpu::Stream::POSITION, "inPosition"); - } - - loc = glGetAttribLocation(glprogram, "inNormal"); - if (loc >= 0 && loc != gpu::Stream::NORMAL) { - glBindAttribLocation(glprogram, gpu::Stream::NORMAL, "inNormal"); - } - - loc = glGetAttribLocation(glprogram, "inColor"); - if (loc >= 0 && loc != gpu::Stream::COLOR) { - glBindAttribLocation(glprogram, gpu::Stream::COLOR, "inColor"); - } - - loc = glGetAttribLocation(glprogram, "inTexCoord0"); - if (loc >= 0 && loc != gpu::Stream::TEXCOORD) { - glBindAttribLocation(glprogram, gpu::Stream::TEXCOORD, "inTexCoord0"); - } - - loc = glGetAttribLocation(glprogram, "inTangent"); - if (loc >= 0 && loc != gpu::Stream::TANGENT) { - glBindAttribLocation(glprogram, gpu::Stream::TANGENT, "inTangent"); - } - - char attribName[] = "inTexCoordn"; - for (auto i = 0; i < 4; i++) { - auto streamId = gpu::Stream::TEXCOORD1 + i; - - attribName[strlen(attribName) - 1] = '1' + i; - loc = glGetAttribLocation(glprogram, attribName); - if (loc >= 0 && loc != streamId) { - glBindAttribLocation(glprogram, streamId, attribName); - } - } - - loc = glGetAttribLocation(glprogram, "inSkinClusterIndex"); - if (loc >= 0 && loc != gpu::Stream::SKIN_CLUSTER_INDEX) { - glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_INDEX, "inSkinClusterIndex"); - } - - loc = glGetAttribLocation(glprogram, "inSkinClusterWeight"); - if (loc >= 0 && loc != gpu::Stream::SKIN_CLUSTER_WEIGHT) { - glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_WEIGHT, "inSkinClusterWeight"); - } - - loc = glGetAttribLocation(glprogram, "_drawCallInfo"); - if (loc >= 0 && loc != gpu::Stream::DRAW_CALL_INFO) { - glBindAttribLocation(glprogram, gpu::Stream::DRAW_CALL_INFO, "_drawCallInfo"); - } - - // Link again to take into account the assigned attrib location - glLinkProgram(glprogram); - - GLint linked = 0; - glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); - if (!linked) { - qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; - } -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp deleted file mode 100644 index 4a5c772b8b..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp +++ /dev/null @@ -1,334 +0,0 @@ -// -// GLBackendState.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 3/22/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLState.h" - -#include - -using namespace gpu; -using namespace gpu::gl; - -void GLBackend::resetPipelineState(State::Signature nextSignature) { - auto currentNotSignature = ~_pipeline._stateSignatureCache; - auto nextNotSignature = ~nextSignature; - auto fieldsToBeReset = currentNotSignature ^ (currentNotSignature | nextNotSignature); - if (fieldsToBeReset.any()) { - for (auto i = 0; i < State::NUM_FIELDS; i++) { - if (fieldsToBeReset[i]) { - GLState::_resetStateCommands[i]->run(this); - _pipeline._stateSignatureCache.reset(i); - } - } - } -} - -void GLBackend::syncPipelineStateCache() { - State::Data state; - - //glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); - qDebug() << "TODO: GLBackendState.cpp:syncPipelineStateCache GL_TEXTURE_CUBE_MAP_SEAMLESS"; - - // Point size is always on - // FIXME CORE - //glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); - //glEnable(GL_PROGRAM_POINT_SIZE_EXT); - qDebug() << "TODO: GLBackendState.cpp:syncPipelineStateCache GL_PROGRAM_POINT_SIZE_EXT"; - - //glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); - qDebug() << "TODO: GLBackendState.cpp:syncPipelineStateCache GL_VERTEX_PROGRAM_POINT_SIZE"; - - // Default line width accross the board - glLineWidth(1.0f); - - getCurrentGLState(state); - State::Signature signature = State::evalSignature(state); - - _pipeline._stateCache = state; - _pipeline._stateSignatureCache = signature; -} - - -void GLBackend::do_setStateFillMode(int32 mode) { - if (_pipeline._stateCache.fillMode != mode) { - //static GLenum GL_FILL_MODES[] = { /*GL_POINT, GL_LINE, GL_FILL*/ }; - //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); - qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode GL_POINT"; - qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode GL_LINE"; - qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode GL_FILL"; - qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode glPolygonMode"; - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.fillMode = State::FillMode(mode); - } -} - -void GLBackend::do_setStateCullMode(int32 mode) { - if (_pipeline._stateCache.cullMode != mode) { - static GLenum GL_CULL_MODES[] = { GL_FRONT_AND_BACK, GL_FRONT, GL_BACK }; - if (mode == State::CULL_NONE) { - glDisable(GL_CULL_FACE); - glCullFace(GL_FRONT_AND_BACK); - } else { - glEnable(GL_CULL_FACE); - glCullFace(GL_CULL_MODES[mode]); - } - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.cullMode = State::CullMode(mode); - } -} - -void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { - if (_pipeline._stateCache.frontFaceClockwise != isClockwise) { - static GLenum GL_FRONT_FACES[] = { GL_CCW, GL_CW }; - glFrontFace(GL_FRONT_FACES[isClockwise]); - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.frontFaceClockwise = isClockwise; - } -} - -void GLBackend::do_setStateDepthClampEnable(bool enable) { - if (_pipeline._stateCache.depthClampEnable != enable) { - //if (enable) { - // glEnable(GL_DEPTH_CLAMP); - //} else { - // glDisable(GL_DEPTH_CLAMP); - //} - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.depthClampEnable = enable; - } -} - -void GLBackend::do_setStateScissorEnable(bool enable) { - if (_pipeline._stateCache.scissorEnable != enable) { - if (enable) { - glEnable(GL_SCISSOR_TEST); - } else { - glDisable(GL_SCISSOR_TEST); - } - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.scissorEnable = enable; - } -} - -void GLBackend::do_setStateMultisampleEnable(bool enable) { - if (_pipeline._stateCache.multisampleEnable != enable) { -#if !defined(USE_GLES) - if (enable) { - glEnable(GL_MULTISAMPLE); - } else { - glDisable(GL_MULTISAMPLE); - } - (void)CHECK_GL_ERROR(); -#endif - - _pipeline._stateCache.multisampleEnable = enable; - } -} - -void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { - if (_pipeline._stateCache.antialisedLineEnable != enable) { -#if !defined(USE_GLES) - if (enable) { - glEnable(GL_LINE_SMOOTH); - } else { - glDisable(GL_LINE_SMOOTH); - } - (void)CHECK_GL_ERROR(); -#endif - - _pipeline._stateCache.antialisedLineEnable = enable; - } -} - -void GLBackend::do_setStateDepthBias(Vec2 bias) { - if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { - if ((bias.x != 0.0f) || (bias.y != 0.0f)) { - glEnable(GL_POLYGON_OFFSET_FILL); -#if !defined(USE_GLES) - glEnable(GL_POLYGON_OFFSET_LINE); - glEnable(GL_POLYGON_OFFSET_POINT); -#endif - glPolygonOffset(bias.x, bias.y); - } else { - glDisable(GL_POLYGON_OFFSET_FILL); -#if !defined(USE_GLES) - glDisable(GL_POLYGON_OFFSET_LINE); - glDisable(GL_POLYGON_OFFSET_POINT); -#endif - } - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.depthBias = bias.x; - _pipeline._stateCache.depthBiasSlopeScale = bias.y; - } -} - -void GLBackend::do_setStateDepthTest(State::DepthTest test) { - const auto& current = _pipeline._stateCache.depthTest; - if (current != test) { - if (test.isEnabled()) { - glEnable(GL_DEPTH_TEST); - } else { - glDisable(GL_DEPTH_TEST); - } - if (test.getWriteMask() != current.getWriteMask()) { - glDepthMask(test.getWriteMask()); - } - if (test.getFunction() != current.getFunction()) { - glDepthFunc(COMPARISON_TO_GL[test.getFunction()]); - } - if (CHECK_GL_ERROR()) { - qCDebug(gpulogging) << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") - << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") - << "Func=" << test.getFunction() - << "Raw=" << test.getRaw(); - } - _pipeline._stateCache.depthTest = test; - } -} - -void GLBackend::do_setStateStencil(State::StencilActivation activation, State::StencilTest testFront, State::StencilTest testBack) { - const auto& currentActivation = _pipeline._stateCache.stencilActivation; - const auto& currentTestFront = _pipeline._stateCache.stencilTestFront; - const auto& currentTestBack = _pipeline._stateCache.stencilTestBack; - if ((currentActivation != activation) - || (currentTestFront != testFront) - || (currentTestBack != testBack)) { - - if (activation.isEnabled()) { - glEnable(GL_STENCIL_TEST); - } else { - glDisable(GL_STENCIL_TEST); - } - - if (activation.getWriteMaskFront() != activation.getWriteMaskBack()) { - glStencilMaskSeparate(GL_FRONT, activation.getWriteMaskFront()); - glStencilMaskSeparate(GL_BACK, activation.getWriteMaskBack()); - } else { - glStencilMask(activation.getWriteMaskFront()); - } - - static GLenum STENCIL_OPS[State::NUM_STENCIL_OPS] = { - GL_KEEP, - GL_ZERO, - GL_REPLACE, - GL_INCR_WRAP, - GL_DECR_WRAP, - GL_INVERT, - GL_INCR, - GL_DECR }; - - if (testFront != testBack) { - glStencilOpSeparate(GL_FRONT, STENCIL_OPS[testFront.getFailOp()], STENCIL_OPS[testFront.getDepthFailOp()], STENCIL_OPS[testFront.getPassOp()]); - glStencilFuncSeparate(GL_FRONT, COMPARISON_TO_GL[testFront.getFunction()], testFront.getReference(), testFront.getReadMask()); - - glStencilOpSeparate(GL_BACK, STENCIL_OPS[testBack.getFailOp()], STENCIL_OPS[testBack.getDepthFailOp()], STENCIL_OPS[testBack.getPassOp()]); - glStencilFuncSeparate(GL_BACK, COMPARISON_TO_GL[testBack.getFunction()], testBack.getReference(), testBack.getReadMask()); - } else { - glStencilOp(STENCIL_OPS[testFront.getFailOp()], STENCIL_OPS[testFront.getDepthFailOp()], STENCIL_OPS[testFront.getPassOp()]); - glStencilFunc(COMPARISON_TO_GL[testFront.getFunction()], testFront.getReference(), testFront.getReadMask()); - } - - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.stencilActivation = activation; - _pipeline._stateCache.stencilTestFront = testFront; - _pipeline._stateCache.stencilTestBack = testBack; - } -} - -void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { - if (_pipeline._stateCache.alphaToCoverageEnable != enable) { - if (enable) { - glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); - } else { - glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); - } - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.alphaToCoverageEnable = enable; - } -} - -void GLBackend::do_setStateSampleMask(uint32 mask) { - if (_pipeline._stateCache.sampleMask != mask) { - if (mask == 0xFFFFFFFF) { - glDisable(GL_SAMPLE_MASK); - } else { - glEnable(GL_SAMPLE_MASK); - glSampleMaski(0, mask); - } - (void)CHECK_GL_ERROR(); - _pipeline._stateCache.sampleMask = mask; - } -} - -void GLBackend::do_setStateBlend(State::BlendFunction function) { - if (_pipeline._stateCache.blendFunction != function) { - if (function.isEnabled()) { - glEnable(GL_BLEND); - - glBlendEquationSeparate(BLEND_OPS_TO_GL[function.getOperationColor()], BLEND_OPS_TO_GL[function.getOperationAlpha()]); - (void)CHECK_GL_ERROR(); - - - glBlendFuncSeparate(BLEND_ARGS_TO_GL[function.getSourceColor()], BLEND_ARGS_TO_GL[function.getDestinationColor()], - BLEND_ARGS_TO_GL[function.getSourceAlpha()], BLEND_ARGS_TO_GL[function.getDestinationAlpha()]); - } else { - glDisable(GL_BLEND); - } - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.blendFunction = function; - } -} - -void GLBackend::do_setStateColorWriteMask(uint32 mask) { - if (_pipeline._stateCache.colorWriteMask != mask) { - glColorMask(mask & State::ColorMask::WRITE_RED, - mask & State::ColorMask::WRITE_GREEN, - mask & State::ColorMask::WRITE_BLUE, - mask & State::ColorMask::WRITE_ALPHA); - (void)CHECK_GL_ERROR(); - - _pipeline._stateCache.colorWriteMask = mask; - } -} - - -void GLBackend::do_setStateBlendFactor(const Batch& batch, size_t paramOffset) { - Vec4 factor(batch._params[paramOffset + 0]._float, - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 2]._float, - batch._params[paramOffset + 3]._float); - - glBlendColor(factor.x, factor.y, factor.z, factor.w); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::do_setStateScissorRect(const Batch& batch, size_t paramOffset) { - Vec4i rect; - memcpy(&rect, batch.readData(batch._params[paramOffset]._uint), sizeof(Vec4i)); - - if (_stereo._enable) { - rect.z /= 2; - if (_stereo._pass) { - rect.x += rect.z; - } - } - glScissor(rect.x, rect.y, rect.z, rect.w); - (void)CHECK_GL_ERROR(); -} - diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp deleted file mode 100644 index ace33766ce..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// -// -// GLBackendTexture.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 1/19/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLTexture.h" - -using namespace gpu; -using namespace gpu::gl; - - -GLuint GLBackend::getTextureID(const TexturePointer& texture) { - GLTexture* object = syncGPUObject(texture); - - if (!object) { - return 0; - } - - return object->_id; -} - -GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) { - const Texture& texture = *texturePointer; - // Special case external textures - if (TextureUsageType::EXTERNAL == texture.getUsageType()) { - Texture::ExternalUpdates updates = texture.getUpdates(); - if (!updates.empty()) { - Texture::ExternalRecycler recycler = texture.getExternalRecycler(); - Q_ASSERT(recycler); - // Discard any superfluous updates - while (updates.size() > 1) { - const auto& update = updates.front(); - // Superfluous updates will never have been read, but we want to ensure the previous - // writes to them are complete before they're written again, so return them with the - // same fences they arrived with. This can happen on any thread because no GL context - // work is involved - recycler(update.first, update.second); - updates.pop_front(); - } - - // The last texture remaining is the one we'll use to create the GLTexture - const auto& update = updates.front(); - // Check for a fence, and if it exists, inject a wait into the command stream, then destroy the fence - if (update.second) { - GLsync fence = static_cast(update.second); - glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(fence); - } - - // Create the new texture object (replaces any previous texture object) - new GLExternalTexture(shared_from_this(), texture, update.first); - } - - // Return the texture object (if any) associated with the texture, without extensive logic - // (external textures are - return Backend::getGPUObject(texture); - } - - return nullptr; -} - -void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { - TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); - if (!resourceTexture) { - return; - } - - // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = syncGPUObject(resourceTexture); - if (!object) { - return; - } - - object->generateMips(); -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLBuffer.cpp b/libraries/gpu-gles/src/gpu/gl/GLBuffer.cpp deleted file mode 100644 index 4f7d0a8632..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBuffer.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Created by Gabriel Calero & Cristian Duarte on 09/27/2016 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GLBuffer.h" -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - -GLBuffer::~GLBuffer() { - Backend::bufferCount.decrement(); - Backend::bufferGPUMemSize.update(_size, 0); - - if (_id) { - auto backend = _backend.lock(); - if (backend) { - backend->releaseBuffer(_id, _size); - } - } -} - -GLBuffer::GLBuffer(const std::weak_ptr& backend, const Buffer& buffer, GLuint id) : - GLObject(backend, buffer, id), - _size((GLuint)buffer._renderSysmem.getSize()), - _stamp(buffer._renderSysmem.getStamp()) -{ - Backend::bufferCount.increment(); - Backend::bufferGPUMemSize.update(0, _size); -} - diff --git a/libraries/gpu-gles/src/gpu/gl/GLBuffer.h b/libraries/gpu-gles/src/gpu/gl/GLBuffer.h deleted file mode 100644 index 182014e764..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLBuffer.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLBuffer_h -#define hifi_gpu_gl_GLBuffer_h - -#include "GLShared.h" -#include "GLBackend.h" - -namespace gpu { namespace gl { - -class GLBuffer : public GLObject { -public: - template - static GLBufferType* sync(GLBackend& backend, const Buffer& buffer) { - if (buffer.getSysmem().getSize() != 0) { - if (buffer._getUpdateCount == 0) { - qWarning() << "Unsynced buffer"; - } - if (buffer._getUpdateCount < buffer._applyUpdateCount) { - qWarning() << "Unsynced buffer " << buffer._getUpdateCount << " " << buffer._applyUpdateCount; - } - } - GLBufferType* object = Backend::getGPUObject(buffer); - - // Has the storage size changed? - if (!object || object->_stamp != buffer._renderSysmem.getStamp()) { - object = new GLBufferType(backend.shared_from_this(), buffer, object); - } - - if (0 != (buffer._renderPages._flags & PageManager::DIRTY)) { - object->transfer(); - } - - return object; - } - - template - static GLuint getId(GLBackend& backend, const Buffer& buffer) { - GLBuffer* bo = sync(backend, buffer); - if (bo) { - return bo->_buffer; - } else { - return 0; - } - } - - const GLuint& _buffer { _id }; - const GLuint _size; - const Stamp _stamp; - - ~GLBuffer(); - - virtual void transfer() = 0; - -protected: - GLBuffer(const std::weak_ptr& backend, const Buffer& buffer, GLuint id); -}; - -} } - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp b/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp new file mode 100644 index 0000000000..2e2c988e77 --- /dev/null +++ b/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp @@ -0,0 +1,56 @@ +// +// GLBackend.cpp +// libraries/gpu-gl-android/src/gpu/gl +// +// Created by Cristian Duarte & Gabriel Calero on 9/21/2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include + +#include + + +#include +#include +#include +#include + +#include "../gles/GLESBackend.h" + +using namespace gpu; +using namespace gpu::gl; + +static GLBackend* INSTANCE{ nullptr }; + +BackendPointer GLBackend::createBackend() { + // FIXME provide a mechanism to override the backend for testing + // Where the gpuContext is initialized and where the TRUE Backend is created and assigned + //auto version = QOpenGLContextWrapper::currentContextVersion(); + std::shared_ptr result; + + qDebug() << "Using OpenGL ES backend"; + result = std::make_shared(); + + result->initInput(); + result->initTransform(); + result->initTextureManagementStage(); + + INSTANCE = result.get(); + void* voidInstance = &(*result); + qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance)); + return result; +} + +GLBackend& getBackend() { + if (!INSTANCE) { + INSTANCE = static_cast(qApp->property(hifi::properties::gl::BACKEND).value()); + } + return *INSTANCE; +} + +bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { + return GLShader::makeProgram(getBackend(), shader, slotBindings, handler); +} diff --git a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp deleted file mode 100644 index 0bca9e86d9..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// Created by Gabriel Calero & Cristian Duarte on 09/27/2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GLFramebuffer.h" -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - -GLFramebuffer::~GLFramebuffer() { - if (_id) { - auto backend = _backend.lock(); - if (backend) { - backend->releaseFramebuffer(_id); - } - } -} - -bool GLFramebuffer::checkStatus() const { - switch (_status) { - case GL_FRAMEBUFFER_COMPLETE: - // Success ! - return true; - - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; - break; - //case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: - // qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; - // break; - //case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: - // qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; - // break; - case GL_FRAMEBUFFER_UNSUPPORTED: - qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; - break; - } - return false; -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h b/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h deleted file mode 100644 index 5a388e1965..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// Created by Gabriel Calero & Cristian Duarte on 09/27/2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLFramebuffer_h -#define hifi_gpu_gl_GLFramebuffer_h - -#include "GLShared.h" -#include "GLBackend.h" - -namespace gpu { namespace gl { - -class GLFramebuffer : public GLObject { -public: - template - static GLFramebufferType* sync(GLBackend& backend, const Framebuffer& framebuffer) { - GLFramebufferType* object = Backend::getGPUObject(framebuffer); - - bool needsUpate { false }; - if (!object || - framebuffer.getDepthStamp() != object->_depthStamp || - framebuffer.getColorStamps() != object->_colorStamps) { - needsUpate = true; - } - - // If GPU object already created and in sync - if (!needsUpate) { - return object; - } else if (framebuffer.isEmpty()) { - // NO framebuffer definition yet so let's avoid thinking - return nullptr; - } - - // need to have a gpu object? - if (!object) { - // All is green, assign the gpuobject to the Framebuffer - object = new GLFramebufferType(backend.shared_from_this(), framebuffer); - Backend::setGPUObject(framebuffer, object); - (void)CHECK_GL_ERROR(); - } - - object->update(); - return object; - } - - template - static GLuint getId(GLBackend& backend, const Framebuffer& framebuffer) { - GLFramebufferType* fbo = sync(backend, framebuffer); - if (fbo) { - return fbo->_id; - } else { - return 0; - } - } - - const GLuint& _fbo { _id }; - std::vector _colorBuffers; - Stamp _depthStamp { 0 }; - std::vector _colorStamps; - -protected: - GLenum _status { GL_FRAMEBUFFER_COMPLETE }; - virtual void update() = 0; - bool checkStatus() const; - - GLFramebuffer(const std::weak_ptr& backend, const Framebuffer& framebuffer, GLuint id) : GLObject(backend, framebuffer, id) {} - ~GLFramebuffer(); - -}; - -} } - - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLInputFormat.cpp b/libraries/gpu-gles/src/gpu/gl/GLInputFormat.cpp deleted file mode 100644 index 7f42350c3b..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLInputFormat.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by Sam Gateau on 2016/07/21 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GLInputFormat.h" -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - - -GLInputFormat::GLInputFormat() { -} - -GLInputFormat:: ~GLInputFormat() { - -} - -GLInputFormat* GLInputFormat::sync(const Stream::Format& inputFormat) { - GLInputFormat* object = Backend::getGPUObject(inputFormat); - - if (!object) { - object = new GLInputFormat(); - object->key = inputFormat.getKey(); - Backend::setGPUObject(inputFormat, object); - } - - return object; -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLInputFormat.h b/libraries/gpu-gles/src/gpu/gl/GLInputFormat.h deleted file mode 100644 index a14e3d4d91..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLInputFormat.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Created by Sam Gateau on 2016/07/21 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLInputFormat_h -#define hifi_gpu_gl_GLInputFormat_h - -#include "GLShared.h" - -namespace gpu { -namespace gl { - -class GLInputFormat : public GPUObject { - public: - static GLInputFormat* sync(const Stream::Format& inputFormat); - - GLInputFormat(); - ~GLInputFormat(); - - std::string key; -}; - -} -} - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp deleted file mode 100644 index ebf1a55232..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GLPipeline.h" - -#include "GLShader.h" -#include "GLState.h" - -using namespace gpu; -using namespace gpu::gl; - -GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { - GLPipeline* object = Backend::getGPUObject(pipeline); - - // If GPU object already created then good - if (object) { - return object; - } - - // No object allocated yet, let's see if it's worth it... - ShaderPointer shader = pipeline.getProgram(); - - // If this pipeline's shader has already failed to compile, don't try again - if (shader->compilationHasFailed()) { - return nullptr; - } - - GLShader* programObject = GLShader::sync(backend, *shader); - if (programObject == nullptr) { - shader->setCompilationHasFailed(true); - return nullptr; - } - - StatePointer state = pipeline.getState(); - GLState* stateObject = GLState::sync(*state); - if (stateObject == nullptr) { - return nullptr; - } - - // Program and state are valid, we can create the pipeline object - if (!object) { - object = new GLPipeline(); - Backend::setGPUObject(pipeline, object); - } - - // Special case for view correction matrices, any pipeline that declares the correction buffer - // uniform will automatically have it provided without any client code necessary. - // Required for stable lighting in the HMD. - object->_cameraCorrection = shader->getUniformBuffers().findLocation("cameraCorrectionBuffer"); - object->_program = programObject; - object->_state = stateObject; - - return object; -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLPipeline.h b/libraries/gpu-gles/src/gpu/gl/GLPipeline.h deleted file mode 100644 index a298f149d9..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLPipeline.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLPipeline_h -#define hifi_gpu_gl_GLPipeline_h - -#include "GLShared.h" - -namespace gpu { namespace gl { - -class GLPipeline : public GPUObject { -public: - static GLPipeline* sync(GLBackend& backend, const Pipeline& pipeline); - - GLShader* _program { nullptr }; - GLState* _state { nullptr }; - // Bit of a hack, any pipeline can need the camera correction buffer at execution time, so - // we store whether a given pipeline has declared the uniform buffer for it. - int32 _cameraCorrection { -1 }; -}; - -} } - - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLQuery.h b/libraries/gpu-gles/src/gpu/gl/GLQuery.h deleted file mode 100644 index 23b1f38621..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLQuery.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLQuery_h -#define hifi_gpu_gl_GLQuery_h - -#include "GLShared.h" -#include "GLBackend.h" - -namespace gpu { namespace gl { - -class GLQuery : public GLObject { - using Parent = gpu::gl::GLObject; -public: - template - static GLQueryType* sync(GLBackend& backend, const Query& query) { - GLQueryType* object = Backend::getGPUObject(query); - - // need to have a gpu object? - if (!object) { - // All is green, assign the gpuobject to the Query - object = new GLQueryType(backend.shared_from_this(), query); - (void)CHECK_GL_ERROR(); - Backend::setGPUObject(query, object); - } - - return object; - } - - template - static GLuint getId(GLBackend& backend, const QueryPointer& query) { - if (!query) { - return 0; - } - - GLQuery* object = sync(backend, *query); - if (!object) { - return 0; - } - - return object->_endqo; - } - - const GLuint& _endqo = { _id }; - const GLuint _beginqo = { 0 }; - GLuint64 _result { (GLuint64)-1 }; - GLuint64 _batchElapsedTime { (GLuint64) 0 }; - uint64_t _profileRangeId { 0 }; - uint32_t _rangeQueryDepth { 0 }; - -protected: - GLQuery(const std::weak_ptr& backend, const Query& query, GLuint endId, GLuint beginId) : Parent(backend, query, endId), _beginqo(beginId) {} - ~GLQuery() { - if (_id) { - GLuint ids[2] = { _endqo, _beginqo }; - glDeleteQueries(2, ids); - } - } -}; - -} } - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLShader.cpp b/libraries/gpu-gles/src/gpu/gl/GLShader.cpp deleted file mode 100644 index 010a7c479c..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLShader.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLShader.h" -#include - -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - -GLShader::GLShader(const std::weak_ptr& backend) : _backend(backend) { -} - -GLShader::~GLShader() { - for (auto& so : _shaderObjects) { - auto backend = _backend.lock(); - if (backend) { - if (so.glshader != 0) { - backend->releaseShader(so.glshader); - } - if (so.glprogram != 0) { - backend->releaseProgram(so.glprogram); - } - } - } -} - -GLShader* GLShader::sync(GLBackend& backend, const Shader& shader, const Shader::CompilationHandler& handler) { - GLShader* object = Backend::getGPUObject(shader); - - // If GPU object already created then good - if (object) { - return object; - } - // need to have a gpu object? - if (shader.isProgram()) { - GLShader* tempObject = backend.compileBackendProgram(shader, handler); - if (tempObject) { - object = tempObject; - Backend::setGPUObject(shader, object); - } - } else if (shader.isDomain()) { - GLShader* tempObject = backend.compileBackendShader(shader, handler); - if (tempObject) { - object = tempObject; - Backend::setGPUObject(shader, object); - } - } - - glFinish(); - return object; -} - -bool GLShader::makeProgram(GLBackend& backend, Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { - - // First make sure the Shader has been compiled - GLShader* object = sync(backend, shader, handler); - if (!object) { - return false; - } - - // Apply bindings to all program versions and generate list of slots from default version - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& shaderObject = object->_shaderObjects[version]; - if (shaderObject.glprogram) { - Shader::SlotSet buffers; - backend.makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); - - Shader::SlotSet uniforms; - Shader::SlotSet textures; - Shader::SlotSet samplers; - backend.makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); - - Shader::SlotSet resourceBuffers; - backend.makeResourceBufferSlots(shaderObject.glprogram, slotBindings, resourceBuffers); - - Shader::SlotSet inputs; - backend.makeInputSlots(shaderObject.glprogram, slotBindings, inputs); - - Shader::SlotSet outputs; - backend.makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); - - // Define the public slots only from the default version - if (version == 0) { - shader.defineSlots(uniforms, buffers, resourceBuffers, textures, samplers, inputs, outputs); - } // else - { - GLShader::UniformMapping mapping; - for (auto srcUniform : shader.getUniforms()) { - mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); - } - object->_uniformMappings.push_back(mapping); - } - } - } - - return true; -} - - - diff --git a/libraries/gpu-gles/src/gpu/gl/GLShader.h b/libraries/gpu-gles/src/gpu/gl/GLShader.h deleted file mode 100644 index f2a144a81c..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLShader.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLShader_h -#define hifi_gpu_gl_GLShader_h - -#include "GLShared.h" - -namespace gpu { namespace gl { - -struct ShaderObject { - GLuint glshader { 0 }; - GLuint glprogram { 0 }; - GLint transformCameraSlot { -1 }; - GLint transformObjectSlot { -1 }; -}; - -class GLShader : public GPUObject { -public: - static GLShader* sync(GLBackend& backend, const Shader& shader, const Shader::CompilationHandler& handler = nullptr); - static bool makeProgram(GLBackend& backend, Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler = nullptr); - - enum Version { - Mono = 0, - Stereo, - - NumVersions - }; - - using ShaderObject = gpu::gl::ShaderObject; - using ShaderObjects = std::array< ShaderObject, NumVersions >; - - using UniformMapping = std::map; - using UniformMappingVersions = std::vector; - - GLShader(const std::weak_ptr& backend); - ~GLShader(); - - ShaderObjects _shaderObjects; - UniformMappingVersions _uniformMappings; - - GLuint getProgram(Version version = Mono) const { - return _shaderObjects[version].glprogram; - } - - GLint getUniformLocation(GLint srcLoc, Version version = Mono) const { - // This check protect against potential invalid src location for this shader, if unknown then return -1. - const auto& mapping = _uniformMappings[version]; - auto found = mapping.find(srcLoc); - if (found == mapping.end()) { - return -1; - } - return found->second; - } - - const std::weak_ptr _backend; -}; - -} } - - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLShared.cpp b/libraries/gpu-gles/src/gpu/gl/GLShared.cpp deleted file mode 100644 index f818a221b2..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLShared.cpp +++ /dev/null @@ -1,335 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/14 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLShared.h" - -#include - -#include - -#include -#include -#include - -Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") -Q_LOGGING_CATEGORY(trace_render_gpu_gl, "trace.render.gpu.gl") -Q_LOGGING_CATEGORY(trace_render_gpu_gl_detail, "trace.render.gpu.gl.detail") - -namespace gpu { namespace gl { - -bool checkGLError(const char* name) { - GLenum error = glGetError(); - if (!error) { - return false; - } else { - switch (error) { - case GL_INVALID_ENUM: - qCWarning(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_INVALID_VALUE: - qCWarning(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; - break; - case GL_INVALID_OPERATION: - qCWarning(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; - break; - case GL_INVALID_FRAMEBUFFER_OPERATION: - qCWarning(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_OUT_OF_MEMORY: - qCWarning(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; - break; - case GL_STACK_UNDERFLOW: - qCWarning(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; - break; - case GL_STACK_OVERFLOW: - qCWarning(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; - break; - } - return true; - } -} - -bool checkGLErrorDebug(const char* name) { - // FIXME, disable in debug mode when near release - return checkGLError(name); -} - -gpu::Size getFreeDedicatedMemory() { - Size result { 0 }; - static bool nvidiaMemorySupported { false }; - static bool atiMemorySupported { false }; - if (nvidiaMemorySupported) { - - GLint nvGpuMemory { 0 }; - qDebug() << "TODO: GLShared.cpp getFreeDedicatedMemory GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX"; - //glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &nvGpuMemory); - if (GL_NO_ERROR == glGetError()) { - result = KB_TO_BYTES(nvGpuMemory); - } else { - nvidiaMemorySupported = false; - } - } else if (atiMemorySupported) { - GLint atiGpuMemory[4]; - qDebug() << "TODO: GLShared.cpp getFreeDedicatedMemory GL_TEXTURE_FREE_MEMORY_ATI"; - // not really total memory, but close enough if called early enough in the application lifecycle - //glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); - if (GL_NO_ERROR == glGetError()) { - result = KB_TO_BYTES(atiGpuMemory[0]); - } else { - atiMemorySupported = false; - } - } - return result; -} - -ComparisonFunction comparisonFuncFromGL(GLenum func) { - if (func == GL_NEVER) { - return NEVER; - } else if (func == GL_LESS) { - return LESS; - } else if (func == GL_EQUAL) { - return EQUAL; - } else if (func == GL_LEQUAL) { - return LESS_EQUAL; - } else if (func == GL_GREATER) { - return GREATER; - } else if (func == GL_NOTEQUAL) { - return NOT_EQUAL; - } else if (func == GL_GEQUAL) { - return GREATER_EQUAL; - } else if (func == GL_ALWAYS) { - return ALWAYS; - } - - return ALWAYS; -} - -State::StencilOp stencilOpFromGL(GLenum stencilOp) { - if (stencilOp == GL_KEEP) { - return State::STENCIL_OP_KEEP; - } else if (stencilOp == GL_ZERO) { - return State::STENCIL_OP_ZERO; - } else if (stencilOp == GL_REPLACE) { - return State::STENCIL_OP_REPLACE; - } else if (stencilOp == GL_INCR_WRAP) { - return State::STENCIL_OP_INCR_SAT; - } else if (stencilOp == GL_DECR_WRAP) { - return State::STENCIL_OP_DECR_SAT; - } else if (stencilOp == GL_INVERT) { - return State::STENCIL_OP_INVERT; - } else if (stencilOp == GL_INCR) { - return State::STENCIL_OP_INCR; - } else if (stencilOp == GL_DECR) { - return State::STENCIL_OP_DECR; - } - - return State::STENCIL_OP_KEEP; -} - -State::BlendOp blendOpFromGL(GLenum blendOp) { - if (blendOp == GL_FUNC_ADD) { - return State::BLEND_OP_ADD; - } else if (blendOp == GL_FUNC_SUBTRACT) { - return State::BLEND_OP_SUBTRACT; - } else if (blendOp == GL_FUNC_REVERSE_SUBTRACT) { - return State::BLEND_OP_REV_SUBTRACT; - } else if (blendOp == GL_MIN) { - return State::BLEND_OP_MIN; - } else if (blendOp == GL_MAX) { - return State::BLEND_OP_MAX; - } - - return State::BLEND_OP_ADD; -} - -State::BlendArg blendArgFromGL(GLenum blendArg) { - if (blendArg == GL_ZERO) { - return State::ZERO; - } else if (blendArg == GL_ONE) { - return State::ONE; - } else if (blendArg == GL_SRC_COLOR) { - return State::SRC_COLOR; - } else if (blendArg == GL_ONE_MINUS_SRC_COLOR) { - return State::INV_SRC_COLOR; - } else if (blendArg == GL_DST_COLOR) { - return State::DEST_COLOR; - } else if (blendArg == GL_ONE_MINUS_DST_COLOR) { - return State::INV_DEST_COLOR; - } else if (blendArg == GL_SRC_ALPHA) { - return State::SRC_ALPHA; - } else if (blendArg == GL_ONE_MINUS_SRC_ALPHA) { - return State::INV_SRC_ALPHA; - } else if (blendArg == GL_DST_ALPHA) { - return State::DEST_ALPHA; - } else if (blendArg == GL_ONE_MINUS_DST_ALPHA) { - return State::INV_DEST_ALPHA; - } else if (blendArg == GL_CONSTANT_COLOR) { - return State::FACTOR_COLOR; - } else if (blendArg == GL_ONE_MINUS_CONSTANT_COLOR) { - return State::INV_FACTOR_COLOR; - } else if (blendArg == GL_CONSTANT_ALPHA) { - return State::FACTOR_ALPHA; - } else if (blendArg == GL_ONE_MINUS_CONSTANT_ALPHA) { - return State::INV_FACTOR_ALPHA; - } - - return State::ONE; -} - -void getCurrentGLState(State::Data& state) { - { - //GLint modes[2]; - //glGetIntegerv(GL_POLYGON_MODE, modes); - //if (modes[0] == GL_FILL) { - // state.fillMode = State::FILL_FACE; - //} else { - // if (modes[0] == GL_LINE) { - // state.fillMode = State::FILL_LINE; - // } else { - // state.fillMode = State::FILL_POINT; - // } - //} - } - { - if (glIsEnabled(GL_CULL_FACE)) { - GLint mode; - glGetIntegerv(GL_CULL_FACE_MODE, &mode); - state.cullMode = (mode == GL_FRONT ? State::CULL_FRONT : State::CULL_BACK); - } else { - state.cullMode = State::CULL_NONE; - } - } - { - GLint winding; - glGetIntegerv(GL_FRONT_FACE, &winding); - state.frontFaceClockwise = (winding == GL_CW); - state.depthClampEnable = false; //glIsEnabled(GL_DEPTH_CLAMP_EXT); - state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); - state.multisampleEnable = false; //glIsEnabled(GL_MULTISAMPLE_EXT); - state.antialisedLineEnable = false; //glIsEnabled(GL_LINE_SMOOTH); - } - { - if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { - glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &state.depthBiasSlopeScale); - glGetFloatv(GL_POLYGON_OFFSET_UNITS, &state.depthBias); - } - } - { - GLboolean isEnabled = glIsEnabled(GL_DEPTH_TEST); - GLboolean writeMask; - glGetBooleanv(GL_DEPTH_WRITEMASK, &writeMask); - GLint func; - glGetIntegerv(GL_DEPTH_FUNC, &func); - - state.depthTest = State::DepthTest(isEnabled, writeMask, comparisonFuncFromGL(func)); - } - { - GLboolean isEnabled = glIsEnabled(GL_STENCIL_TEST); - - GLint frontWriteMask; - GLint frontReadMask; - GLint frontRef; - GLint frontFail; - GLint frontDepthFail; - GLint frontPass; - GLint frontFunc; - glGetIntegerv(GL_STENCIL_WRITEMASK, &frontWriteMask); - glGetIntegerv(GL_STENCIL_VALUE_MASK, &frontReadMask); - glGetIntegerv(GL_STENCIL_REF, &frontRef); - glGetIntegerv(GL_STENCIL_FAIL, &frontFail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &frontDepthFail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &frontPass); - glGetIntegerv(GL_STENCIL_FUNC, &frontFunc); - - GLint backWriteMask; - GLint backReadMask; - GLint backRef; - GLint backFail; - GLint backDepthFail; - GLint backPass; - GLint backFunc; - glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &backWriteMask); - glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &backReadMask); - glGetIntegerv(GL_STENCIL_BACK_REF, &backRef); - glGetIntegerv(GL_STENCIL_BACK_FAIL, &backFail); - glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &backDepthFail); - glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &backPass); - glGetIntegerv(GL_STENCIL_BACK_FUNC, &backFunc); - - state.stencilActivation = State::StencilActivation(isEnabled, frontWriteMask, backWriteMask); - state.stencilTestFront = State::StencilTest(frontRef, frontReadMask, comparisonFuncFromGL(frontFunc), stencilOpFromGL(frontFail), stencilOpFromGL(frontDepthFail), stencilOpFromGL(frontPass)); - state.stencilTestBack = State::StencilTest(backRef, backReadMask, comparisonFuncFromGL(backFunc), stencilOpFromGL(backFail), stencilOpFromGL(backDepthFail), stencilOpFromGL(backPass)); - } - { - GLint mask = 0xFFFFFFFF; - if (glIsEnabled(GL_SAMPLE_MASK)) { - glGetIntegerv(GL_SAMPLE_MASK, &mask); - state.sampleMask = mask; - } - state.sampleMask = mask; - } - { - state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); - } - { - GLboolean isEnabled = glIsEnabled(GL_BLEND); - GLint srcRGB; - GLint srcA; - GLint dstRGB; - GLint dstA; - glGetIntegerv(GL_BLEND_SRC_RGB, &srcRGB); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &srcA); - glGetIntegerv(GL_BLEND_DST_RGB, &dstRGB); - glGetIntegerv(GL_BLEND_DST_ALPHA, &dstA); - - GLint opRGB; - GLint opA; - glGetIntegerv(GL_BLEND_EQUATION_RGB, &opRGB); - glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &opA); - - state.blendFunction = State::BlendFunction(isEnabled, - blendArgFromGL(srcRGB), blendOpFromGL(opRGB), blendArgFromGL(dstRGB), - blendArgFromGL(srcA), blendOpFromGL(opA), blendArgFromGL(dstA)); - } - { - GLboolean mask[4]; - glGetBooleanv(GL_COLOR_WRITEMASK, mask); - state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) - | (mask[1] ? State::WRITE_GREEN : 0) - | (mask[2] ? State::WRITE_BLUE : 0) - | (mask[3] ? State::WRITE_ALPHA : 0); - } - - (void)CHECK_GL_ERROR(); -} - - -void serverWait() { - auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - assert(fence); - glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(fence); -} - -void clientWait() { - auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - assert(fence); - auto result = glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0); - while (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) { - // Minimum sleep - QThread::usleep(1); - result = glClientWaitSync(fence, 0, 0); - } - glDeleteSync(fence); -} - -} } - - -using namespace gpu; - - diff --git a/libraries/gpu-gles/src/gpu/gl/GLShared.h b/libraries/gpu-gles/src/gpu/gl/GLShared.h deleted file mode 100644 index 1341dd16fa..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLShared.h +++ /dev/null @@ -1,152 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_GLShared_h -#define hifi_gpu_GLShared_h - -#include -#include - -#include -#include -#include -#include - - -Q_DECLARE_LOGGING_CATEGORY(gpugllogging) -Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl) -Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl_detail) - -#define BUFFER_OFFSET(bytes) ((GLubyte*) nullptr + (bytes)) - -namespace gpu { namespace gl { - -// Create a fence and inject a GPU wait on the fence -void serverWait(); - -// Create a fence and synchronously wait on the fence -void clientWait(); - -gpu::Size getFreeDedicatedMemory(); -ComparisonFunction comparisonFuncFromGL(GLenum func); -State::StencilOp stencilOpFromGL(GLenum stencilOp); -State::BlendOp blendOpFromGL(GLenum blendOp); -State::BlendArg blendArgFromGL(GLenum blendArg); -void getCurrentGLState(State::Data& state); - -enum GLSyncState { - // The object is currently undergoing no processing, although it's content - // may be out of date, or it's storage may be invalid relative to the - // owning GPU object - Idle, - // The object has been queued for transfer to the GPU - Pending, - // The object has been transferred to the GPU, but is awaiting - // any post transfer operations that may need to occur on the - // primary rendering thread - Transferred, -}; - -static const GLenum BLEND_OPS_TO_GL[State::NUM_BLEND_OPS] = { - GL_FUNC_ADD, - GL_FUNC_SUBTRACT, - GL_FUNC_REVERSE_SUBTRACT, - GL_MIN, - GL_MAX -}; - -static const GLenum BLEND_ARGS_TO_GL[State::NUM_BLEND_ARGS] = { - GL_ZERO, - GL_ONE, - GL_SRC_COLOR, - GL_ONE_MINUS_SRC_COLOR, - GL_SRC_ALPHA, - GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, - GL_ONE_MINUS_DST_ALPHA, - GL_DST_COLOR, - GL_ONE_MINUS_DST_COLOR, - GL_SRC_ALPHA_SATURATE, - GL_CONSTANT_COLOR, - GL_ONE_MINUS_CONSTANT_COLOR, - GL_CONSTANT_ALPHA, - GL_ONE_MINUS_CONSTANT_ALPHA, -}; - -static const GLenum COMPARISON_TO_GL[gpu::NUM_COMPARISON_FUNCS] = { - GL_NEVER, - GL_LESS, - GL_EQUAL, - GL_LEQUAL, - GL_GREATER, - GL_NOTEQUAL, - GL_GEQUAL, - GL_ALWAYS -}; - -static const GLenum PRIMITIVE_TO_GL[gpu::NUM_PRIMITIVES] = { - GL_POINTS, - GL_LINES, - GL_LINE_STRIP, - GL_TRIANGLES, - GL_TRIANGLE_STRIP, - GL_TRIANGLE_FAN, -}; - -static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { - GL_FLOAT, - GL_INT, - GL_UNSIGNED_INT, - GL_HALF_FLOAT, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_BYTE, - GL_UNSIGNED_BYTE, - // Normalized values - GL_INT, - GL_UNSIGNED_INT, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_BYTE, - GL_UNSIGNED_BYTE, - GL_UNSIGNED_BYTE, - GL_INT_2_10_10_10_REV, -}; - -bool checkGLError(const char* name = nullptr); -bool checkGLErrorDebug(const char* name = nullptr); - -class GLBackend; - -template -struct GLObject : public GPUObject { -public: - GLObject(const std::weak_ptr& backend, const GPUType& gpuObject, GLuint id) : _gpuObject(gpuObject), _id(id), _backend(backend) {} - - virtual ~GLObject() { } - - const GPUType& _gpuObject; - const GLuint _id; -protected: - const std::weak_ptr _backend; -}; - -class GlBuffer; -class GLFramebuffer; -class GLPipeline; -class GLQuery; -class GLState; -class GLShader; -class GLTexture; -struct ShaderObject; - -} } // namespace gpu::gl - -#endif - - - diff --git a/libraries/gpu-gles/src/gpu/gl/GLState.cpp b/libraries/gpu-gles/src/gpu/gl/GLState.cpp deleted file mode 100644 index b6d917b928..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLState.cpp +++ /dev/null @@ -1,248 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#if __GNUC__ >= 5 && __GNUC_MINOR__ >= 1 -#pragma GCC diagnostic ignored "-Wsuggest-override" -#endif -#endif - - -#include "GLState.h" - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - - -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - -typedef GLState::Command Command; -typedef GLState::CommandPointer CommandPointer; -typedef GLState::Command1 Command1U; -typedef GLState::Command1 Command1I; -typedef GLState::Command1 Command1B; -typedef GLState::Command1 CommandDepthBias; -typedef GLState::Command1 CommandDepthTest; -typedef GLState::Command3 CommandStencil; -typedef GLState::Command1 CommandBlend; - -const GLState::Commands makeResetStateCommands(); - -// NOTE: This must stay in sync with the ordering of the State::Field enum -const GLState::Commands makeResetStateCommands() { - // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random - // and we have a 50/50 chance that State::DEFAULT is not yet initialized. - // Since State::DEFAULT = State::Data() it is much easier to not use the actual State::DEFAULT - // but another State::Data object with a default initialization. - const State::Data DEFAULT = State::Data(); - - auto depthBiasCommand = std::make_shared(&GLBackend::do_setStateDepthBias, - Vec2(DEFAULT.depthBias, DEFAULT.depthBiasSlopeScale)); - auto stencilCommand = std::make_shared(&GLBackend::do_setStateStencil, DEFAULT.stencilActivation, - DEFAULT.stencilTestFront, DEFAULT.stencilTestBack); - - // The state commands to reset to default, - // WARNING depending on the order of the State::Field enum - return { - std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), - std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), - std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), - std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), - std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), - std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), - std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), - - // Depth bias has 2 fields in State but really one call in GLBackend - CommandPointer(depthBiasCommand), - CommandPointer(depthBiasCommand), - - std::make_shared(&GLBackend::do_setStateDepthTest, DEFAULT.depthTest), - - // Depth bias has 3 fields in State but really one call in GLBackend - CommandPointer(stencilCommand), - CommandPointer(stencilCommand), - CommandPointer(stencilCommand), - - std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), - - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), - - std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), - - std::make_shared(&GLBackend::do_setStateColorWriteMask, DEFAULT.colorWriteMask) - }; -} - -const GLState::Commands GLState::_resetStateCommands = makeResetStateCommands(); - - -void generateFillMode(GLState::Commands& commands, State::FillMode fillMode) { - commands.push_back(std::make_shared(&GLBackend::do_setStateFillMode, int32(fillMode))); -} - -void generateCullMode(GLState::Commands& commands, State::CullMode cullMode) { - commands.push_back(std::make_shared(&GLBackend::do_setStateCullMode, int32(cullMode))); -} - -void generateFrontFaceClockwise(GLState::Commands& commands, bool isClockwise) { - commands.push_back(std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, isClockwise)); -} - -void generateDepthClampEnable(GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthClampEnable, enable)); -} - -void generateScissorEnable(GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateScissorEnable, enable)); -} - -void generateMultisampleEnable(GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateMultisampleEnable, enable)); -} - -void generateAntialiasedLineEnable(GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, enable)); -} - -void generateDepthBias(GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthBias, Vec2(state.getDepthBias(), state.getDepthBiasSlopeScale()))); -} - -void generateDepthTest(GLState::Commands& commands, const State::DepthTest& test) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthTest, int32(test.getRaw()))); -} - -void generateStencil(GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateStencil, state.getStencilActivation(), state.getStencilTestFront(), state.getStencilTestBack())); -} - -void generateAlphaToCoverageEnable(GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, enable)); -} - -void generateSampleMask(GLState::Commands& commands, uint32 mask) { - commands.push_back(std::make_shared(&GLBackend::do_setStateSampleMask, mask)); -} - -void generateBlend(GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateBlend, state.getBlendFunction())); -} - -void generateColorWriteMask(GLState::Commands& commands, uint32 mask) { - commands.push_back(std::make_shared(&GLBackend::do_setStateColorWriteMask, mask)); -} - -GLState* GLState::sync(const State& state) { - GLState* object = Backend::getGPUObject(state); - - // If GPU object already created then good - if (object) { - return object; - } - - // Else allocate and create the GLState - if (!object) { - object = new GLState(); - Backend::setGPUObject(state, object); - } - - // here, we need to regenerate something so let's do it all - object->_commands.clear(); - object->_stamp = state.getStamp(); - object->_signature = state.getSignature(); - - bool depthBias = false; - bool stencilState = false; - - // go thorugh the list of state fields in the State and record the corresponding gl command - for (int i = 0; i < State::NUM_FIELDS; i++) { - if (state.getSignature()[i]) { - switch (i) { - case State::FILL_MODE: { - generateFillMode(object->_commands, state.getFillMode()); - break; - } - case State::CULL_MODE: { - generateCullMode(object->_commands, state.getCullMode()); - break; - } - case State::DEPTH_BIAS: - case State::DEPTH_BIAS_SLOPE_SCALE: { - depthBias = true; - break; - } - case State::FRONT_FACE_CLOCKWISE: { - generateFrontFaceClockwise(object->_commands, state.isFrontFaceClockwise()); - break; - } - case State::DEPTH_CLAMP_ENABLE: { - generateDepthClampEnable(object->_commands, state.isDepthClampEnable()); - break; - } - case State::SCISSOR_ENABLE: { - generateScissorEnable(object->_commands, state.isScissorEnable()); - break; - } - case State::MULTISAMPLE_ENABLE: { - generateMultisampleEnable(object->_commands, state.isMultisampleEnable()); - break; - } - case State::ANTIALISED_LINE_ENABLE: { - generateAntialiasedLineEnable(object->_commands, state.isAntialiasedLineEnable()); - break; - } - case State::DEPTH_TEST: { - generateDepthTest(object->_commands, state.getDepthTest()); - break; - } - - case State::STENCIL_ACTIVATION: - case State::STENCIL_TEST_FRONT: - case State::STENCIL_TEST_BACK: { - stencilState = true; - break; - } - - case State::SAMPLE_MASK: { - generateSampleMask(object->_commands, state.getSampleMask()); - break; - } - case State::ALPHA_TO_COVERAGE_ENABLE: { - generateAlphaToCoverageEnable(object->_commands, state.isAlphaToCoverageEnabled()); - break; - } - - case State::BLEND_FUNCTION: { - generateBlend(object->_commands, state); - break; - } - - case State::COLOR_WRITE_MASK: { - generateColorWriteMask(object->_commands, state.getColorWriteMask()); - break; - } - } - } - } - - if (depthBias) { - generateDepthBias(object->_commands, state); - } - - if (stencilState) { - generateStencil(object->_commands, state); - } - - return object; -} - diff --git a/libraries/gpu-gles/src/gpu/gl/GLState.h b/libraries/gpu-gles/src/gpu/gl/GLState.h deleted file mode 100644 index 82635db893..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLState.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLState_h -#define hifi_gpu_gl_GLState_h - -#include "GLShared.h" - -#include - -namespace gpu { namespace gl { - -class GLBackend; -class GLState : public GPUObject { -public: - static GLState* sync(const State& state); - - class Command { - public: - virtual void run(GLBackend* backend) = 0; - Command() {} - virtual ~Command() {}; - }; - - template class Command1 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T); - void run(GLBackend* backend) { (backend->*(_func))(_param); } - Command1(GLFunction func, T param) : _func(func), _param(param) {}; - GLFunction _func; - T _param; - }; - template class Command2 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T, U); - void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1); } - Command2(GLFunction func, T param0, U param1) : _func(func), _param0(param0), _param1(param1) {}; - GLFunction _func; - T _param0; - U _param1; - }; - - template class Command3 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T, U, V); - void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1, _param2); } - Command3(GLFunction func, T param0, U param1, V param2) : _func(func), _param0(param0), _param1(param1), _param2(param2) {}; - GLFunction _func; - T _param0; - U _param1; - V _param2; - }; - - typedef std::shared_ptr< Command > CommandPointer; - typedef std::vector< CommandPointer > Commands; - - Commands _commands; - Stamp _stamp; - State::Signature _signature; - - // The state commands to reset to default, - static const Commands _resetStateCommands; - - friend class GLBackend; -}; - -} } - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp deleted file mode 100644 index 2a39901ee7..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp +++ /dev/null @@ -1,652 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GLTexelFormat.h" - -using namespace gpu; -using namespace gpu::gl; - -bool GLTexelFormat::isCompressed() const { - switch (internalFormat) { - case GL_COMPRESSED_R11_EAC: - case GL_COMPRESSED_SIGNED_R11_EAC: - case GL_COMPRESSED_RG11_EAC: - case GL_COMPRESSED_SIGNED_RG11_EAC: - case GL_COMPRESSED_RGB8_ETC2: - case GL_COMPRESSED_SRGB8_ETC2: - case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: - case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: - case GL_COMPRESSED_RGBA8_ETC2_EAC: - case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: - case GL_COMPRESSED_RGBA_ASTC_4x4: - case GL_COMPRESSED_RGBA_ASTC_5x4: - case GL_COMPRESSED_RGBA_ASTC_5x5: - case GL_COMPRESSED_RGBA_ASTC_6x5: - case GL_COMPRESSED_RGBA_ASTC_6x6: - case GL_COMPRESSED_RGBA_ASTC_8x5: - case GL_COMPRESSED_RGBA_ASTC_8x6: - case GL_COMPRESSED_RGBA_ASTC_8x8: - case GL_COMPRESSED_RGBA_ASTC_10x5: - case GL_COMPRESSED_RGBA_ASTC_10x6: - case GL_COMPRESSED_RGBA_ASTC_10x8: - case GL_COMPRESSED_RGBA_ASTC_10x10: - case GL_COMPRESSED_RGBA_ASTC_12x10: - case GL_COMPRESSED_RGBA_ASTC_12x12: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10: - case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12: - return true; - default: - return false; - } -} - -GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { - GLenum result = GL_RGBA8; - switch (dstFormat.getDimension()) { - case gpu::SCALAR: { - switch (dstFormat.getSemantic()) { - case gpu::RED: - case gpu::RGB: - case gpu::RGBA: - case gpu::SRGB: - case gpu::SRGBA: - switch (dstFormat.getType()) { - case gpu::UINT32: - result = GL_R32UI; - break; - case gpu::INT32: - result = GL_R32I; - break; - case gpu::NUINT32: - result = GL_R8; - break; - case gpu::NINT32: - result = GL_R8_SNORM; - break; - case gpu::FLOAT: - result = GL_R32F; - break; - case gpu::UINT16: - result = GL_R16UI; - break; - case gpu::INT16: - result = GL_R16I; - break; - case gpu::NUINT16: - //result = GL_R16_EXT; - break; - case gpu::NINT16: - //result = GL_R16_SNORM_EXT; - break; - case gpu::HALF: - result = GL_R16F; - break; - case gpu::UINT8: - result = GL_R8UI; - break; - case gpu::INT8: - result = GL_R8I; - break; - case gpu::NUINT8: - if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { - result = GL_SLUMINANCE8_NV; - } else { - result = GL_R8; - } - break; - case gpu::NINT8: - result = GL_R8_SNORM; - break; - default: - Q_UNREACHABLE(); - break; - } - break; - case gpu::R11G11B10: - // the type should be float - result = GL_R11F_G11F_B10F; - break; - case gpu::RGB9E5: - // the type should be float - result = GL_RGB9_E5; - break; - case gpu::DEPTH: - result = GL_DEPTH_COMPONENT16; - switch (dstFormat.getType()) { - case gpu::UINT32: - case gpu::INT32: - case gpu::NUINT32: - case gpu::NINT32: - result = GL_DEPTH_COMPONENT32_OES; - break; - case gpu::FLOAT: - result = GL_DEPTH_COMPONENT32F; - break; - case gpu::UINT16: - case gpu::INT16: - case gpu::NUINT16: - case gpu::NINT16: - case gpu::HALF: - result = GL_DEPTH_COMPONENT16; - break; - case gpu::UINT8: - case gpu::INT8: - case gpu::NUINT8: - case gpu::NINT8: - result = GL_DEPTH_COMPONENT24; - break; - default: - Q_UNREACHABLE(); - break; - } - break; - - case gpu::DEPTH_STENCIL: - result = GL_DEPTH24_STENCIL8; - break; - - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - break; - } - - case gpu::VEC2: { - switch (dstFormat.getSemantic()) { - case gpu::RGB: - case gpu::RGBA: - case gpu::XY: - result = GL_RG8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - - break; - } - - case gpu::VEC3: { - switch (dstFormat.getSemantic()) { - case gpu::RGB: - case gpu::RGBA: - result = GL_RGB8; - break; - case gpu::SRGB: - case gpu::SRGBA: - result = GL_SRGB8; // standard 2.2 gamma correction color - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - - break; - } - - case gpu::VEC4: { - switch (dstFormat.getSemantic()) { - case gpu::RGB: - result = GL_RGB8; - break; - case gpu::RGBA: - switch (dstFormat.getType()) { - case gpu::UINT32: - result = GL_RGBA32UI; - break; - case gpu::INT32: - result = GL_RGBA32I; - break; - case gpu::FLOAT: - result = GL_RGBA32F; - break; - case gpu::UINT16: - result = GL_RGBA16UI; - break; - case gpu::INT16: - result = GL_RGBA16I; - break; - case gpu::NUINT16: - //result = GL_RGBA16_EXT; - break; - case gpu::NINT16: - //result = GL_RGBA16_SNORM_EXT; - break; - case gpu::HALF: - result = GL_RGBA16F; - break; - case gpu::UINT8: - result = GL_RGBA8UI; - break; - case gpu::INT8: - result = GL_RGBA8I; - break; - case gpu::NUINT8: - result = GL_RGBA8; - break; - case gpu::NUINT2: - //result = GL_RGBA2; - break; - case gpu::NINT8: - result = GL_RGBA8_SNORM; - break; - case gpu::NINT2_10_10_10: - case gpu::NUINT32: - case gpu::NINT32: - case gpu::COMPRESSED: - case gpu::NUM_TYPES: // quiet compiler - Q_UNREACHABLE(); - } - break; - case gpu::SRGB: - result = GL_SRGB8; - break; - case gpu::SRGBA: - result = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - break; - } - default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; - } - - //qDebug() << "GLTexelFormat::evalGLTexelFormatInternal result " << result; - return result; -} - -GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat) { - - if (dstFormat != srcFormat) { - GLTexelFormat texel = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE }; - - switch (dstFormat.getDimension()) { - case gpu::SCALAR: { - texel.format = GL_RED; - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - case gpu::RED: - case gpu::RGB: - case gpu::RGBA: - texel.internalFormat = GL_R8; - break; - - case gpu::DEPTH: - texel.format = GL_DEPTH_COMPONENT; - texel.internalFormat = GL_DEPTH_COMPONENT32F; - break; - case gpu::DEPTH_STENCIL: - texel.type = GL_UNSIGNED_INT_24_8; - texel.format = GL_DEPTH_STENCIL; - texel.internalFormat = GL_DEPTH24_STENCIL8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - break; - } - - case gpu::VEC2: { - texel.format = GL_RG; - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - case gpu::RGB: - case gpu::RGBA: - case gpu::XY: - texel.internalFormat = GL_RG8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - - break; - } - - case gpu::VEC3: { - texel.format = GL_RGB; - - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - case gpu::RGB: - case gpu::RGBA: - texel.internalFormat = GL_RGB8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - - break; - } - - case gpu::VEC4: { - texel.format = GL_RGBA; - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (srcFormat.getSemantic()) { - case gpu::BGRA: - case gpu::SBGRA: - texel.format = GL_RGBA; // GL_BGRA_EXT; - break; - case gpu::RGB: - case gpu::RGBA: - case gpu::SRGB: - case gpu::SRGBA: - default: - break; - }; - - switch (dstFormat.getSemantic()) { - case gpu::RGB: - texel.internalFormat = GL_RGB8; - break; - case gpu::RGBA: - texel.internalFormat = GL_RGBA8; - break; - case gpu::SRGB: - texel.internalFormat = GL_SRGB8; - break; - case gpu::SRGBA: - texel.internalFormat = GL_SRGB8_ALPHA8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - break; - } - - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - return texel; - } else { - GLTexelFormat texel = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }; - - switch (dstFormat.getDimension()) { - case gpu::SCALAR: { - texel.format = GL_RED; - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - - case gpu::RED: - case gpu::RGB: - case gpu::RGBA: - case gpu::SRGB: - case gpu::SRGBA: - texel.internalFormat = GL_RED; - switch (dstFormat.getType()) { - case gpu::UINT32: { - texel.internalFormat = GL_R32UI; - break; - } - case gpu::INT32: { - texel.internalFormat = GL_R32I; - break; - } - case gpu::NUINT32: { - texel.internalFormat = GL_R8; - break; - } - case gpu::NINT32: { - texel.internalFormat = GL_R8_SNORM; - break; - } - case gpu::FLOAT: { - texel.internalFormat = GL_R32F; - break; - } - case gpu::UINT16: { - texel.internalFormat = GL_R16UI; - break; - } - case gpu::INT16: { - texel.internalFormat = GL_R16I; - break; - } - case gpu::NUINT16: { - texel.internalFormat = GL_R16_EXT; - break; - } - case gpu::NINT16: { - texel.internalFormat = GL_R16_SNORM_EXT; - break; - } - case gpu::HALF: { - texel.internalFormat = GL_R16F; - break; - } - case gpu::UINT8: { - texel.internalFormat = GL_R8UI; - break; - } - case gpu::INT8: { - texel.internalFormat = GL_R8I; - break; - } - case gpu::NUINT8: { - if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { - texel.internalFormat = GL_SLUMINANCE8_NV; - } else { - texel.internalFormat = GL_R8; - } - break; - } - case gpu::NINT8: { - texel.internalFormat = GL_R8_SNORM; - break; - } - case gpu::COMPRESSED: - case gpu::NUINT2: - case gpu::NUM_TYPES: { // quiet compiler - Q_UNREACHABLE(); - } - - } - break; - - case gpu::R11G11B10: - texel.format = GL_RGB; - texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV; - texel.internalFormat = GL_R11F_G11F_B10F; - break; - - case gpu::RGB9E5: - texel.format = GL_RGB; - texel.type = GL_UNSIGNED_INT_5_9_9_9_REV; - texel.internalFormat = GL_RGB9_E5; - break; - - case gpu::DEPTH: - texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it - texel.internalFormat = GL_DEPTH_COMPONENT32_OES; - switch (dstFormat.getType()) { - case gpu::UINT32: - case gpu::INT32: - case gpu::NUINT32: - case gpu::NINT32: { - texel.internalFormat = GL_DEPTH_COMPONENT32_OES; - break; - } - case gpu::FLOAT: { - texel.internalFormat = GL_DEPTH_COMPONENT32F; - break; - } - case gpu::UINT16: - case gpu::INT16: - case gpu::NUINT16: - case gpu::NINT16: - case gpu::HALF: { - texel.internalFormat = GL_DEPTH_COMPONENT16; - break; - } - case gpu::UINT8: - case gpu::INT8: - case gpu::NUINT8: - case gpu::NINT8: { - texel.internalFormat = GL_DEPTH_COMPONENT24; - break; - } - case gpu::COMPRESSED: - case gpu::NUINT2: - case gpu::NINT2_10_10_10: - case gpu::NUM_TYPES: { // quiet compiler - Q_UNREACHABLE(); - } - } - break; - case gpu::DEPTH_STENCIL: - texel.type = GL_UNSIGNED_INT_24_8; - texel.format = GL_DEPTH_STENCIL; - texel.internalFormat = GL_DEPTH24_STENCIL8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - - break; - } - - case gpu::VEC2: { - texel.format = GL_RG; - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - case gpu::RGB: - case gpu::RGBA: - case gpu::XY: - texel.internalFormat = GL_RG8; - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - - break; - } - - case gpu::VEC3: { - texel.format = GL_RGB; - - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - case gpu::RGB: - case gpu::RGBA: - texel.internalFormat = GL_RGB8; - break; - case gpu::SRGB: - case gpu::SRGBA: - texel.internalFormat = GL_SRGB8; // standard 2.2 gamma correction color - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - break; - } - - case gpu::VEC4: { - texel.format = GL_RGBA; - texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; - - switch (dstFormat.getSemantic()) { - case gpu::RGB: - texel.internalFormat = GL_RGB8; - break; - case gpu::RGBA: - texel.internalFormat = GL_RGBA8; - switch (dstFormat.getType()) { - case gpu::UINT32: - texel.format = GL_RGBA_INTEGER; - texel.internalFormat = GL_RGBA32UI; - break; - case gpu::INT32: - texel.format = GL_RGBA_INTEGER; - texel.internalFormat = GL_RGBA32I; - break; - case gpu::FLOAT: - texel.internalFormat = GL_RGBA32F; - break; - case gpu::UINT16: - texel.format = GL_RGBA_INTEGER; - texel.internalFormat = GL_RGBA16UI; - break; - case gpu::INT16: - texel.format = GL_RGBA_INTEGER; - texel.internalFormat = GL_RGBA16I; - break; - case gpu::NUINT16: - texel.format = GL_RGBA; - //texel.internalFormat = GL_RGBA16_EXT; - break; - case gpu::NINT16: - texel.format = GL_RGBA; - //texel.internalFormat = GL_RGBA16_SNORM_EXT; - break; - case gpu::HALF: - texel.format = GL_RGBA; - texel.internalFormat = GL_RGBA16F; - break; - case gpu::UINT8: - texel.format = GL_RGBA_INTEGER; - texel.internalFormat = GL_RGBA8UI; - break; - case gpu::INT8: - texel.format = GL_RGBA_INTEGER; - texel.internalFormat = GL_RGBA8I; - break; - case gpu::NUINT8: - texel.format = GL_RGBA; - texel.internalFormat = GL_RGBA8; - break; - case gpu::NINT8: - texel.format = GL_RGBA; - texel.internalFormat = GL_RGBA8_SNORM; - break; - case gpu::NUINT2: - texel.format = GL_RGBA; - texel.internalFormat = GL_RGBA8; - break; - case gpu::NUINT32: - case gpu::NINT32: - case gpu::NINT2_10_10_10: - case gpu::COMPRESSED: - case gpu::NUM_TYPES: // quiet compiler - Q_UNREACHABLE(); - } - break; - case gpu::SRGB: - texel.internalFormat = GL_SRGB8; - break; - case gpu::SRGBA: - texel.internalFormat = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color - break; - default: - qCWarning(gpugllogging) << "Unknown combination of texel format"; - } - break; - } - default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; - } - return texel; - } -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h deleted file mode 100644 index 8f37f6b604..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLTexelFormat_h -#define hifi_gpu_gl_GLTexelFormat_h - -#include "GLShared.h" - -namespace gpu { namespace gl { - -class GLTexelFormat { -public: - GLenum internalFormat; - GLenum format; - GLenum type; - - GLTexelFormat(GLenum glinternalFormat, GLenum glformat, GLenum gltype) : internalFormat(glinternalFormat), format(glformat), type(gltype) {} - GLTexelFormat(GLenum glinternalFormat) : internalFormat(glinternalFormat) {} - - bool isCompressed() const; - - static GLTexelFormat evalGLTexelFormat(const Element& dstFormat) { - return evalGLTexelFormat(dstFormat, dstFormat); - } - static GLenum evalGLTexelFormatInternal(const Element& dstFormat); - - static GLTexelFormat evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat); -}; - -} } - - -#endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp deleted file mode 100644 index 08b3f87094..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp +++ /dev/null @@ -1,701 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GLTexture.h" - -#include -#include - -#include "GLBackend.h" - -using namespace gpu; -using namespace gpu::gl; - - -const GLenum GLTexture::CUBE_FACE_LAYOUT[GLTexture::TEXTURE_CUBE_NUM_FACES] = { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z -}; - - -const GLenum GLTexture::WRAP_MODES[Sampler::NUM_WRAP_MODES] = { - GL_REPEAT, // WRAP_REPEAT, - GL_MIRRORED_REPEAT, // WRAP_MIRROR, - GL_CLAMP_TO_EDGE, // WRAP_CLAMP, - GL_CLAMP_TO_BORDER, // WRAP_BORDER, - GL_MIRRORED_REPEAT //GL_MIRROR_CLAMP_TO_EDGE_EXT // WRAP_MIRROR_ONCE, -}; - -const GLFilterMode GLTexture::FILTER_MODES[Sampler::NUM_FILTERS] = { - { GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT, - { GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, - { GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, - { GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR, - - { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, - { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, - { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, - { GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, - { GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, - { GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, - { GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, - { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, - { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC, -}; - -GLenum GLTexture::getGLTextureType(const Texture& texture) { - switch (texture.getType()) { - case Texture::TEX_2D: - return GL_TEXTURE_2D; - break; - - case Texture::TEX_CUBE: - return GL_TEXTURE_CUBE_MAP; - break; - - default: - qFatal("Unsupported texture type"); - } - Q_UNREACHABLE(); - return GL_TEXTURE_2D; -} - -uint8_t GLTexture::getFaceCount(GLenum target) { - switch (target) { - case GL_TEXTURE_2D: - return TEXTURE_2D_NUM_FACES; - case GL_TEXTURE_CUBE_MAP: - return TEXTURE_CUBE_NUM_FACES; - default: - Q_UNREACHABLE(); - break; - } -} - -const std::vector& GLTexture::getFaceTargets(GLenum target) { - static std::vector cubeFaceTargets { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z - }; - static std::vector faceTargets { - GL_TEXTURE_2D - }; - switch (target) { - case GL_TEXTURE_2D: - return faceTargets; - case GL_TEXTURE_CUBE_MAP: - return cubeFaceTargets; - default: - Q_UNREACHABLE(); - break; - } - Q_UNREACHABLE(); - return faceTargets; -} - -GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) : - GLObject(backend, texture, id), - _source(texture.source()), - _target(getGLTextureType(texture)), - _texelFormat(GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat())) -{ - Backend::setGPUObject(texture, this); -} - -GLTexture::~GLTexture() { - auto backend = _backend.lock(); - if (backend && _id) { - backend->releaseTexture(_id, 0); - } -} - -Size GLTexture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { - if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { - return 0; - } - auto dim = _gpuObject.evalMipDimensions(sourceMip); - auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); - auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); - if (mipData) { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat()); - return copyMipFaceLinesFromTexture(targetMip, face, dim, 0, texelFormat.internalFormat, texelFormat.format, texelFormat.type, mipSize, mipData->readData()); - } else { - qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str(); - } - return 0; -} - - -GLExternalTexture::GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) - : Parent(backend, texture, id) { - Backend::textureExternalCount.increment(); -} - -GLExternalTexture::~GLExternalTexture() { - auto backend = _backend.lock(); - if (backend) { - auto recycler = _gpuObject.getExternalRecycler(); - if (recycler) { - backend->releaseExternalTexture(_id, recycler); - } else { - qCWarning(gpugllogging) << "No recycler available for texture " << _id << " possible leak"; - } - const_cast(_id) = 0; - } - Backend::textureExternalCount.decrement(); -} - - -// Variable sized textures -using MemoryPressureState = GLVariableAllocationSupport::MemoryPressureState; -using WorkQueue = GLVariableAllocationSupport::WorkQueue; -using TransferJobPointer = GLVariableAllocationSupport::TransferJobPointer; - -std::list GLVariableAllocationSupport::_memoryManagedTextures; -MemoryPressureState GLVariableAllocationSupport::_memoryPressureState { MemoryPressureState::Idle }; -std::atomic GLVariableAllocationSupport::_memoryPressureStateStale { false }; -const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; -WorkQueue GLVariableAllocationSupport::_transferQueue; -WorkQueue GLVariableAllocationSupport::_promoteQueue; -WorkQueue GLVariableAllocationSupport::_demoteQueue; -size_t GLVariableAllocationSupport::_frameTexturesCreated { 0 }; - -#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f -#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f -#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024) - -static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); - -using TransferJob = GLVariableAllocationSupport::TransferJob; - -const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; -const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.x * GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.y * 4; - -#if THREADED_TEXTURE_BUFFERING - -TexturePointer GLVariableAllocationSupport::_currentTransferTexture; -TransferJobPointer GLVariableAllocationSupport::_currentTransferJob; -QThreadPool* TransferJob::_bufferThreadPool { nullptr }; - -void TransferJob::startBufferingThread() { - static std::once_flag once; - std::call_once(once, [&] { - _bufferThreadPool = new QThreadPool(qApp); - _bufferThreadPool->setMaxThreadCount(1); - }); -} - -#endif - -TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) - : _parent(parent) { - - auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip); - GLenum format; - GLenum internalFormat; - GLenum type; - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), _parent._gpuObject.getStoredMipFormat()); - format = texelFormat.format; - internalFormat = texelFormat.internalFormat; - type = texelFormat.type; - _transferSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face); - - // If we're copying a subsection of the mip, do additional calculations to find the size and offset of the segment - if (0 != lines) { - transferDimensions.y = lines; - auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); - auto bytesPerLine = (uint32_t)_transferSize / dimensions.y; - _transferOffset = bytesPerLine * lineOffset; - _transferSize = bytesPerLine * lines; - } - - Backend::texturePendingGPUTransferMemSize.update(0, _transferSize); - - if (_transferSize > GLVariableAllocationSupport::MAX_TRANSFER_SIZE) { - qCWarning(gpugllogging) << "Transfer size of " << _transferSize << " exceeds theoretical maximum transfer size"; - } - - // Buffering can invoke disk IO, so it should be off of the main and render threads - _bufferingLambda = [=] { - auto mipStorage = _parent._gpuObject.accessStoredMipFace(sourceMip, face); - if (mipStorage) { - _mipData = mipStorage->createView(_transferSize, _transferOffset); - } else { - qCWarning(gpugllogging) << "Buffering failed because mip could not be retrieved from texture " << _parent._source.c_str() ; - } - }; - - _transferLambda = [=] { - if (_mipData) { - _parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format, type, _mipData->size(), _mipData->readData()); - _mipData.reset(); - } else { - qCWarning(gpugllogging) << "Transfer failed because mip could not be retrieved from texture " << _parent._source.c_str(); - } - }; -} - -TransferJob::TransferJob(const GLTexture& parent, std::function transferLambda) - : _parent(parent), _bufferingRequired(false), _transferLambda(transferLambda) { -} - -TransferJob::~TransferJob() { - Backend::texturePendingGPUTransferMemSize.update(_transferSize, 0); -} - -bool TransferJob::tryTransfer() { -#if THREADED_TEXTURE_BUFFERING - // Are we ready to transfer - if (!bufferingCompleted()) { - startBuffering(); - return false; - } -#else - if (_bufferingRequired) { - _bufferingLambda(); - } -#endif - _transferLambda(); - return true; -} - -#if THREADED_TEXTURE_BUFFERING -bool TransferJob::bufferingRequired() const { - if (!_bufferingRequired) { - return false; - } - - // The default state of a QFuture is with status Canceled | Started | Finished, - // so we have to check isCancelled before we check the actual state - if (_bufferingStatus.isCanceled()) { - return true; - } - - return !_bufferingStatus.isStarted(); -} - -bool TransferJob::bufferingCompleted() const { - if (!_bufferingRequired) { - return true; - } - - // The default state of a QFuture is with status Canceled | Started | Finished, - // so we have to check isCancelled before we check the actual state - if (_bufferingStatus.isCanceled()) { - return false; - } - - return _bufferingStatus.isFinished(); -} - -void TransferJob::startBuffering() { - if (bufferingRequired()) { - assert(_bufferingStatus.isCanceled()); - _bufferingStatus = QtConcurrent::run(_bufferThreadPool, [=] { - _bufferingLambda(); - }); - assert(!_bufferingStatus.isCanceled()); - assert(_bufferingStatus.isStarted()); - } -} -#endif - -GLVariableAllocationSupport::GLVariableAllocationSupport() { - _memoryPressureStateStale = true; -} - -GLVariableAllocationSupport::~GLVariableAllocationSupport() { - _memoryPressureStateStale = true; -} - -void GLVariableAllocationSupport::addMemoryManagedTexture(const TexturePointer& texturePointer) { - _memoryManagedTextures.push_back(texturePointer); - if (MemoryPressureState::Idle != _memoryPressureState) { - addToWorkQueue(texturePointer); - } -} - -void GLVariableAllocationSupport::addToWorkQueue(const TexturePointer& texturePointer) { - GLTexture* gltexture = Backend::getGPUObject(*texturePointer); - GLVariableAllocationSupport* vargltexture = dynamic_cast(gltexture); - switch (_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - if (vargltexture->canDemote()) { - // Demote largest first - _demoteQueue.push({ texturePointer, (float)gltexture->size() }); - } - break; - - case MemoryPressureState::Undersubscribed: - if (vargltexture->canPromote()) { - // Promote smallest first - _promoteQueue.push({ texturePointer, 1.0f / (float)gltexture->size() }); - } - break; - - case MemoryPressureState::Transfer: - if (vargltexture->hasPendingTransfers()) { - // Transfer priority given to smaller mips first - _transferQueue.push({ texturePointer, 1.0f / (float)gltexture->_gpuObject.evalMipSize(vargltexture->_populatedMip) }); - } - break; - - case MemoryPressureState::Idle: - Q_UNREACHABLE(); - break; - } -} - -WorkQueue& GLVariableAllocationSupport::getActiveWorkQueue() { - static WorkQueue empty; - switch (_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - return _demoteQueue; - - case MemoryPressureState::Undersubscribed: - return _promoteQueue; - - case MemoryPressureState::Transfer: - return _transferQueue; - - case MemoryPressureState::Idle: - Q_UNREACHABLE(); - break; - } - return empty; -} - -// FIXME hack for stats display -QString getTextureMemoryPressureModeString() { - switch (GLVariableAllocationSupport::_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - return "Oversubscribed"; - - case MemoryPressureState::Undersubscribed: - return "Undersubscribed"; - - case MemoryPressureState::Transfer: - return "Transfer"; - - case MemoryPressureState::Idle: - return "Idle"; - } - Q_UNREACHABLE(); - return "Unknown"; -} - -void GLVariableAllocationSupport::updateMemoryPressure() { - static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); - - size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); - if (0 == allowedMemoryAllocation) { - allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; - } - - // If the user explicitly changed the allowed memory usage, we need to mark ourselves stale - // so that we react - if (allowedMemoryAllocation != lastAllowedMemoryAllocation) { - _memoryPressureStateStale = true; - lastAllowedMemoryAllocation = allowedMemoryAllocation; - } - - if (!_memoryPressureStateStale.exchange(false)) { - return; - } - - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - - // Clear any defunct textures (weak pointers that no longer have a valid texture) - _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { - return weakPointer.expired(); - }); - - // Convert weak pointers to strong. This new list may still contain nulls if a texture was - // deleted on another thread between the previous line and this one - std::vector strongTextures; { - strongTextures.reserve(_memoryManagedTextures.size()); - std::transform( - _memoryManagedTextures.begin(), _memoryManagedTextures.end(), - std::back_inserter(strongTextures), - [](const TextureWeakPointer& p) { return p.lock(); }); - } - - size_t totalVariableMemoryAllocation = 0; - size_t idealMemoryAllocation = 0; - bool canDemote = false; - bool canPromote = false; - bool hasTransfers = false; - for (const auto& texture : strongTextures) { - // Race conditions can still leave nulls in the list, so we need to check - if (!texture) { - continue; - } - GLTexture* gltexture = Backend::getGPUObject(*texture); - GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); - // Track how much the texture thinks it should be using - idealMemoryAllocation += texture->evalTotalSize(); - // Track how much we're actually using - totalVariableMemoryAllocation += gltexture->size(); - canDemote |= vartexture->canDemote(); - canPromote |= vartexture->canPromote(); - hasTransfers |= vartexture->hasPendingTransfers(); - } - - size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; - float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation; - - auto newState = MemoryPressureState::Idle; - if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) { - newState = MemoryPressureState::Undersubscribed; - } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) { - newState = MemoryPressureState::Oversubscribed; - } else if (hasTransfers) { - newState = MemoryPressureState::Transfer; - } - - if (newState != _memoryPressureState) { - _memoryPressureState = newState; - // Clear the existing queue - _transferQueue = WorkQueue(); - _promoteQueue = WorkQueue(); - _demoteQueue = WorkQueue(); - - // Populate the existing textures into the queue - if (_memoryPressureState != MemoryPressureState::Idle) { - for (const auto& texture : strongTextures) { - // Race conditions can still leave nulls in the list, so we need to check - if (!texture) { - continue; - } - addToWorkQueue(texture); - } - } - } -} - -TexturePointer GLVariableAllocationSupport::getNextWorkQueueItem(WorkQueue& workQueue) { - while (!workQueue.empty()) { - auto workTarget = workQueue.top(); - - auto texture = workTarget.first.lock(); - if (!texture) { - workQueue.pop(); - continue; - } - - // Check whether the resulting texture can actually have work performed - GLTexture* gltexture = Backend::getGPUObject(*texture); - GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); - switch (_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - if (vartexture->canDemote()) { - return texture; - } - break; - - case MemoryPressureState::Undersubscribed: - if (vartexture->canPromote()) { - return texture; - } - break; - - case MemoryPressureState::Transfer: - if (vartexture->hasPendingTransfers()) { - return texture; - } - break; - - case MemoryPressureState::Idle: - Q_UNREACHABLE(); - break; - } - - // If we got here, then the texture has no work to do in the current state, - // so pop it off the queue and continue - workQueue.pop(); - } - - return TexturePointer(); -} - -void GLVariableAllocationSupport::processWorkQueue(WorkQueue& workQueue) { - if (workQueue.empty()) { - return; - } - - // Get the front of the work queue to perform work - auto texture = getNextWorkQueueItem(workQueue); - if (!texture) { - return; - } - - // Grab the first item off the demote queue - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - - GLTexture* gltexture = Backend::getGPUObject(*texture); - GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); - switch (_memoryPressureState) { - case MemoryPressureState::Oversubscribed: - vartexture->demote(); - workQueue.pop(); - addToWorkQueue(texture); - _memoryPressureStateStale = true; - break; - - case MemoryPressureState::Undersubscribed: - vartexture->promote(); - workQueue.pop(); - addToWorkQueue(texture); - _memoryPressureStateStale = true; - break; - - case MemoryPressureState::Transfer: - if (vartexture->executeNextTransfer(texture)) { - workQueue.pop(); - addToWorkQueue(texture); - -#if THREADED_TEXTURE_BUFFERING - // Eagerly start the next buffering job if possible - texture = getNextWorkQueueItem(workQueue); - if (texture) { - gltexture = Backend::getGPUObject(*texture); - vartexture = dynamic_cast(gltexture); - vartexture->executeNextBuffer(texture); - } -#endif - } - break; - - case MemoryPressureState::Idle: - Q_UNREACHABLE(); - break; - } -} - -void GLVariableAllocationSupport::processWorkQueues() { - if (MemoryPressureState::Idle == _memoryPressureState) { - return; - } - - auto& workQueue = getActiveWorkQueue(); - // Do work on the front of the queue - processWorkQueue(workQueue); - - if (workQueue.empty()) { - _memoryPressureState = MemoryPressureState::Idle; - _memoryPressureStateStale = true; - } -} - -void GLVariableAllocationSupport::manageMemory() { - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - updateMemoryPressure(); - processWorkQueues(); -} - -bool GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& currentTexture) { -#if THREADED_TEXTURE_BUFFERING - // If a transfer job is active on the buffering thread, but has not completed it's buffering lambda, - // then we need to exit early, since we don't want to have the transfer job leave scope while it's - // being used in another thread -- See https://highfidelity.fogbugz.com/f/cases/4626 - if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) { - return false; - } -#endif - - if (_populatedMip <= _allocatedMip) { -#if THREADED_TEXTURE_BUFFERING - _currentTransferJob.reset(); - _currentTransferTexture.reset(); -#endif - return true; - } - - // If the transfer queue is empty, rebuild it - if (_pendingTransfers.empty()) { - populateTransferQueue(); - } - - bool result = false; - if (!_pendingTransfers.empty()) { -#if THREADED_TEXTURE_BUFFERING - // If there is a current transfer, but it's not the top of the pending transfer queue, then it's an orphan, so we want to abandon it. - if (_currentTransferJob && _currentTransferJob != _pendingTransfers.front()) { - _currentTransferJob.reset(); - } - - if (!_currentTransferJob) { - // Keeping hold of a strong pointer to the transfer job ensures that if the pending transfer queue is rebuilt, the transfer job - // doesn't leave scope, causing a crash in the buffering thread - _currentTransferJob = _pendingTransfers.front(); - - // Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture - _currentTransferTexture = currentTexture; - } - - // transfer jobs use asynchronous buffering of the texture data because it may involve disk IO, so we execute a try here to determine if the buffering - // is complete - if (_currentTransferJob->tryTransfer()) { - _pendingTransfers.pop(); - // Once a given job is finished, release the shared pointers keeping them alive - _currentTransferTexture.reset(); - _currentTransferJob.reset(); - result = true; - } -#else - if (_pendingTransfers.front()->tryTransfer()) { - _pendingTransfers.pop(); - result = true; - } -#endif - } - return result; -} - -#if THREADED_TEXTURE_BUFFERING -void GLVariableAllocationSupport::executeNextBuffer(const TexturePointer& currentTexture) { - if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) { - return; - } - - // If the transfer queue is empty, rebuild it - if (_pendingTransfers.empty()) { - populateTransferQueue(); - } - - if (!_pendingTransfers.empty()) { - if (!_currentTransferJob) { - _currentTransferJob = _pendingTransfers.front(); - _currentTransferTexture = currentTexture; - } - - _currentTransferJob->startBuffering(); - } -} -#endif - -void GLVariableAllocationSupport::incrementPopulatedSize(Size delta) const { - _populatedSize += delta; - // Keep the 2 code paths to be able to debug - if (_size < _populatedSize) { - Backend::textureResourcePopulatedGPUMemSize.update(0, delta); - } else { - Backend::textureResourcePopulatedGPUMemSize.update(0, delta); - } -} -void GLVariableAllocationSupport::decrementPopulatedSize(Size delta) const { - _populatedSize -= delta; - // Keep the 2 code paths to be able to debug - if (_size < _populatedSize) { - Backend::textureResourcePopulatedGPUMemSize.update(delta, 0); - } else { - Backend::textureResourcePopulatedGPUMemSize.update(delta, 0); - } -} \ No newline at end of file diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexture.h b/libraries/gpu-gles/src/gpu/gl/GLTexture.h deleted file mode 100644 index ce27d02033..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLTexture.h +++ /dev/null @@ -1,211 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLTexture_h -#define hifi_gpu_gl_GLTexture_h - -#include -#include - -#include "GLShared.h" -#include "GLBackend.h" -#include "GLTexelFormat.h" -#include - -#define THREADED_TEXTURE_BUFFERING 1 - -namespace gpu { namespace gl { - -struct GLFilterMode { - GLint minFilter; - GLint magFilter; -}; - -class GLVariableAllocationSupport { - friend class GLBackend; - -public: - GLVariableAllocationSupport(); - virtual ~GLVariableAllocationSupport(); - - enum class MemoryPressureState { - Idle, - Transfer, - Oversubscribed, - Undersubscribed, - }; - - using QueuePair = std::pair; - struct QueuePairLess { - bool operator()(const QueuePair& a, const QueuePair& b) { - return a.second < b.second; - } - }; - using WorkQueue = std::priority_queue, QueuePairLess>; - - class TransferJob { - using VoidLambda = std::function; - using VoidLambdaQueue = std::queue; - const GLTexture& _parent; - Texture::PixelsPointer _mipData; - size_t _transferOffset { 0 }; - size_t _transferSize { 0 }; - - bool _bufferingRequired { true }; - VoidLambda _transferLambda; - VoidLambda _bufferingLambda; - -#if THREADED_TEXTURE_BUFFERING - // Indicates if a transfer from backing storage to interal storage has started - QFuture _bufferingStatus; - static QThreadPool* _bufferThreadPool; -#endif - - public: - TransferJob(const TransferJob& other) = delete; - TransferJob(const GLTexture& parent, std::function transferLambda); - TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0); - ~TransferJob(); - bool tryTransfer(); - -#if THREADED_TEXTURE_BUFFERING - void startBuffering(); - bool bufferingRequired() const; - bool bufferingCompleted() const; - static void startBufferingThread(); -#endif - - private: - void transfer(); - }; - - using TransferJobPointer = std::shared_ptr; - using TransferQueue = std::queue; - static MemoryPressureState _memoryPressureState; - -public: - static void addMemoryManagedTexture(const TexturePointer& texturePointer); - -protected: - static size_t _frameTexturesCreated; - static std::atomic _memoryPressureStateStale; - static std::list _memoryManagedTextures; - static WorkQueue _transferQueue; - static WorkQueue _promoteQueue; - static WorkQueue _demoteQueue; -#if THREADED_TEXTURE_BUFFERING - static TexturePointer _currentTransferTexture; - static TransferJobPointer _currentTransferJob; -#endif - static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; - static const uvec3 MAX_TRANSFER_DIMENSIONS; - static const size_t MAX_TRANSFER_SIZE; - - - static void updateMemoryPressure(); - static void processWorkQueues(); - static void processWorkQueue(WorkQueue& workQueue); - static TexturePointer getNextWorkQueueItem(WorkQueue& workQueue); - static void addToWorkQueue(const TexturePointer& texture); - static WorkQueue& getActiveWorkQueue(); - - static void manageMemory(); - - //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; } - bool canPromote() const { return _allocatedMip > _minAllocatedMip; } - bool canDemote() const { return _allocatedMip < _maxAllocatedMip; } - bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; } -#if THREADED_TEXTURE_BUFFERING - void executeNextBuffer(const TexturePointer& currentTexture); -#endif - bool executeNextTransfer(const TexturePointer& currentTexture); - virtual void populateTransferQueue() = 0; - virtual void promote() = 0; - virtual void demote() = 0; - - // THe amount of memory currently allocated - Size _size { 0 }; - - // The amount of memory currnently populated - void incrementPopulatedSize(Size delta) const; - void decrementPopulatedSize(Size delta) const; - mutable Size _populatedSize { 0 }; - - // The allocated mip level, relative to the number of mips in the gpu::Texture object - // The relationship between a given glMip to the original gpu::Texture mip is always - // glMip + _allocatedMip - uint16 _allocatedMip { 0 }; - // The populated mip level, relative to the number of mips in the gpu::Texture object - // This must always be >= the allocated mip - uint16 _populatedMip { 0 }; - // The highest (lowest resolution) mip that we will support, relative to the number - // of mips in the gpu::Texture object - uint16 _maxAllocatedMip { 0 }; - // The lowest (highest resolution) mip that we will support, relative to the number - // of mips in the gpu::Texture object - uint16 _minAllocatedMip { 0 }; - // Contains a series of lambdas that when executed will transfer data to the GPU, modify - // the _populatedMip and update the sampler in order to fully populate the allocated texture - // until _populatedMip == _allocatedMip - TransferQueue _pendingTransfers; -}; - -class GLTexture : public GLObject { - using Parent = GLObject; - friend class GLBackend; - friend class GLVariableAllocationSupport; -public: - static const uint16_t INVALID_MIP { (uint16_t)-1 }; - static const uint8_t INVALID_FACE { (uint8_t)-1 }; - - ~GLTexture(); - - const GLuint& _texture { _id }; - const std::string _source; - const GLenum _target; - GLTexelFormat _texelFormat; - - static const std::vector& getFaceTargets(GLenum textureType); - static uint8_t getFaceCount(GLenum textureType); - static GLenum getGLTextureType(const Texture& texture); - - static const uint8_t TEXTURE_2D_NUM_FACES = 1; - static const uint8_t TEXTURE_CUBE_NUM_FACES = 6; - static const GLenum CUBE_FACE_LAYOUT[TEXTURE_CUBE_NUM_FACES]; - static const GLFilterMode FILTER_MODES[Sampler::NUM_FILTERS]; - static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES]; - -protected: - virtual Size size() const = 0; - virtual void generateMips() const = 0; - virtual void syncSampler() const = 0; - - virtual Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const = 0; - virtual Size copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const final; - virtual void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {} // Only relevant for Variable Allocation textures - - GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); -}; - -class GLExternalTexture : public GLTexture { - using Parent = GLTexture; - friend class GLBackend; -public: - ~GLExternalTexture(); -protected: - GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); - void generateMips() const override {} - void syncSampler() const override {} - Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override { return 0;} - - Size size() const override { return 0; } -}; - - -} } - -#endif diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index aeda054e72..38e28e630a 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -13,8 +13,8 @@ #include -#include "../gl/GLBackend.h" -#include "../gl/GLTexture.h" +#include +#include namespace gpu { namespace gles { diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp index 05bda34d7f..17fdad8377 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLESBackend.h" -#include "../gl/GLBuffer.h" +#include namespace gpu { namespace gles { diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp index dc4025247e..0bf1548a4b 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp @@ -12,8 +12,8 @@ #include -#include "../gl/GLFramebuffer.h" -#include "../gl/GLTexture.h" +#include +#include namespace gpu { namespace gles { diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendQuery.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendQuery.cpp index db541b07bc..434fbcb04f 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendQuery.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendQuery.cpp @@ -10,7 +10,7 @@ // #include "GLESBackend.h" -#include "../gl/GLQuery.h" +#include using namespace gpu; using namespace gpu::gl; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp index 01a87978c2..16cf1559dd 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLESBackend.h" -#include "../gl/GLShader.h" +#include using namespace gpu; using namespace gpu::gl; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index d2fa2aabab..2009dc5dc9 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -13,16 +13,28 @@ #include #include -#include "../gl/GLTexelFormat.h" +#include using namespace gpu; using namespace gpu::gl; using namespace gpu::gles; bool GLESBackend::supportedTextureFormat(const gpu::Element& format) { - // FIXME distinguish between GLES and GL compressed formats after support - // for the former is added to gpu::Element - return !format.isCompressed(); + switch (format.getSemantic()) { + case gpu::Semantic::COMPRESSED_ETC2_RGB: + case gpu::Semantic::COMPRESSED_ETC2_SRGB: + case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_RGBA: + case gpu::Semantic::COMPRESSED_ETC2_SRGBA: + case gpu::Semantic::COMPRESSED_EAC_RED: + case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED: + case gpu::Semantic::COMPRESSED_EAC_XY: + case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED: + return true; + default: + return !format.isCompressed(); + } } GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) { @@ -231,6 +243,29 @@ GLESFixedAllocationTexture::GLESFixedAllocationTexture(const std::weak_ptr(sampler.getMipOffset(), sampler.getMinMip()); - - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip); glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip())); } @@ -459,7 +496,6 @@ void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLui sourceMip._size = (GLint)faceTargets.size() * sourceMip._faceSize; sourceMip._offset = bufferOffset; bufferOffset += sourceMip._size; - gpu::gl::checkGLError(); } (void)CHECK_GL_ERROR(); @@ -505,7 +541,6 @@ void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLui #endif glCompressedTexSubImage2D(faceTargets[f], destLevel, 0, 0, sourceMip._width, sourceMip._height, internalFormat, sourceMip._faceSize, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); - gpu::gl::checkGLError(); } } diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 7141a7eac7..db4941c163 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -45,7 +45,12 @@ size_t Batch::_dataMax { BATCH_PREALLOCATE_MIN }; size_t Batch::_objectsMax { BATCH_PREALLOCATE_MIN }; size_t Batch::_drawCallInfosMax { BATCH_PREALLOCATE_MIN }; -Batch::Batch() { +Batch::Batch(const char* name) { +#ifdef DEBUG + if (name) { + _name = name; + } +#endif _commands.reserve(_commandsMax); _commandOffsets.reserve(_commandOffsetsMax); _params.reserve(_paramsMax); @@ -56,6 +61,9 @@ Batch::Batch() { Batch::Batch(const Batch& batch_) { Batch& batch = *const_cast(&batch_); +#ifdef DEBUG + _name = batch_._name; +#endif _commands.swap(batch._commands); _commandOffsets.swap(batch._commandOffsets); _params.swap(batch._params); @@ -67,10 +75,12 @@ Batch::Batch(const Batch& batch_) { _buffers._items.swap(batch._buffers._items); _textures._items.swap(batch._textures._items); + _textureTables._items.swap(batch._textureTables._items); _streamFormats._items.swap(batch._streamFormats._items); _transforms._items.swap(batch._transforms._items); _pipelines._items.swap(batch._pipelines._items); _framebuffers._items.swap(batch._framebuffers._items); + _swapChains._items.swap(batch._swapChains._items); _drawCallInfos.swap(batch._drawCallInfos); _queries._items.swap(batch._queries._items); _lambdas._items.swap(batch._lambdas._items); @@ -104,10 +114,12 @@ void Batch::clear() { _data.clear(); _buffers.clear(); _textures.clear(); + _textureTables.clear(); _streamFormats.clear(); _transforms.clear(); _pipelines.clear(); _framebuffers.clear(); + _swapChains.clear(); _objects.clear(); _drawCallInfos.clear(); } @@ -327,6 +339,21 @@ void Batch::setResourceTexture(uint32 slot, const TextureView& view) { setResourceTexture(slot, view._texture); } +void Batch::setResourceTextureTable(const TextureTablePointer& textureTable, uint32 slot) { + ADD_COMMAND(setResourceTextureTable); + _params.emplace_back(_textureTables.cache(textureTable)); + _params.emplace_back(slot); +} + +void Batch::setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex, unsigned int renderBufferSlot) { + ADD_COMMAND(setResourceFramebufferSwapChainTexture); + + _params.emplace_back(_swapChains.cache(framebuffer)); + _params.emplace_back(slot); + _params.emplace_back(swapChainIndex); + _params.emplace_back(renderBufferSlot); +} + void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { ADD_COMMAND(setFramebuffer); @@ -334,6 +361,19 @@ void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { } +void Batch::setFramebufferSwapChain(const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex) { + ADD_COMMAND(setFramebufferSwapChain); + + _params.emplace_back(_swapChains.cache(framebuffer)); + _params.emplace_back(swapChainIndex); +} + +void Batch::advance(const SwapChainPointer& swapChain) { + ADD_COMMAND(advance); + + _params.emplace_back(_swapChains.cache(swapChain)); +} + void Batch::clearFramebuffer(Framebuffer::Masks targets, const Vec4& color, float depth, int stencil, bool enableScissor) { ADD_COMMAND(clearFramebuffer); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 79f08ea5cc..96b234295d 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -91,7 +91,7 @@ public: void captureDrawCallInfo(); void captureNamedDrawCallInfo(std::string name); - Batch(); + Batch(const char* name = nullptr); Batch(const Batch& batch); ~Batch(); @@ -187,11 +187,15 @@ public: void setResourceTexture(uint32 slot, const TexturePointer& texture); void setResourceTexture(uint32 slot, const TextureView& view); // not a command, just a shortcut from a TextureView - + void setResourceTextureTable(const TextureTablePointer& table, uint32 slot = 0); + void setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swpaChainIndex, unsigned int renderBufferSlot = 0U); // not a command, just a shortcut from a TextureView // Ouput Stage void setFramebuffer(const FramebufferPointer& framebuffer); - + void setFramebufferSwapChain(const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex); + + void advance(const SwapChainPointer& swapChain); + // Clear framebuffer layers // Targets can be any of the render buffers contained in the currnetly bound Framebuffer // Optionally the scissor test can be enabled locally for this command and to restrict the clearing command to the pixels contained in the scissor rectangle @@ -299,12 +303,17 @@ public: COMMAND_setUniformBuffer, COMMAND_setResourceBuffer, COMMAND_setResourceTexture, + COMMAND_setResourceTextureTable, + COMMAND_setResourceFramebufferSwapChainTexture, COMMAND_setFramebuffer, + COMMAND_setFramebufferSwapChain, COMMAND_clearFramebuffer, COMMAND_blit, COMMAND_generateTextureMips, + COMMAND_advance, + COMMAND_beginQuery, COMMAND_endQuery, COMMAND_getQuery, @@ -402,9 +411,10 @@ public: return offset; } - Data get(uint32 offset) const { + const Data& get(uint32 offset) const { if (offset >= _items.size()) { - return Data(); + static const Data EMPTY; + return EMPTY; } return (_items.data() + offset)->_data; } @@ -417,10 +427,12 @@ public: typedef Cache::Vector BufferCaches; typedef Cache::Vector TextureCaches; + typedef Cache::Vector TextureTableCaches; typedef Cache::Vector StreamFormatCaches; typedef Cache::Vector TransformCaches; typedef Cache::Vector PipelineCaches; typedef Cache::Vector FramebufferCaches; + typedef Cache::Vector SwapChainCaches; typedef Cache::Vector QueryCaches; typedef Cache::Vector StringCaches; typedef Cache>::Vector LambdaCache; @@ -471,10 +483,12 @@ public: BufferCaches _buffers; TextureCaches _textures; + TextureTableCaches _textureTables; StreamFormatCaches _streamFormats; TransformCaches _transforms; PipelineCaches _pipelines; FramebufferCaches _framebuffers; + SwapChainCaches _swapChains; QueryCaches _queries; LambdaCache _lambdas; StringCaches _profileRanges; @@ -486,6 +500,11 @@ public: bool _enableSkybox { false }; protected: + +#ifdef DEBUG + std::string _name; +#endif + friend class Context; friend class Frame; diff --git a/libraries/gpu/src/gpu/Buffer.cpp b/libraries/gpu/src/gpu/Buffer.cpp index 7ed2411c71..ebb768e597 100644 --- a/libraries/gpu/src/gpu/Buffer.cpp +++ b/libraries/gpu/src/gpu/Buffer.cpp @@ -98,8 +98,9 @@ Buffer::Update::Update(const Buffer& parent) : buffer(parent) { void Buffer::Update::apply() const { // Make sure we're loaded in order - ++buffer._applyUpdateCount; - assert(buffer._applyUpdateCount.load() == updateNumber); + buffer._applyUpdateCount++; + assert(buffer._applyUpdateCount == updateNumber); + const auto pageSize = buffer._pages._pageSize; buffer._renderSysmem.resize(size); buffer._renderPages.accommodate(size); diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index 526cc3dd46..d70e588f4d 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -11,18 +11,45 @@ <@if not GPU_COLOR_SLH@> <@def GPU_COLOR_SLH@> -float sRGBFloatToLinear(float value) { +// Linear ====> linear RGB +// sRGB ======> standard RGB with gamma of 2.2 +// YCoCg =====> Luma (Y) chrominance green (Cg) and chrominance orange (Co) +// https://software.intel.com/en-us/node/503873 + +float color_scalar_sRGBToLinear(float value) { const float SRGB_ELBOW = 0.04045; return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); } -vec3 colorToLinearRGB(vec3 srgb) { - return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b)); +vec3 color_sRGBToLinear(vec3 srgb) { + return vec3(color_scalar_sRGBToLinear(srgb.r), color_scalar_sRGBToLinear(srgb.g), color_scalar_sRGBToLinear(srgb.b)); } -vec4 colorToLinearRGBA(vec4 srgba) { - return vec4(colorToLinearRGB(srgba.xyz), srgba.w); +vec4 color_sRGBAToLinear(vec4 srgba) { + return vec4(color_sRGBToLinear(srgba.xyz), srgba.w); +} + +vec3 color_LinearToYCoCg(vec3 rgb) { + // Y = R/4 + G/2 + B/4 + // Co = R/2 - B/2 + // Cg = -R/4 + G/2 - B/4 + return vec3( + rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, + rgb.x/2.0 - rgb.z/2.0, + -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 + ); +} + +vec3 color_YCoCgToLinear(vec3 ycocg) { + // R = Y + Co - Cg + // G = Y + Cg + // B = Y - Co - Cg + return clamp(vec3( + ycocg.x + ycocg.y - ycocg.z, + ycocg.x + ycocg.z, + ycocg.x - ycocg.y - ycocg.z + ), vec3(0.0), vec3(1.0)); } <@func declareColorWheel()@> diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index d7d86c3ef7..e1b68c88ca 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -53,11 +53,12 @@ const std::string& Context::getBackendVersion() const { return _backend->getVersion(); } -void Context::beginFrame(const glm::mat4& renderPose) { +void Context::beginFrame(const glm::mat4& renderView, const glm::mat4& renderPose) { assert(!_frameActive); _frameActive = true; _currentFrame = std::make_shared(); _currentFrame->pose = renderPose; + _currentFrame->view = renderView; if (!_frameRangeTimer) { _frameRangeTimer = std::make_shared("gpu::Context::Frame"); @@ -108,7 +109,7 @@ void Context::executeFrame(const FramePointer& frame) const { consumeFrameUpdates(frame); _backend->setStereoState(frame->stereoState); { - Batch beginBatch; + Batch beginBatch("Context::executeFrame::begin"); _frameRangeTimer->begin(beginBatch); _backend->render(beginBatch); @@ -117,7 +118,7 @@ void Context::executeFrame(const FramePointer& frame) const { _backend->render(batch); } - Batch endBatch; + Batch endBatch("Context::executeFrame::end"); _frameRangeTimer->end(endBatch); _backend->render(endBatch); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 195565f438..2df7de2331 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -161,7 +161,7 @@ public: const std::string& getBackendVersion() const; - void beginFrame(const glm::mat4& renderPose = glm::mat4()); + void beginFrame(const glm::mat4& renderView = glm::mat4(), const glm::mat4& renderPose = glm::mat4()); void appendFrameBatch(Batch& batch); FramePointer endFrame(); @@ -274,8 +274,8 @@ protected: typedef std::shared_ptr ContextPointer; template -void doInBatch(std::shared_ptr context, F f) { - gpu::Batch batch; +void doInBatch(const char* name, std::shared_ptr context, F f) { + gpu::Batch batch(name); f(batch); context->appendFrameBatch(batch); } diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index c4b6b39723..7e277ae488 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -21,13 +21,24 @@ const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA }; const Element Element::COLOR_RGBA_2{ VEC4, NUINT2, RGBA }; -const Element Element::COLOR_COMPRESSED_RED{ TILE4x4, COMPRESSED, COMPRESSED_BC4_RED }; -const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB }; -const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; -const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; -const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; -const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; -const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; +const Element Element::COLOR_COMPRESSED_BCX_RED { TILE4x4, COMPRESSED, COMPRESSED_BC4_RED }; +const Element Element::COLOR_COMPRESSED_BCX_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB }; +const Element Element::COLOR_COMPRESSED_BCX_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; +const Element Element::COLOR_COMPRESSED_BCX_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; +const Element Element::COLOR_COMPRESSED_BCX_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; +const Element Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_BCX_HDR_RGB { TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; + +const Element Element::COLOR_COMPRESSED_ETC2_RGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB }; +const Element Element::COLOR_COMPRESSED_ETC2_SRGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB }; +const Element Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA }; +const Element Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA }; +const Element Element::COLOR_COMPRESSED_ETC2_RGBA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGBA }; +const Element Element::COLOR_COMPRESSED_ETC2_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGBA }; +const Element Element::COLOR_COMPRESSED_EAC_RED { TILE4x4, COMPRESSED, COMPRESSED_EAC_RED }; +const Element Element::COLOR_COMPRESSED_EAC_RED_SIGNED { TILE4x4, COMPRESSED, COMPRESSED_EAC_RED_SIGNED }; +const Element Element::COLOR_COMPRESSED_EAC_XY { TILE4x4, COMPRESSED, COMPRESSED_EAC_XY }; +const Element Element::COLOR_COMPRESSED_EAC_XY_SIGNED { TILE4x4, COMPRESSED, COMPRESSED_EAC_XY_SIGNED }; const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 17102d0415..d4e4a89636 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -194,6 +194,17 @@ enum Semantic : uint8_t { COMPRESSED_BC6_RGB, COMPRESSED_BC7_SRGBA, + COMPRESSED_ETC2_RGB, + COMPRESSED_ETC2_SRGB, + COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, + COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, + COMPRESSED_ETC2_RGBA, + COMPRESSED_ETC2_SRGBA, + COMPRESSED_EAC_RED, + COMPRESSED_EAC_RED_SIGNED, + COMPRESSED_EAC_XY, + COMPRESSED_EAC_XY_SIGNED, + _LAST_COMPRESSED, R11G11B10, @@ -249,6 +260,17 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = { 16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes 16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes + 8, //COMPRESSED_ETC2_RGB, + 8, //COMPRESSED_ETC2_SRGB, + 8, //COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, + 8, //COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, + 16, //COMPRESSED_ETC2_RGBA, + 16, //COMPRESSED_ETC2_SRGBA, + 8, //COMPRESSED_EAC_RED, + 8, //COMPRESSED_EAC_RED_SIGNED, + 16, //COMPRESSED_EAC_XY, + 16, //COMPRESSED_EAC_XY_SIGNED, + 1, //_LAST_COMPRESSED, 1, //R11G11B10, @@ -316,13 +338,23 @@ public: static const Element COLOR_RGBA_2; static const Element COLOR_R11G11B10; static const Element COLOR_RGB9E5; - static const Element COLOR_COMPRESSED_RED; - static const Element COLOR_COMPRESSED_SRGB; - static const Element COLOR_COMPRESSED_SRGBA_MASK; - static const Element COLOR_COMPRESSED_SRGBA; - static const Element COLOR_COMPRESSED_XY; - static const Element COLOR_COMPRESSED_SRGBA_HIGH; - static const Element COLOR_COMPRESSED_HDR_RGB; + static const Element COLOR_COMPRESSED_BCX_RED; + static const Element COLOR_COMPRESSED_BCX_SRGB; + static const Element COLOR_COMPRESSED_BCX_SRGBA_MASK; + static const Element COLOR_COMPRESSED_BCX_SRGBA; + static const Element COLOR_COMPRESSED_BCX_XY; + static const Element COLOR_COMPRESSED_BCX_SRGBA_HIGH; + static const Element COLOR_COMPRESSED_BCX_HDR_RGB; + static const Element COLOR_COMPRESSED_ETC2_RGB; + static const Element COLOR_COMPRESSED_ETC2_SRGB; + static const Element COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA; + static const Element COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA; + static const Element COLOR_COMPRESSED_ETC2_RGBA; + static const Element COLOR_COMPRESSED_ETC2_SRGBA; + static const Element COLOR_COMPRESSED_EAC_RED; + static const Element COLOR_COMPRESSED_EAC_RED_SIGNED; + static const Element COLOR_COMPRESSED_EAC_XY; + static const Element COLOR_COMPRESSED_EAC_XY_SIGNED; static const Element VEC2NU8_XY; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 8100efafaf..987788c29b 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -91,6 +91,8 @@ namespace gpu { using Textures = std::vector; class TextureView; using TextureViews = std::vector; + class TextureTable; + using TextureTablePointer = std::shared_ptr; struct StereoState { bool isStereo() const { diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 1ed77a26b7..441e3c5b5d 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -28,6 +28,8 @@ namespace gpu { StereoState stereoState; uint32_t frameIndex{ 0 }; + /// The view matrix used for rendering the frame, only applicable for HMDs + Mat4 view; /// The sensor pose used for rendering the frame, only applicable for HMDs Mat4 pose; /// The collection of batches which make up the frame diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index f470cc8aa9..fbbec50a28 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -12,6 +12,7 @@ #define hifi_gpu_Framebuffer_h #include "Texture.h" +#include "ResourceSwapChain.h" #include class Transform; // Texcood transform util @@ -177,6 +178,8 @@ protected: Framebuffer() {} }; typedef std::shared_ptr FramebufferPointer; +typedef ResourceSwapChain FramebufferSwapChain; +typedef std::shared_ptr FramebufferSwapChainPointer; } diff --git a/libraries/gpu/src/gpu/ResourceSwapChain.h b/libraries/gpu/src/gpu/ResourceSwapChain.h new file mode 100644 index 0000000000..7b46b35521 --- /dev/null +++ b/libraries/gpu/src/gpu/ResourceSwapChain.h @@ -0,0 +1,62 @@ +// +// Created by Olivier Prat on 2018/02/19 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_ResourceSwapChain_h +#define hifi_gpu_ResourceSwapChain_h + +#include +#include + +namespace gpu { + class SwapChain { + public: + + SwapChain(unsigned int size = 2U) : _size{ size } {} + virtual ~SwapChain() {} + + void advance() { + _frontIndex = (_frontIndex + 1) % _size; + } + + unsigned int getSize() const { return _size; } + + protected: + unsigned int _size; + unsigned int _frontIndex{ 0U }; + + }; + typedef std::shared_ptr SwapChainPointer; + + template + class ResourceSwapChain : public SwapChain { + public: + + enum { + MAX_SIZE = 4 + }; + + using Type = R; + using TypePointer = std::shared_ptr; + + ResourceSwapChain(unsigned int size = 2U) : SwapChain{ size } {} + + void reset() { + for (auto& ptr : _resources) { + ptr.reset(); + } + } + + TypePointer& edit(unsigned int index) { return _resources[(index + _frontIndex) % _size]; } + const TypePointer& get(unsigned int index) const { return _resources[(index + _frontIndex) % _size]; } + + private: + + std::array _resources; + }; +} + +#endif diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 4a588c3c84..ed9505766b 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -868,16 +868,6 @@ void SphericalHarmonics::evalFromTexture(const Texture& texture) { // TextureSource -TextureSource::TextureSource() { -} - -TextureSource::~TextureSource() { -} - -void TextureSource::reset(const QUrl& url) { - _imageUrl = url; -} - void TextureSource::resetTexture(gpu::TexturePointer texture) { _gpuTexture = texture; } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index ad3dc5fada..4d82aba595 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -17,8 +17,10 @@ #include #include +#include #include #include +#include #include "Forward.h" #include "Resource.h" #include "Metric.h" @@ -132,6 +134,19 @@ public: Desc() {} Desc(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _filter(filter), _wrapModeU(wrap), _wrapModeV(wrap), _wrapModeW(wrap) {} + + bool operator==(const Desc& other) const { + return _borderColor == other._borderColor && + _maxAnisotropy == other._maxAnisotropy && + _filter == other._filter && + _comparisonFunc == other._comparisonFunc && + _wrapModeU == other._wrapModeU && + _wrapModeV == other._wrapModeV && + _wrapModeW == other._wrapModeW && + _mipOffset == other._mipOffset && + _minMip == other._minMip && + _maxMip == other._maxMip; + } }; Sampler() {} @@ -156,6 +171,13 @@ public: uint8 getMaxMip() const { return _desc._maxMip; } const Desc& getDesc() const { return _desc; } + + bool operator==(const Sampler& other) const { + return _desc == other._desc; + } + bool operator!=(const Sampler& other) const { + return !(*this == other); + } protected: Desc _desc; }; @@ -647,13 +669,11 @@ typedef std::vector TextureViews; // It provides the mechanism to create a texture using a customizable TextureLoader class TextureSource { public: - TextureSource(); - ~TextureSource(); + TextureSource(const QUrl& url, int type = 0) : _imageUrl(url), _type(type) {} const QUrl& getUrl() const { return _imageUrl; } const gpu::TexturePointer getGPUTexture() const { return _gpuTexture; } - - void reset(const QUrl& url); + int getType() const { return _type; } void resetTexture(gpu::TexturePointer texture); @@ -662,11 +682,23 @@ public: protected: gpu::TexturePointer _gpuTexture; QUrl _imageUrl; + int _type { 0 }; }; typedef std::shared_ptr< TextureSource > TextureSourcePointer; }; +namespace std { + template<> struct hash { + size_t operator()(const gpu::Sampler& sampler) const noexcept { + size_t result = 0; + const auto& desc = sampler.getDesc(); + hash_combine(result, desc._comparisonFunc, desc._filter, desc._maxAnisotropy, desc._maxMip, desc._minMip, desc._wrapModeU, desc._wrapModeV, desc._wrapModeW); + return result; + } + }; +} + Q_DECLARE_METATYPE(gpu::TexturePointer) #endif diff --git a/libraries/gpu/src/gpu/TextureTable.cpp b/libraries/gpu/src/gpu/TextureTable.cpp new file mode 100644 index 0000000000..0c3a43808b --- /dev/null +++ b/libraries/gpu/src/gpu/TextureTable.cpp @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2017/01/25 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TextureTable.h" +#include "Texture.h" + +#include +using namespace gpu; + + +const size_t TextureTable::COUNT{ TEXTURE_TABLE_COUNT }; + +TextureTable::TextureTable() { } + +TextureTable::TextureTable(const std::initializer_list& textures) { + auto max = std::min(COUNT, textures.size()); + auto itr = textures.begin(); + size_t index = 0; + while (itr != textures.end() && index < max) { + setTexture(index, *itr); + ++index; + } +} + +TextureTable::TextureTable(const Array& textures) : _textures(textures) { +} + +void TextureTable::setTexture(size_t index, const TexturePointer& texturePointer) { + if (index >= COUNT || _textures[index] == texturePointer) { + return; + } + { + Lock lock(_mutex); + ++_stamp; + _textures[index] = texturePointer; + } +} + +void TextureTable::setTexture(size_t index, const TextureView& textureView) { + setTexture(index, textureView._texture); +} + +TextureTable::Array TextureTable::getTextures() const { + Array result; + { + Lock lock(_mutex); + result = _textures; + } + return result; +} diff --git a/libraries/gpu/src/gpu/TextureTable.h b/libraries/gpu/src/gpu/TextureTable.h new file mode 100644 index 0000000000..794b8535d0 --- /dev/null +++ b/libraries/gpu/src/gpu/TextureTable.h @@ -0,0 +1,44 @@ +// +// Created by Bradley Austin Davis on 2017/01/25 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_TextureTable_h +#define hifi_gpu_TextureTable_h + +#include "Forward.h" + +#include + +#define TEXTURE_TABLE_COUNT 8 + +namespace gpu { + +class TextureTable { +public: + static const size_t COUNT; + using Array = std::array; + TextureTable(); + TextureTable(const std::initializer_list& textures); + TextureTable(const Array& textures); + + // Only for gpu::Context + const GPUObjectPointer gpuObject{}; + + void setTexture(size_t index, const TexturePointer& texturePointer); + void setTexture(size_t index, const TextureView& texturePointer); + + Array getTextures() const; + Stamp getStamp() const { return _stamp; } + +private: + mutable Mutex _mutex; + Array _textures; + Stamp _stamp{ 1 }; +}; + +} + +#endif diff --git a/libraries/gpu/src/gpu/TextureTable.slh b/libraries/gpu/src/gpu/TextureTable.slh new file mode 100644 index 0000000000..f2d89e753b --- /dev/null +++ b/libraries/gpu/src/gpu/TextureTable.slh @@ -0,0 +1,38 @@ + +<@if not GPU_TEXTURE_TABLE_SLH@> +<@def GPU_TEXTURE_TABLE_SLH@> + +#ifdef GPU_TEXTURE_TABLE_BINDLESS +#define GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES 8 + +struct GPUTextureTable { + uvec4 _textures[GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES]; +}; + +#define TextureTable(index, name) layout (std140) uniform gpu_resourceTextureTable##index { GPUTextureTable name; } + +#define tableTex(name, slot) sampler2D(name._textures[slot].xy) +#define tableTexMinLod(name, slot) float(name._textures[slot].z) + +#define tableTexValue(name, slot, uv) \ + tableTexValueLod(tableTex(matTex, albedoMap), tableTexMinLod(matTex, albedoMap), uv) + +vec4 tableTexValueLod(sampler2D sampler, float minLod, vec2 uv) { + float queryLod = textureQueryLod(sampler, uv).x; + queryLod = max(minLod, queryLod); + return textureLod(sampler, uv, queryLod); +} + +#else + +#endif + +<@endif@> diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 754bdca445..5e354c0a5c 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -504,6 +504,8 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { gpuktxKeyValue._usage = Texture::Usage::Builder().withColor().withAlpha().build(); } + auto samplerDesc = gpuktxKeyValue._samplerDesc; + samplerDesc._maxMip = gpu::Sampler::MAX_MIP_LEVEL; auto texture = create(gpuktxKeyValue._usageType, type, texelFormat, @@ -513,7 +515,7 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { 1, // num Samples header.getNumberOfSlices(), header.getNumberOfLevels(), - gpuktxKeyValue._samplerDesc); + samplerDesc); texture->setUsage(gpuktxKeyValue._usage); // Assing the mips availables @@ -572,20 +574,40 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RED, ktx::GLInternalFormat::R8, ktx::GLBaseInternalFormat::RED); } else if (texelFormat == Format::VEC2NU8_XY && mipFormat == Format::VEC2NU8_XY) { header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RG, ktx::GLInternalFormat::RG8, ktx::GLBaseInternalFormat::RG); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGB && mipFormat == Format::COLOR_COMPRESSED_SRGB) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGB && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGB) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGB); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_SRGBA_MASK) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_MASK) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGBA); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA && mipFormat == Format::COLOR_COMPRESSED_SRGBA) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, ktx::GLBaseInternalFormat::RGBA); - } else if (texelFormat == Format::COLOR_COMPRESSED_RED && mipFormat == Format::COLOR_COMPRESSED_RED) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_RED && mipFormat == Format::COLOR_COMPRESSED_BCX_RED) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RED_RGTC1, ktx::GLBaseInternalFormat::RED); - } else if (texelFormat == Format::COLOR_COMPRESSED_XY && mipFormat == Format::COLOR_COMPRESSED_XY) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_XY && mipFormat == Format::COLOR_COMPRESSED_BCX_XY) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA); - } else if (texelFormat == Format::COLOR_COMPRESSED_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_HDR_RGB) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_BCX_HDR_RGB) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGB && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGB && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGBA && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGBA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGBA && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGBA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_RED && mipFormat == Format::COLOR_COMPRESSED_EAC_RED) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_R11_EAC, ktx::GLBaseInternalFormat::RED); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_RED_SIGNED && mipFormat == Format::COLOR_COMPRESSED_EAC_RED_SIGNED) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC, ktx::GLBaseInternalFormat::RED); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_XY && mipFormat == Format::COLOR_COMPRESSED_EAC_XY) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG11_EAC, ktx::GLBaseInternalFormat::RG); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_XY_SIGNED && mipFormat == Format::COLOR_COMPRESSED_EAC_XY_SIGNED) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC, ktx::GLBaseInternalFormat::RG); } else if (texelFormat == Format::COLOR_RGB9E5 && mipFormat == Format::COLOR_RGB9E5) { header.setUncompressed(ktx::GLType::UNSIGNED_INT_5_9_9_9_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::RGB9_E5, ktx::GLBaseInternalFormat::RGB); } else if (texelFormat == Format::COLOR_R11G11B10 && mipFormat == Format::COLOR_R11G11B10) { @@ -640,31 +662,45 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E texelFormat = Format::COLOR_RGB9E5; } else if (header.isCompressed()) { if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) { - mipFormat = Format::COLOR_COMPRESSED_SRGB; - texelFormat = Format::COLOR_COMPRESSED_SRGB; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGB; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) { - mipFormat = Format::COLOR_COMPRESSED_SRGBA_MASK; - texelFormat = Format::COLOR_COMPRESSED_SRGBA_MASK; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) { - mipFormat = Format::COLOR_COMPRESSED_SRGBA; - texelFormat = Format::COLOR_COMPRESSED_SRGBA; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) { - mipFormat = Format::COLOR_COMPRESSED_RED; - texelFormat = Format::COLOR_COMPRESSED_RED; + texelFormat = Format::COLOR_COMPRESSED_BCX_RED; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) { - mipFormat = Format::COLOR_COMPRESSED_XY; - texelFormat = Format::COLOR_COMPRESSED_XY; + texelFormat = Format::COLOR_COMPRESSED_BCX_XY; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { - mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; - texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) { - mipFormat = Format::COLOR_COMPRESSED_HDR_RGB; - texelFormat = Format::COLOR_COMPRESSED_HDR_RGB; + texelFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_RGBA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_R11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_RED; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_XY; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED; } else { return false; } + mipFormat = texelFormat; } else { return false; } return true; -} +} \ No newline at end of file diff --git a/libraries/graphics-scripting/CMakeLists.txt b/libraries/graphics-scripting/CMakeLists.txt index ad8055b647..0f59fb41f8 100644 --- a/libraries/graphics-scripting/CMakeLists.txt +++ b/libraries/graphics-scripting/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME graphics-scripting) setup_hifi_library() -link_hifi_libraries(shared networking graphics fbx model-networking script-engine) +link_hifi_libraries(shared networking graphics fbx image model-networking script-engine) include_hifi_library_headers(gpu) diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index a0e186dda5..ed8e96a12f 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -6,9 +6,14 @@ #include #include #include +#include #include #include + +#include "graphics/Material.h" +#include "graphics/TextureMap.h" + namespace graphics { class Mesh; } @@ -31,6 +36,53 @@ namespace scriptable { using ModelProviderPointer = std::shared_ptr; using WeakModelProviderPointer = std::weak_ptr; + class ScriptableMaterial { + public: + ScriptableMaterial() {} + ScriptableMaterial(const graphics::MaterialPointer& material); + ScriptableMaterial(const ScriptableMaterial& material) { *this = material; } + ScriptableMaterial& operator=(const ScriptableMaterial& material); + + QString name; + QString model; + float opacity; + float roughness; + float metallic; + float scattering; + bool unlit; + glm::vec3 emissive; + glm::vec3 albedo; + QString emissiveMap; + QString albedoMap; + QString opacityMap; + QString metallicMap; + QString specularMap; + QString roughnessMap; + QString glossMap; + QString normalMap; + QString bumpMap; + QString occlusionMap; + QString lightmapMap; + QString scatteringMap; + }; + + /**jsdoc + * @typedef {object} Graphics.MaterialLayer + * @property {Material} material - This layer's material. + * @property {number} priority - The priority of this layer. If multiple materials are applied to a mesh part, only the highest priority layer is used. + */ + class ScriptableMaterialLayer { + public: + ScriptableMaterialLayer() {} + ScriptableMaterialLayer(const graphics::MaterialLayer& materialLayer) : material(materialLayer.material), priority(materialLayer.priority) {} + ScriptableMaterialLayer(const ScriptableMaterialLayer& materialLayer) { *this = materialLayer; } + ScriptableMaterialLayer& operator=(const ScriptableMaterialLayer& materialLayer); + + ScriptableMaterial material; + quint16 priority; + }; + typedef QHash> MultiMaterialMap; + class ScriptableMeshBase : public QObject { Q_OBJECT public: @@ -55,6 +107,8 @@ namespace scriptable { WeakModelProviderPointer provider; QUuid objectID; // spatially nestable ID QVector meshes; + MultiMaterialMap materialLayers; + QVector materialNames; ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} ScriptableModelBase(const ScriptableModelBase& other) : QObject(other.parent()) { *this = other; } @@ -63,9 +117,11 @@ namespace scriptable { void append(const ScriptableMeshBase& mesh); void append(scriptable::WeakMeshPointer mesh); + void appendMaterial(const graphics::MaterialLayer& materialLayer, int shapeID, std::string materialName); + void appendMaterials(const std::unordered_map& materialsToAppend); + void appendMaterialNames(const std::vector& names); // TODO: in future containers for these could go here // QVariantMap shapes; - // QVariantMap materials; // QVariantMap armature; }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index f3ba5e8406..20b54b02c9 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -297,6 +297,10 @@ namespace { qRegisterMetaType(), qRegisterMetaType(), qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType>(), + qRegisterMetaType(), qRegisterMetaType(), }; } @@ -335,6 +339,67 @@ namespace scriptable { ); } + QScriptValue qVectorScriptableMaterialLayerToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorScriptableMaterialLayerFromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QScriptValue scriptableMaterialToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterial &material) { + QScriptValue obj = engine->newObject(); + obj.setProperty("name", material.name); + obj.setProperty("model", material.model); + obj.setProperty("opacity", material.opacity); + obj.setProperty("roughness", material.roughness); + obj.setProperty("metallic", material.metallic); + obj.setProperty("scattering", material.scattering); + obj.setProperty("unlit", material.unlit); + obj.setProperty("emissive", vec3toScriptValue(engine, material.emissive)); + obj.setProperty("albedo", vec3toScriptValue(engine, material.albedo)); + obj.setProperty("emissiveMap", material.emissiveMap); + obj.setProperty("albedoMap", material.albedoMap); + obj.setProperty("opacityMap", material.opacityMap); + obj.setProperty("metallicMap", material.metallicMap); + obj.setProperty("specularMap", material.specularMap); + obj.setProperty("roughnessMap", material.roughnessMap); + obj.setProperty("glossMap", material.glossMap); + obj.setProperty("normalMap", material.normalMap); + obj.setProperty("bumpMap", material.bumpMap); + obj.setProperty("occlusionMap", material.occlusionMap); + obj.setProperty("lightmapMap", material.lightmapMap); + obj.setProperty("scatteringMap", material.scatteringMap); + return obj; + } + + void scriptableMaterialFromScriptValue(const QScriptValue &object, scriptable::ScriptableMaterial& material) { + // No need to convert from QScriptValue to ScriptableMaterial + } + + QScriptValue scriptableMaterialLayerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterialLayer &materialLayer) { + QScriptValue obj = engine->newObject(); + obj.setProperty("material", scriptableMaterialToScriptValue(engine, materialLayer.material)); + obj.setProperty("priority", materialLayer.priority); + return obj; + } + + void scriptableMaterialLayerFromScriptValue(const QScriptValue &object, scriptable::ScriptableMaterialLayer& materialLayer) { + // No need to convert from QScriptValue to ScriptableMaterialLayer + } + + QScriptValue multiMaterialMapToScriptValue(QScriptEngine* engine, const scriptable::MultiMaterialMap& map) { + QScriptValue obj = engine->newObject(); + for (auto key : map.keys()) { + obj.setProperty(key, qVectorScriptableMaterialLayerToScriptValue(engine, map[key])); + } + return obj; + } + + void multiMaterialMapFromScriptValue(const QScriptValue& map, scriptable::MultiMaterialMap& result) { + // No need to convert from QScriptValue to MultiMaterialMap + } + template int registerDebugEnum(QScriptEngine* engine, const DebugEnums& debugEnums) { static const DebugEnums& instance = debugEnums; return qScriptRegisterMetaType( @@ -351,6 +416,7 @@ namespace scriptable { void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); scriptable::registerQPointerMetaType(engine); scriptable::registerQPointerMetaType(engine); @@ -361,6 +427,11 @@ void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { scriptable::registerDebugEnum(engine, gpu::SEMANTICS); scriptable::registerDebugEnum(engine, gpu::DIMENSIONS); + qScriptRegisterMetaType(engine, scriptable::scriptableMaterialToScriptValue, scriptable::scriptableMaterialFromScriptValue); + qScriptRegisterMetaType(engine, scriptable::scriptableMaterialLayerToScriptValue, scriptable::scriptableMaterialLayerFromScriptValue); + qScriptRegisterMetaType(engine, scriptable::qVectorScriptableMaterialLayerToScriptValue, scriptable::qVectorScriptableMaterialLayerFromScriptValue); + qScriptRegisterMetaType(engine, scriptable::multiMaterialMapToScriptValue, scriptable::multiMaterialMapFromScriptValue); + Q_UNUSED(metaTypeIds); } diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 84c6cb6fa8..526352804b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -38,7 +38,7 @@ public slots: * Returns a model reference object associated with the specified UUID ({@link EntityID}, {@link OverlayID}, or {@link AvatarID}). * * @function Graphics.getModel - * @param {UUID} The objectID of the model whose meshes are to be retrieved. + * @param {UUID} entityID - The objectID of the model whose meshes are to be retrieved. * @return {Graphics.Model} the resulting Model object */ scriptable::ScriptableModelPointer getModel(QUuid uuid); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index bc31d45229..8e6d4bec9b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -400,6 +400,5 @@ scriptable::ScriptableMesh::~ScriptableMesh() { strongMesh.reset(); } - #include "ScriptableMesh.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 36322d170d..c65764a225 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -14,14 +14,120 @@ #include +#include "graphics/Material.h" + +#include "image/Image.h" + // #define SCRIPTABLE_MESH_DEBUG 1 +scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const scriptable::ScriptableMaterial& material) { + name = material.name; + model = material.model; + opacity = material.opacity; + roughness = material.roughness; + metallic = material.metallic; + scattering = material.scattering; + unlit = material.unlit; + emissive = material.emissive; + albedo = material.albedo; + emissiveMap = material.emissiveMap; + albedoMap = material.albedoMap; + opacityMap = material.opacityMap; + metallicMap = material.metallicMap; + specularMap = material.specularMap; + roughnessMap = material.roughnessMap; + glossMap = material.glossMap; + normalMap = material.normalMap; + bumpMap = material.bumpMap; + occlusionMap = material.occlusionMap; + lightmapMap = material.lightmapMap; + scatteringMap = material.scatteringMap; + + return *this; +} + +scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) : + name(material->getName().c_str()), + model(material->getModel().c_str()), + opacity(material->getOpacity()), + roughness(material->getRoughness()), + metallic(material->getMetallic()), + scattering(material->getScattering()), + unlit(material->isUnlit()), + emissive(material->getEmissive()), + albedo(material->getAlbedo()) +{ + auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); + if (map && map->getTextureSource()) { + emissiveMap = map->getTextureSource()->getUrl().toString(); + } + + map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP); + if (map && map->getTextureSource()) { + albedoMap = map->getTextureSource()->getUrl().toString(); + if (map->useAlphaChannel()) { + opacityMap = albedoMap; + } + } + + map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP); + if (map && map->getTextureSource()) { + if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) { + metallicMap = map->getTextureSource()->getUrl().toString(); + } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) { + specularMap = map->getTextureSource()->getUrl().toString(); + } + } + + map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP); + if (map && map->getTextureSource()) { + if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) { + roughnessMap = map->getTextureSource()->getUrl().toString(); + } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) { + glossMap = map->getTextureSource()->getUrl().toString(); + } + } + + map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP); + if (map && map->getTextureSource()) { + if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) { + normalMap = map->getTextureSource()->getUrl().toString(); + } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) { + bumpMap = map->getTextureSource()->getUrl().toString(); + } + } + + map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP); + if (map && map->getTextureSource()) { + occlusionMap = map->getTextureSource()->getUrl().toString(); + } + + map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP); + if (map && map->getTextureSource()) { + lightmapMap = map->getTextureSource()->getUrl().toString(); + } + + map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP); + if (map && map->getTextureSource()) { + scatteringMap = map->getTextureSource()->getUrl().toString(); + } +} + +scriptable::ScriptableMaterialLayer& scriptable::ScriptableMaterialLayer::operator=(const scriptable::ScriptableMaterialLayer& materialLayer) { + material = materialLayer.material; + priority = materialLayer.priority; + + return *this; +} + scriptable::ScriptableModelBase& scriptable::ScriptableModelBase::operator=(const scriptable::ScriptableModelBase& other) { provider = other.provider; objectID = other.objectID; for (const auto& mesh : other.meshes) { append(mesh); } + materialLayers = other.materialLayers; + materialNames = other.materialNames; return *this; } @@ -34,6 +140,8 @@ scriptable::ScriptableModelBase::~ScriptableModelBase() { m.strongMesh.reset(); } meshes.clear(); + materialLayers.clear(); + materialNames.clear(); } void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh) { @@ -47,6 +155,27 @@ void scriptable::ScriptableModelBase::append(const ScriptableMeshBase& mesh) { meshes << mesh; } +void scriptable::ScriptableModelBase::appendMaterial(const graphics::MaterialLayer& materialLayer, int shapeID, std::string materialName) { + materialLayers[QString::number(shapeID)].push_back(ScriptableMaterialLayer(materialLayer)); + materialLayers["mat::" + QString::fromStdString(materialName)].push_back(ScriptableMaterialLayer(materialLayer)); +} + +void scriptable::ScriptableModelBase::appendMaterials(const std::unordered_map& materialsToAppend) { + auto materialsToAppendCopy = materialsToAppend; + for (auto& multiMaterial : materialsToAppendCopy) { + while (!multiMaterial.second.empty()) { + materialLayers[QString(multiMaterial.first.c_str())].push_back(ScriptableMaterialLayer(multiMaterial.second.top())); + multiMaterial.second.pop(); + } + } +} + +void scriptable::ScriptableModelBase::appendMaterialNames(const std::vector& names) { + for (auto& name : names) { + materialNames.append(QString::fromStdString(name)); + } +} + QString scriptable::ScriptableModel::toString() const { return QString("[ScriptableModel%1%2 numMeshes=%3]") .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 9a4c4f695b..ac0b7b9623 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -21,6 +21,8 @@ namespace scriptable { * @property {Uuid} objectID - UUID of corresponding inworld object (if model is associated) * @property {number} numMeshes - The number of submeshes contained in the model. * @property {Graphics.Mesh[]} meshes - Array of submesh references. + * @property {Object.} materialLayers - Map of materials layer lists. You can look up a material layer list by mesh part number or by material name. + * @property {string[]} materialNames - Array of all the material names used by the mesh parts of this model, in order (e.g. materialNames[0] is the name of the first mesh part's material). */ class ScriptableModel : public ScriptableModelBase { @@ -28,18 +30,24 @@ namespace scriptable { Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) Q_PROPERTY(glm::uint32 numMeshes READ getNumMeshes) Q_PROPERTY(ScriptableMeshes meshes READ getMeshes) + Q_PROPERTY(scriptable::MultiMaterialMap materialLayers READ getMaterialLayers) + Q_PROPERTY(QVector materialNames READ getMaterialNames) public: ScriptableModel(QObject* parent = nullptr) : ScriptableModelBase(parent) {} ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } + operator scriptable::ScriptableModelBasePointer() { return QPointer(qobject_cast(this)); } ScriptableMeshes getMeshes(); const ScriptableMeshes getConstMeshes() const; + scriptable::MultiMaterialMap getMaterialLayers() { return materialLayers; } + QVector getMaterialNames() { return materialNames; } + public slots: scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); QString toString() const; @@ -53,3 +61,6 @@ namespace scriptable { Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::ScriptableMaterial) +Q_DECLARE_METATYPE(scriptable::ScriptableMaterialLayer) +Q_DECLARE_METATYPE(scriptable::MultiMaterialMap) diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index a75fb1bf62..485542d26b 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -74,7 +74,7 @@ public: size_t getNumIndices() const { return _indexBuffer.getNumElements(); } // Access vertex position value - const Vec3& getPos3(Index index) const { return _vertexBuffer.get(index); } + const Vec3& getPos(Index index) const { return _vertexBuffer.get(index); } enum Topology { POINTS = 0, diff --git a/libraries/graphics/src/graphics/GpuHelpers.cpp b/libraries/graphics/src/graphics/GpuHelpers.cpp index 0c3bd945e1..b864b0f040 100644 --- a/libraries/graphics/src/graphics/GpuHelpers.cpp +++ b/libraries/graphics/src/graphics/GpuHelpers.cpp @@ -87,6 +87,17 @@ namespace gpu { { Semantic::COMPRESSED_BC6_RGB, "compressed_bc6_rgb" }, { Semantic::COMPRESSED_BC7_SRGBA, "compressed_bc7_srgba" }, + { Semantic::COMPRESSED_ETC2_RGB, "compressed_etc2_rgb" }, + { Semantic::COMPRESSED_ETC2_SRGB, "compressed_etc2_srgb" }, + { Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, "compressed_etc2_rgb_punchthrough_alpha" }, + { Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, "compressed_etc2_srgb_punchthrough_alpha" }, + { Semantic::COMPRESSED_ETC2_RGBA, "compressed_etc2_rgba" }, + { Semantic::COMPRESSED_ETC2_SRGBA, "compressed_etc2_srgba" }, + { Semantic::COMPRESSED_EAC_RED, "compressed_eac_red" }, + { Semantic::COMPRESSED_EAC_RED_SIGNED, "compressed_eac_red_signed" }, + { Semantic::COMPRESSED_EAC_XY, "compressed_eac_xy" }, + { Semantic::COMPRESSED_EAC_XY_SIGNED, "compressed_eac_xy_signed" }, + { Semantic::_LAST_COMPRESSED, "_last_compressed" }, { Semantic::R11G11B10, "r11g11b10" }, diff --git a/libraries/graphics/src/graphics/Light.cpp b/libraries/graphics/src/graphics/Light.cpp index 9da14fec4f..76d8a6030a 100755 --- a/libraries/graphics/src/graphics/Light.cpp +++ b/libraries/graphics/src/graphics/Light.cpp @@ -65,6 +65,14 @@ const Vec3& Light::getDirection() const { return _lightSchemaBuffer->volume.direction; } +void Light::setCastShadows(const bool castShadows) { + _castShadows = castShadows; +} + +bool Light::getCastShadows() const { + return _castShadows; +} + void Light::setColor(const Color& color) { _lightSchemaBuffer.edit().irradiance.color = color; updateLightRadius(); @@ -132,7 +140,6 @@ void Light::setSpotExponent(float exponent) { _lightSchemaBuffer.edit().irradiance.falloffSpot = exponent; } - void Light::setAmbientIntensity(float intensity) { _ambientSchemaBuffer.edit().intensity = intensity; } diff --git a/libraries/graphics/src/graphics/Light.h b/libraries/graphics/src/graphics/Light.h index e6ef1e35c5..bb9fb3e5b9 100755 --- a/libraries/graphics/src/graphics/Light.h +++ b/libraries/graphics/src/graphics/Light.h @@ -103,6 +103,9 @@ public: void setDirection(const Vec3& direction); const Vec3& getDirection() const; + void setCastShadows(const bool castShadows); + bool getCastShadows() const; + void setOrientation(const Quat& orientation); const glm::quat& getOrientation() const { return _transform.getRotation(); } @@ -191,6 +194,8 @@ protected: void updateLightRadius(); + bool _castShadows{ false }; + }; typedef std::shared_ptr< Light > LightPointer; diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 632cf99391..fd3f12e865 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -20,6 +20,7 @@ #include #include +#include class Transform; @@ -356,10 +357,13 @@ public: void setTextureTransforms(const Transform& transform); - const std::string& getName() { return _name; } + const std::string& getName() const { return _name; } + const std::string& getModel() const { return _model; } void setModel(const std::string& model) { _model = model; } + const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } + protected: std::string _name { "" }; @@ -367,6 +371,7 @@ private: mutable MaterialKey _key; mutable UniformBufferView _schemaBuffer; mutable UniformBufferView _texMapArrayBuffer; + mutable gpu::TextureTablePointer _textureTable{ std::make_shared() }; TextureMaps _textureMaps; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 696e311495..c1c07838c7 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -466,7 +466,8 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomicgetStoredMipFormat(); - if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGB) { + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { compressionOptions.setFormat(nvtt::Format_BC1); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_MASK) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { alphaMode = nvtt::AlphaMode_Transparency; compressionOptions.setFormat(nvtt::Format_BC1a); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { alphaMode = nvtt::AlphaMode_Transparency; compressionOptions.setFormat(nvtt::Format_BC3); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_RED) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { compressionOptions.setFormat(nvtt::Format_BC4); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_XY) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { compressionOptions.setFormat(nvtt::Format_BC5); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { alphaMode = nvtt::AlphaMode_Transparency; compressionOptions.setFormat(nvtt::Format_BC7); } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { @@ -736,13 +738,21 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma gpu::Element formatMip; gpu::Element formatGPU; if (isColorTexturesCompressionEnabled()) { +#ifndef USE_GLES if (validAlpha) { // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; } +#else + if (validAlpha) { + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; + } +#endif formatMip = formatGPU; } else { #ifdef USE_GLES @@ -869,8 +879,12 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr gpu::Element formatMip = gpu::Element::VEC2NU8_XY; gpu::Element formatGPU = gpu::Element::VEC2NU8_XY; if (isNormalTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_XY; - formatGPU = gpu::Element::COLOR_COMPRESSED_XY; +#ifndef USE_GLES + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; +#else + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; +#endif + formatMip = formatGPU; } theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); @@ -903,8 +917,12 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (isGrayscaleTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_RED; - formatGPU = gpu::Element::COLOR_COMPRESSED_RED; +#ifndef USE_GLES + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; +#else + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; +#endif + formatMip = formatGPU; } else { formatMip = gpu::Element::COLOR_R_8; formatGPU = gpu::Element::COLOR_R_8; @@ -1271,8 +1289,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + // TODO: gles: pick HDR ETC format + formatMip = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; } else { formatMip = HDR_FORMAT; formatGPU = HDR_FORMAT; diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 96f1daba8c..6f94e7592c 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -20,9 +20,10 @@ InputPluginList getInputPlugins() { InputPlugin* PLUGIN_POOL[] = { new KeyboardMouseDevice(), - new TouchscreenDevice(), #if defined(Q_OS_ANDROID) new TouchscreenVirtualPadDevice(), +#else + new TouchscreenDevice(), // Touchscreen and Controller Scripts take care on Android #endif nullptr }; diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 8e1e6c2fba..957104bd30 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -22,6 +22,8 @@ #include #include "VirtualPadManager.h" +#include + const char* TouchscreenVirtualPadDevice::NAME = "TouchscreenVirtualPad"; bool TouchscreenVirtualPadDevice::isSupported() const { @@ -37,10 +39,116 @@ bool TouchscreenVirtualPadDevice::isSupported() const { return false; } +void TouchscreenVirtualPadDevice::init() { + _fixedPosition = true; // This should be config + _viewTouchUpdateCount = 0; + + QScreen* eventScreen = qApp->primaryScreen(); + if (_screenDPIProvided != eventScreen->physicalDotsPerInch()) { + _screenWidthCenter = eventScreen->size().width() / 2; + _screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX(); + _screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY(); + _screenDPIProvided = eventScreen->physicalDotsPerInch(); + _screenDPI = eventScreen->physicalDotsPerInch(); + + _fixedRadius = _screenDPI * 0.5f * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI; + _fixedRadiusForCalc = _fixedRadius - _screenDPI * VirtualPad::Manager::STICK_RADIUS_PIXELS / VirtualPad::Manager::DPI; + + _jumpButtonRadius = _screenDPI * VirtualPad::Manager::JUMP_BTN_TRIMMED_RADIUS_PIXELS / VirtualPad::Manager::DPI; + } + + auto& virtualPadManager = VirtualPad::Manager::instance(); + setupControlsPositions(virtualPadManager, true); + + if (_fixedPosition) { + virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled + } + + KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin +} + +void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& virtualPadManager, bool force) { + if (_extraBottomMargin == virtualPadManager.extraBottomMargin() && !force) return; // Our only criteria to decide a center change is the bottom margin + + QScreen* eventScreen = qApp->primaryScreen(); // do not call every time + _extraBottomMargin = virtualPadManager.extraBottomMargin(); + + // Movement stick + float margin = _screenDPI * VirtualPad::Manager::BASE_MARGIN_PIXELS / VirtualPad::Manager::DPI; + _fixedCenterPosition = glm::vec2( _fixedRadius + margin, eventScreen->size().height() - margin - _fixedRadius - _extraBottomMargin); + _moveRefTouchPoint = _fixedCenterPosition; + virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); + + // Jump button + float leftMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_LEFT_MARGIN_PIXELS / VirtualPad::Manager::DPI; + float bottomMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS/ VirtualPad::Manager::DPI; + _jumpButtonPosition = glm::vec2( _jumpButtonRadius + leftMargin, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin); + virtualPadManager.setJumpButtonPosition(_jumpButtonPosition); +} + float clip(float n, float lower, float upper) { return std::max(lower, std::min(n, upper)); } +glm::vec2 TouchscreenVirtualPadDevice::clippedPointInCircle(float radius, glm::vec2 origin, glm::vec2 touchPoint) { + float deltaX = touchPoint.x - origin.x; + float deltaY = touchPoint.y - origin.y; + + float distance = sqrt(pow(deltaX, 2) + pow(deltaY, 2)); + + // First case, inside the boundaires, just use the distance + if (distance <= radius) { + return touchPoint; + } + + // Second case, purely vertical (avoid division by zero) + if (deltaX == 0.0f) { + return vec2(touchPoint.x, clip(touchPoint.y, origin.y-radius, origin.y+radius) ); + } + + // Third case, calculate point in circumference + // line formula + float m = deltaY / deltaX; + float b = touchPoint.y - m * touchPoint.x; + + // quadtratic coefs of circumference and line intersection + float qa = powf(m, 2.0f) + 1.0f; + float qb = 2.0f * ( m * b - origin.x - origin.y * m); + float qc = powf(origin.x, 2.0f) - powf(radius, 2.0f) + b * b - 2.0f * b * origin.y + powf(origin.y, 2.0f); + + float discr = qb * qb - 4.0f * qa * qc; + float discrSign = deltaX > 0.0f ? 1.0f : - 1.0f; + + float finalX = (-qb + discrSign * sqrtf(discr)) / (2.0f * qa); + float finalY = m * finalX + b; + + return vec2(finalX, finalY); +} + +void TouchscreenVirtualPadDevice::processInputDeviceForMove(VirtualPad::Manager& virtualPadManager) { + vec2 clippedPoint = clippedPointInCircle(_fixedRadiusForCalc, _moveRefTouchPoint, _moveCurrentTouchPoint); + + _inputDevice->_axisStateMap[controller::LX] = (clippedPoint.x - _moveRefTouchPoint.x) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LY] = (clippedPoint.y - _moveRefTouchPoint.y) / _fixedRadiusForCalc; + + virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); + virtualPadManager.getLeftVirtualPad()->setCurrentTouch(clippedPoint); + virtualPadManager.getLeftVirtualPad()->setBeingTouched(true); + virtualPadManager.getLeftVirtualPad()->setShown(true); // If touched, show in any mode (fixed joystick position or non-fixed) +} + +void TouchscreenVirtualPadDevice::processInputDeviceForView() { + // We use average across how many times we've got touchUpdate events. + // Using the average instead of the full deltaX and deltaY, makes deltaTime in MyAvatar dont't accelerate rotation when there is a low touchUpdate rate (heavier domains). + // (Because it multiplies this input value by deltaTime (with a coefficient)). + _inputDevice->_axisStateMap[controller::RX] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _viewTouchUpdateCount; + _inputDevice->_axisStateMap[controller::RY] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _viewTouchUpdateCount; + + // after use, save last touch point as ref + _viewRefTouchPoint = _viewCurrentTouchPoint; + _viewTouchUpdateCount = 0; +} + void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { @@ -48,47 +156,22 @@ void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller }); auto& virtualPadManager = VirtualPad::Manager::instance(); - if (_validTouchLeft) { - float leftDistanceScaleX, leftDistanceScaleY; - leftDistanceScaleX = (_currentTouchLeftPoint.x - _firstTouchLeftPoint.x) / _screenDPIScale.x; - leftDistanceScaleY = (_currentTouchLeftPoint.y - _firstTouchLeftPoint.y) / _screenDPIScale.y; + setupControlsPositions(virtualPadManager); - leftDistanceScaleX = clip(leftDistanceScaleX, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - leftDistanceScaleY = clip(leftDistanceScaleY, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - - // NOW BETWEEN -1 1 - leftDistanceScaleX /= STICK_RADIUS_INCHES; - leftDistanceScaleY /= STICK_RADIUS_INCHES; - - _inputDevice->_axisStateMap[controller::LX] = leftDistanceScaleX; - _inputDevice->_axisStateMap[controller::LY] = leftDistanceScaleY; - - /* Shared variables for stick rendering (clipped to the stick radius)*/ - // Prevent this for being done when not in first person view - virtualPadManager.getLeftVirtualPad()->setBeingTouched(true); - virtualPadManager.getLeftVirtualPad()->setFirstTouch(_firstTouchLeftPoint); - virtualPadManager.getLeftVirtualPad()->setCurrentTouch( - glm::vec2(clip(_currentTouchLeftPoint.x, -STICK_RADIUS_INCHES * _screenDPIScale.x + _firstTouchLeftPoint.x, STICK_RADIUS_INCHES * _screenDPIScale.x + _firstTouchLeftPoint.x), - clip(_currentTouchLeftPoint.y, -STICK_RADIUS_INCHES * _screenDPIScale.y + _firstTouchLeftPoint.y, STICK_RADIUS_INCHES * _screenDPIScale.y + _firstTouchLeftPoint.y)) - ); + if (_moveHasValidTouch) { + processInputDeviceForMove(virtualPadManager); } else { virtualPadManager.getLeftVirtualPad()->setBeingTouched(false); + if (_fixedPosition) { + virtualPadManager.getLeftVirtualPad()->setCurrentTouch(_fixedCenterPosition); // reset to the center + virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled + } else { + virtualPadManager.getLeftVirtualPad()->setShown(false); + } } - if (_validTouchRight) { - float rightDistanceScaleX, rightDistanceScaleY; - rightDistanceScaleX = (_currentTouchRightPoint.x - _firstTouchRightPoint.x) / _screenDPIScale.x; - rightDistanceScaleY = (_currentTouchRightPoint.y - _firstTouchRightPoint.y) / _screenDPIScale.y; - - rightDistanceScaleX = clip(rightDistanceScaleX, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - rightDistanceScaleY = clip(rightDistanceScaleY, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - - // NOW BETWEEN -1 1 - rightDistanceScaleX /= STICK_RADIUS_INCHES; - rightDistanceScaleY /= STICK_RADIUS_INCHES; - - _inputDevice->_axisStateMap[controller::RX] = rightDistanceScaleX; - _inputDevice->_axisStateMap[controller::RY] = rightDistanceScaleY; + if (_viewHasValidTouch) { + processInputDeviceForView(); } } @@ -127,116 +210,272 @@ void TouchscreenVirtualPadDevice::debugPoints(const QTouchEvent* event, QString void TouchscreenVirtualPadDevice::touchBeginEvent(const QTouchEvent* event) { // touch begin here is a big begin -> begins both pads? maybe it does nothing debugPoints(event, " BEGIN ++++++++++++++++"); - KeyboardMouseDevice::enableTouch(false); - QScreen* eventScreen = event->window()->screen(); - _screenWidthCenter = eventScreen->size().width() / 2; - if (_screenDPI != eventScreen->physicalDotsPerInch()) { - _screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX(); - _screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY(); - _screenDPI = eventScreen->physicalDotsPerInch(); + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + return; } } void TouchscreenVirtualPadDevice::touchEndEvent(const QTouchEvent* event) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + moveTouchEnd(); + viewTouchEnd(); + jumpTouchEnd(); + return; + } // touch end here is a big reset -> resets both pads _touchPointCount = 0; - KeyboardMouseDevice::enableTouch(true); + _unusedTouches.clear(); debugPoints(event, " END ----------------"); - touchLeftEnd(); - touchRightEnd(); + moveTouchEnd(); + viewTouchEnd(); + jumpTouchEnd(); _inputDevice->_axisStateMap.clear(); + _inputDevice->_buttonPressedMap.clear(); +} + +void TouchscreenVirtualPadDevice::processUnusedTouches(std::map unusedTouchesInEvent) { + std::vector touchesToDelete; + for (auto const& touchEntry : _unusedTouches) { + if (!unusedTouchesInEvent.count(touchEntry.first)) { + touchesToDelete.push_back(touchEntry.first); + } + } + for (int touchToDelete : touchesToDelete) { + _unusedTouches.erase(touchToDelete); + } + + for (auto const& touchEntry : unusedTouchesInEvent) { + if (!_unusedTouches.count(touchEntry.first)) { + _unusedTouches[touchEntry.first] = touchEntry.second; + } + } + } void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + moveTouchEnd(); + viewTouchEnd(); + return; + } _touchPointCount = event->touchPoints().count(); const QList& tPoints = event->touchPoints(); - bool leftTouchFound = false; - bool rightTouchFound = false; + bool moveTouchFound = false; + bool viewTouchFound = false; + bool jumpTouchFound = false; + + int idxMoveStartingPointCandidate = -1; + int idxViewStartingPointCandidate = -1; + int idxJumpStartingPointCandidate = -1; + + glm::vec2 thisPoint; + int thisPointId; + std::map unusedTouchesInEvent; + for (int i = 0; i < _touchPointCount; ++i) { - glm::vec2 thisPoint(tPoints[i].pos().x(), tPoints[i].pos().y()); - if (thisPoint.x < _screenWidthCenter) { - if (!leftTouchFound) { - leftTouchFound = true; - if (!_validTouchLeft) { - touchLeftBegin(thisPoint); - } else { - touchLeftUpdate(thisPoint); - } - } + thisPoint.x = tPoints[i].pos().x(); + thisPoint.y = tPoints[i].pos().y(); + thisPointId = tPoints[i].id(); + + if (!moveTouchFound && _moveHasValidTouch && _moveCurrentTouchId == thisPointId) { + // valid if it's an ongoing touch + moveTouchFound = true; + moveTouchUpdate(thisPoint); + continue; + } + + if (!viewTouchFound && _viewHasValidTouch && _viewCurrentTouchId == thisPointId) { + // valid if it's an ongoing touch + viewTouchFound = true; + viewTouchUpdate(thisPoint); + continue; + } + + if (!jumpTouchFound && _jumpHasValidTouch && _jumpCurrentTouchId == thisPointId) { + // valid if it's an ongoing touch + jumpTouchFound = true; + jumpTouchUpdate(thisPoint); + continue; + } + + if (!moveTouchFound && idxMoveStartingPointCandidate == -1 && moveTouchBeginIsValid(thisPoint) && + (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == MOVE )) { + idxMoveStartingPointCandidate = i; + continue; + } + + if (!viewTouchFound && idxViewStartingPointCandidate == -1 && viewTouchBeginIsValid(thisPoint) && + (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == VIEW )) { + idxViewStartingPointCandidate = i; + continue; + } + + if (!jumpTouchFound && idxJumpStartingPointCandidate == -1 && jumpTouchBeginIsValid(thisPoint) && + (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == JUMP )) { + idxJumpStartingPointCandidate = i; + continue; + } + + if (moveTouchBeginIsValid(thisPoint)) { + unusedTouchesInEvent[thisPointId] = MOVE; + } else if (jumpTouchBeginIsValid(thisPoint)) { + unusedTouchesInEvent[thisPointId] = JUMP; + } else if (viewTouchBeginIsValid(thisPoint)) { + unusedTouchesInEvent[thisPointId] = VIEW; + } + + } + + processUnusedTouches(unusedTouchesInEvent); + + if (!moveTouchFound) { + if (idxMoveStartingPointCandidate != -1) { + _moveCurrentTouchId = tPoints[idxMoveStartingPointCandidate].id(); + _unusedTouches.erase(_moveCurrentTouchId); + moveTouchBegin(thisPoint); } else { - if (!rightTouchFound) { - rightTouchFound = true; - if (!_validTouchRight) { - touchRightBegin(thisPoint); - } else { - touchRightUpdate(thisPoint); - } + moveTouchEnd(); + } + } + if (!viewTouchFound) { + if (idxViewStartingPointCandidate != -1) { + _viewCurrentTouchId = tPoints[idxViewStartingPointCandidate].id(); + _unusedTouches.erase(_viewCurrentTouchId); + viewTouchBegin(thisPoint); + } else { + viewTouchEnd(); + } + } + if (!jumpTouchFound) { + if (idxJumpStartingPointCandidate != -1) { + _jumpCurrentTouchId = tPoints[idxJumpStartingPointCandidate].id(); + _unusedTouches.erase(_jumpCurrentTouchId); + jumpTouchBegin(thisPoint); + } else { + if (_jumpHasValidTouch) { + jumpTouchEnd(); } } } - if (!leftTouchFound) { - touchLeftEnd(); - } - if (!rightTouchFound) { - touchRightEnd(); + +} + +bool TouchscreenVirtualPadDevice::viewTouchBeginIsValid(glm::vec2 touchPoint) { + return !moveTouchBeginIsValid(touchPoint) && !jumpTouchBeginIsValid(touchPoint); +} + +bool TouchscreenVirtualPadDevice::moveTouchBeginIsValid(glm::vec2 touchPoint) { + if (_fixedPosition) { + // inside circle + return glm::distance2(touchPoint, _fixedCenterPosition) < _fixedRadius * _fixedRadius; + } else { + // left side + return touchPoint.x < _screenWidthCenter; } } -void TouchscreenVirtualPadDevice::touchLeftBegin(glm::vec2 touchPoint) { +bool TouchscreenVirtualPadDevice::jumpTouchBeginIsValid(glm::vec2 touchPoint) { + // position of button and boundaries + return glm::distance2(touchPoint, _jumpButtonPosition) < _jumpButtonRadius * _jumpButtonRadius; +} + +void TouchscreenVirtualPadDevice::jumpTouchBegin(glm::vec2 touchPoint) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (virtualPadManager.isEnabled()) { - _firstTouchLeftPoint = touchPoint; - _validTouchLeft = true; + if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + _jumpHasValidTouch = true; + + _inputDevice->_buttonPressedMap.insert(TouchButtonChannel::JUMP_BUTTON_PRESS); } } -void TouchscreenVirtualPadDevice::touchLeftUpdate(glm::vec2 touchPoint) { - _currentTouchLeftPoint = touchPoint; +void TouchscreenVirtualPadDevice::jumpTouchUpdate(glm::vec2 touchPoint) {} + +void TouchscreenVirtualPadDevice::jumpTouchEnd() { + if (_jumpHasValidTouch) { + _jumpHasValidTouch = false; + + _inputDevice->_buttonPressedMap.erase(TouchButtonChannel::JUMP_BUTTON_PRESS); + } } -void TouchscreenVirtualPadDevice::touchLeftEnd() { - if (_validTouchLeft) { // do stuff once - _validTouchLeft = false; +void TouchscreenVirtualPadDevice::moveTouchBegin(glm::vec2 touchPoint) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + if (_fixedPosition) { + _moveRefTouchPoint = _fixedCenterPosition; + } else { + _moveRefTouchPoint = touchPoint; + } + _moveHasValidTouch = true; + } +} + +void TouchscreenVirtualPadDevice::moveTouchUpdate(glm::vec2 touchPoint) { + _moveCurrentTouchPoint = touchPoint; +} + +void TouchscreenVirtualPadDevice::moveTouchEnd() { + if (_moveHasValidTouch) { // do stuff once + _moveHasValidTouch = false; _inputDevice->_axisStateMap[controller::LX] = 0; _inputDevice->_axisStateMap[controller::LY] = 0; } } -void TouchscreenVirtualPadDevice::touchRightBegin(glm::vec2 touchPoint) { +void TouchscreenVirtualPadDevice::viewTouchBegin(glm::vec2 touchPoint) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (virtualPadManager.isEnabled()) { - _firstTouchRightPoint = touchPoint; - _validTouchRight = true; + if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + _viewRefTouchPoint = touchPoint; + _viewCurrentTouchPoint = touchPoint; + _viewTouchUpdateCount++; + _viewHasValidTouch = true; } } -void TouchscreenVirtualPadDevice::touchRightUpdate(glm::vec2 touchPoint) { - _currentTouchRightPoint = touchPoint; +void TouchscreenVirtualPadDevice::viewTouchUpdate(glm::vec2 touchPoint) { + _viewCurrentTouchPoint = touchPoint; + _viewTouchUpdateCount++; } -void TouchscreenVirtualPadDevice::touchRightEnd() { - if (_validTouchRight) { // do stuff once - _validTouchRight = false; +void TouchscreenVirtualPadDevice::viewTouchEnd() { + if (_viewHasValidTouch) { // do stuff once + _viewHasValidTouch = false; _inputDevice->_axisStateMap[controller::RX] = 0; _inputDevice->_axisStateMap[controller::RY] = 0; } } void TouchscreenVirtualPadDevice::touchGestureEvent(const QGestureEvent* event) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + return; + } if (QGesture* gesture = event->gesture(Qt::PinchGesture)) { QPinchGesture* pinch = static_cast(gesture); _pinchScale = pinch->totalScaleFactor(); } } +controller::Input TouchscreenVirtualPadDevice::InputDevice::makeInput(TouchscreenVirtualPadDevice::TouchAxisChannel axis) const { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); +} + +controller::Input TouchscreenVirtualPadDevice::InputDevice::makeInput(TouchscreenVirtualPadDevice::TouchButtonChannel button) const { + return controller::Input(_deviceID, button, controller::ChannelType::BUTTON); +} + controller::Input::NamedVector TouchscreenVirtualPadDevice::InputDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ - makePair(LX, "LX"), - makePair(LY, "LY"), - makePair(RX, "RX"), - makePair(RY, "RY") + Input::NamedPair(makeInput(TouchAxisChannel::LX), "LX"), + Input::NamedPair(makeInput(TouchAxisChannel::LY), "LY"), + Input::NamedPair(makeInput(TouchAxisChannel::RX), "RX"), + Input::NamedPair(makeInput(TouchAxisChannel::RY), "RY"), + Input::NamedPair(makeInput(TouchButtonChannel::JUMP_BUTTON_PRESS), "JUMP_BUTTON_PRESS") }; return availableInputs; } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h index fd74009ee8..212b7633ec 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h @@ -15,17 +15,17 @@ #include #include "InputPlugin.h" #include +#include "VirtualPadManager.h" class QTouchEvent; class QGestureEvent; -const float STICK_RADIUS_INCHES = .3f; - class TouchscreenVirtualPadDevice : public InputPlugin { Q_OBJECT public: // Plugin functions + virtual void init() override; virtual bool isSupported() const override; virtual const QString getName() const override { return NAME; } @@ -41,6 +41,18 @@ public: static const char* NAME; + int _viewTouchUpdateCount; + enum TouchAxisChannel { + LX, + LY, + RX, + RY + }; + + enum TouchButtonChannel { + JUMP_BUTTON_PRESS + }; + protected: class InputDevice : public controller::InputDevice { @@ -54,32 +66,79 @@ protected: virtual void focusOutEvent() override; friend class TouchscreenVirtualPadDevice; + + controller::Input makeInput(TouchAxisChannel axis) const; + controller::Input makeInput(TouchButtonChannel button) const; }; public: const std::shared_ptr& getInputDevice() const { return _inputDevice; } protected: - qreal _lastPinchScale; - qreal _pinchScale; - qreal _screenDPI; + + enum TouchType { + MOVE = 1, + VIEW, + JUMP + }; + + float _lastPinchScale; + float _pinchScale; + float _screenDPI; + qreal _screenDPIProvided; glm::vec2 _screenDPIScale; - bool _validTouchLeft; - glm::vec2 _firstTouchLeftPoint; - glm::vec2 _currentTouchLeftPoint; - bool _validTouchRight; - glm::vec2 _firstTouchRightPoint; - glm::vec2 _currentTouchRightPoint; + + bool _moveHasValidTouch; + glm::vec2 _moveRefTouchPoint; + glm::vec2 _moveCurrentTouchPoint; + int _moveCurrentTouchId; + + bool _viewHasValidTouch; + glm::vec2 _viewRefTouchPoint; + glm::vec2 _viewCurrentTouchPoint; + int _viewCurrentTouchId; + + bool _jumpHasValidTouch; + int _jumpCurrentTouchId; + + std::map _unusedTouches; + int _touchPointCount; int _screenWidthCenter; std::shared_ptr _inputDevice { std::make_shared() }; - void touchLeftBegin(glm::vec2 touchPoint); - void touchLeftUpdate(glm::vec2 touchPoint); - void touchLeftEnd(); - void touchRightBegin(glm::vec2 touchPoint); - void touchRightUpdate(glm::vec2 touchPoint); - void touchRightEnd(); + bool _fixedPosition; + glm::vec2 _fixedCenterPosition; + float _fixedRadius; + float _fixedRadiusForCalc; + int _extraBottomMargin {0}; + + glm::vec2 _jumpButtonPosition; + float _jumpButtonRadius; + + void moveTouchBegin(glm::vec2 touchPoint); + void moveTouchUpdate(glm::vec2 touchPoint); + void moveTouchEnd(); + bool moveTouchBeginIsValid(glm::vec2 touchPoint); + + void viewTouchBegin(glm::vec2 touchPoint); + void viewTouchUpdate(glm::vec2 touchPoint); + void viewTouchEnd(); + bool viewTouchBeginIsValid(glm::vec2 touchPoint); + + void jumpTouchBegin(glm::vec2 touchPoint); + void jumpTouchUpdate(glm::vec2 touchPoint); + void jumpTouchEnd(); + bool jumpTouchBeginIsValid(glm::vec2 touchPoint); + + void setupControlsPositions(VirtualPad::Manager& virtualPadManager, bool force = false); + + void processInputDeviceForMove(VirtualPad::Manager& virtualPadManager); + glm::vec2 clippedPointInCircle(float radius, glm::vec2 origin, glm::vec2 touchPoint); + + void processUnusedTouches(std::map unusedTouchesInEvent); + + void processInputDeviceForView(); // just for debug private: void debugPoints(const QTouchEvent* event, QString who); diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index cda22513ee..4ee893e4fc 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -358,6 +358,17 @@ namespace khronos { case InternalFormat::COMPRESSED_RG_RGTC2: // BC5 case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: // BC6 case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7 + // ETC2 / EAC + case InternalFormat::COMPRESSED_RGB8_ETC2: + case InternalFormat::COMPRESSED_SRGB8_ETC2: + case InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_RGBA8_ETC2_EAC: + case InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case InternalFormat::COMPRESSED_R11_EAC: + case InternalFormat::COMPRESSED_SIGNED_R11_EAC: + case InternalFormat::COMPRESSED_RG11_EAC: + case InternalFormat::COMPRESSED_SIGNED_RG11_EAC: return evalAlignedCompressedBlockCount<4>(value); default: @@ -370,12 +381,22 @@ namespace khronos { case InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case InternalFormat::COMPRESSED_RED_RGTC1: + case InternalFormat::COMPRESSED_RGB8_ETC2: + case InternalFormat::COMPRESSED_SRGB8_ETC2: + case InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_R11_EAC: + case InternalFormat::COMPRESSED_SIGNED_R11_EAC: return 8; case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case InternalFormat::COMPRESSED_RG_RGTC2: + case InternalFormat::COMPRESSED_RGBA8_ETC2_EAC: + case InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case InternalFormat::COMPRESSED_RG11_EAC: + case InternalFormat::COMPRESSED_SIGNED_RG11_EAC: return 16; default: diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index 8d9d6571f8..f0cbfc914a 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -11,6 +11,8 @@ #include "QJsonDocument" #include "QJsonArray" +#include "RegisteredMetaTypes.h" + NetworkMaterialResource::NetworkMaterialResource(const QUrl& url) : Resource(url) {} @@ -18,7 +20,7 @@ void NetworkMaterialResource::downloadFinished(const QByteArray& data) { parsedMaterials.reset(); if (_url.toString().contains(".json")) { - parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data)); + parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data), _url); } // TODO: parse other material types @@ -26,6 +28,25 @@ void NetworkMaterialResource::downloadFinished(const QByteArray& data) { finishedLoading(true); } +/**jsdoc + *

An RGB or SRGB color value.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
IndexTypeAttributesDefaultValue
0numberRed component value. Number in the range 0.01.0.
1numberGreen component value. Number in the range 0.01.0.
2numberBlue component value. Number in the range 0.01.0.
3boolean<optional>falseIf true then the color is an SRGB color.
+ * @typedef {array} RGBS + */ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB) { if (array.isArray()) { QJsonArray colorArray = array.toArray(); @@ -39,11 +60,22 @@ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& color = glm::vec3(colorArray[0].toDouble(), colorArray[1].toDouble(), colorArray[2].toDouble()); return true; } + } else if (array.isObject()) { + bool toReturn; + isSRGB = true; + color = vec3FromVariant(array.toObject(), toReturn); + return toReturn; } return false; } -NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON) { +/**jsdoc + * A material or set of materials such as may be used by a {@link Entities.EntityType|Material} entity. + * @typedef {object} MaterialResource + * @property {number} materialVersion=1 - The version of the material. Currently not used. + * @property {Material|Material[]} materials - The details of the material or materials. + */ +NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON, const QUrl& baseUrl) { ParsedMaterials toReturn; if (!materialJSON.isNull() && materialJSON.isObject()) { QJsonObject materialJSONObject = materialJSON.object(); @@ -59,13 +91,13 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater QJsonArray materials = materialsValue.toArray(); for (auto material : materials) { if (!material.isNull() && material.isObject()) { - auto parsedMaterial = parseJSONMaterial(material.toObject()); + auto parsedMaterial = parseJSONMaterial(material.toObject(), baseUrl); toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second; toReturn.names.push_back(parsedMaterial.first); } } } else if (materialsValue.isObject()) { - auto parsedMaterial = parseJSONMaterial(materialsValue.toObject()); + auto parsedMaterial = parseJSONMaterial(materialsValue.toObject(), baseUrl); toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second; toReturn.names.push_back(parsedMaterial.first); } @@ -76,7 +108,37 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater return toReturn; } -std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) { +/**jsdoc + * A material such as may be used by a {@link Entities.EntityType|Material} entity. + * @typedef {object} Material + * @property {string} name="" - A name for the material. + * @property {string} model="hifi_pbr" - Currently not used. + * @property {Vec3Color|RGBS} emissive - The emissive color, i.e., the color that the material emits. A {@link Vec3Color} value + * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. + * @property {number} opacity=1.0 - The opacity, 0.01.0. + * @property {boolean} unlit=false - If true, the material is not lit. + * @property {Vec3Color|RGBS} albedo - The albedo color. A {@link Vec3Color} value is treated as sRGB. A {@link RGBS} value can + * be either RGB or sRGB. + * @property {number} roughness - The roughness, 0.01.0. + * @property {number} metallic - The metallicness, 0.01.0. + * @property {number} scattering - The scattering, 0.01.0. + * @property {string} emissiveMap - URL of emissive texture image. + * @property {string} albedoMap - URL of albedo texture image. + * @property {string} opacityMap - URL of opacity texture image. Set value the same as the albedoMap value for + * transparency. + * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. + * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. + * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. + * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. + * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. + * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. + * @property {string} occlusionMap - URL of occlusion texture image. + * @property {string} scatteringMap - URL of scattering texture image. Only used if normalMap or + * bumpMap is specified. + * @property {string} lightMap - URL of light map texture image. Currently not used. + */ +// Note: See MaterialEntityItem.h for default values used in practice. +std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) { std::string name = ""; std::shared_ptr material = std::make_shared(); for (auto& key : materialJSON.keys()) { @@ -137,57 +199,58 @@ std::pair> NetworkMaterialResource } else if (key == "albedoMap") { auto value = materialJSON.value(key); if (value.isString()) { + QString urlString = value.toString(); bool useAlphaChannel = false; auto opacityMap = materialJSON.find("opacityMap"); - if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == value.toString()) { + if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == urlString) { useAlphaChannel = true; } - material->setAlbedoMap(value.toString(), useAlphaChannel); + material->setAlbedoMap(baseUrl.resolved(urlString), useAlphaChannel); } } else if (key == "roughnessMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setRoughnessMap(value.toString(), false); + material->setRoughnessMap(baseUrl.resolved(value.toString()), false); } } else if (key == "glossMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setRoughnessMap(value.toString(), true); + material->setRoughnessMap(baseUrl.resolved(value.toString()), true); } } else if (key == "metallicMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setMetallicMap(value.toString(), false); + material->setMetallicMap(baseUrl.resolved(value.toString()), false); } } else if (key == "specularMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setMetallicMap(value.toString(), true); + material->setMetallicMap(baseUrl.resolved(value.toString()), true); } } else if (key == "normalMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setNormalMap(value.toString(), false); + material->setNormalMap(baseUrl.resolved(value.toString()), false); } } else if (key == "bumpMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setNormalMap(value.toString(), true); + material->setNormalMap(baseUrl.resolved(value.toString()), true); } } else if (key == "occlusionMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setOcclusionMap(value.toString()); + material->setOcclusionMap(baseUrl.resolved(value.toString())); } } else if (key == "scatteringMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setScatteringMap(value.toString()); + material->setScatteringMap(baseUrl.resolved(value.toString())); } } else if (key == "lightMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setLightmapMap(value.toString()); + material->setLightmapMap(baseUrl.resolved(value.toString())); } } } diff --git a/libraries/model-networking/src/model-networking/MaterialCache.h b/libraries/model-networking/src/model-networking/MaterialCache.h index 468a12c677..074cd6c98d 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.h +++ b/libraries/model-networking/src/model-networking/MaterialCache.h @@ -37,8 +37,8 @@ public: ParsedMaterials parsedMaterials; - static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON); - static std::pair> parseJSONMaterial(const QJsonObject& materialJSON); + static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON, const QUrl& baseUrl); + static std::pair> parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl); private: static bool parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index d21e942581..f17cdbb7e8 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -190,11 +190,11 @@ void GeometryReader::run() { throw QString("empty geometry, possibly due to an unsupported FBX version"); } } else if (_url.path().toLower().endsWith(".obj")) { - fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _combineParts, _url)); + fbxGeometry = OBJReader().readOBJ(_data, _mapping, _combineParts, _url); } else if (_url.path().toLower().endsWith(".obj.gz")) { QByteArray uncompressedData; - if (gunzip(_data, uncompressedData)) { - fbxGeometry.reset(OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url)); + if (gunzip(_data, uncompressedData)){ + fbxGeometry = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url); } else { throw QString("failed to decompress .obj.gz"); } @@ -556,58 +556,58 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, im return nullptr; } -void NetworkMaterial::setAlbedoMap(const QString& url, bool useAlphaChannel) { - auto map = fetchTextureMap(QUrl(url), image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); +void NetworkMaterial::setAlbedoMap(const QUrl& url, bool useAlphaChannel) { + auto map = fetchTextureMap(url, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); if (map) { map->setUseAlphaChannel(useAlphaChannel); setTextureMap(MapChannel::ALBEDO_MAP, map); } } -void NetworkMaterial::setNormalMap(const QString& url, bool isBumpmap) { - auto map = fetchTextureMap(QUrl(url), isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP); +void NetworkMaterial::setNormalMap(const QUrl& url, bool isBumpmap) { + auto map = fetchTextureMap(url, isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP); if (map) { setTextureMap(MapChannel::NORMAL_MAP, map); } } -void NetworkMaterial::setRoughnessMap(const QString& url, bool isGloss) { - auto map = fetchTextureMap(QUrl(url), isGloss ? image::TextureUsage::GLOSS_TEXTURE : image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); +void NetworkMaterial::setRoughnessMap(const QUrl& url, bool isGloss) { + auto map = fetchTextureMap(url, isGloss ? image::TextureUsage::GLOSS_TEXTURE : image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); if (map) { setTextureMap(MapChannel::ROUGHNESS_MAP, map); } } -void NetworkMaterial::setMetallicMap(const QString& url, bool isSpecular) { - auto map = fetchTextureMap(QUrl(url), isSpecular ? image::TextureUsage::SPECULAR_TEXTURE : image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); +void NetworkMaterial::setMetallicMap(const QUrl& url, bool isSpecular) { + auto map = fetchTextureMap(url, isSpecular ? image::TextureUsage::SPECULAR_TEXTURE : image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); if (map) { setTextureMap(MapChannel::METALLIC_MAP, map); } } -void NetworkMaterial::setOcclusionMap(const QString& url) { - auto map = fetchTextureMap(QUrl(url), image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); +void NetworkMaterial::setOcclusionMap(const QUrl& url) { + auto map = fetchTextureMap(url, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); if (map) { setTextureMap(MapChannel::OCCLUSION_MAP, map); } } -void NetworkMaterial::setEmissiveMap(const QString& url) { - auto map = fetchTextureMap(QUrl(url), image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); +void NetworkMaterial::setEmissiveMap(const QUrl& url) { + auto map = fetchTextureMap(url, image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); if (map) { setTextureMap(MapChannel::EMISSIVE_MAP, map); } } -void NetworkMaterial::setScatteringMap(const QString& url) { - auto map = fetchTextureMap(QUrl(url), image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP); +void NetworkMaterial::setScatteringMap(const QUrl& url) { + auto map = fetchTextureMap(url, image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP); if (map) { setTextureMap(MapChannel::SCATTERING_MAP, map); } } -void NetworkMaterial::setLightmapMap(const QString& url) { - auto map = fetchTextureMap(QUrl(url), image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); +void NetworkMaterial::setLightmapMap(const QUrl& url) { + auto map = fetchTextureMap(url, image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); if (map) { //map->setTextureTransform(_lightmapTransform); //map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index bbb00d72eb..4dfa8b17ea 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -164,14 +164,14 @@ public: NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl); NetworkMaterial(const NetworkMaterial& material); - void setAlbedoMap(const QString& url, bool useAlphaChannel); - void setNormalMap(const QString& url, bool isBumpmap); - void setRoughnessMap(const QString& url, bool isGloss); - void setMetallicMap(const QString& url, bool isSpecular); - void setOcclusionMap(const QString& url); - void setEmissiveMap(const QString& url); - void setScatteringMap(const QString& url); - void setLightmapMap(const QString& url); + void setAlbedoMap(const QUrl& url, bool useAlphaChannel); + void setNormalMap(const QUrl& url, bool isBumpmap); + void setRoughnessMap(const QUrl& url, bool isGloss); + void setMetallicMap(const QUrl& url, bool isSpecular); + void setOcclusionMap(const QUrl& url); + void setEmissiveMap(const QUrl& url); + void setScatteringMap(const QUrl& url); + void setLightmapMap(const QUrl& url); protected: friend class Geometry; diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp index 741478789e..8f3d1ffec1 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp @@ -21,7 +21,7 @@ int SimpleMeshProxy::getNumVertices() const { return (int)_mesh->getNumVertices(); } -glm::vec3 SimpleMeshProxy::getPos3(int index) const { - return _mesh->getPos3(index); +glm::vec3 SimpleMeshProxy::getPos(int index) const { + return _mesh->getPos(index); } diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h index 24c3fca27e..073eb1c00f 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -26,8 +26,8 @@ public: int getNumVertices() const override; - glm::vec3 getPos3(int index) const override; - + glm::vec3 getPos(int index) const override; + glm::vec3 getPos3(int index) const override { return getPos(index); } // deprecated protected: const MeshPointer _mesh; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 912a44cb97..04696cea1a 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -38,11 +38,13 @@ #include #include #include +#include #include #include #include "NetworkLogging.h" #include "ModelNetworkingLogging.h" +#include "NetworkingConstants.h" #include #include @@ -294,7 +296,7 @@ _type(), _sourceIsKTX(false), _maxNumPixels(100) { - _textureSource = std::make_shared(); + _textureSource = std::make_shared(url); _lowestRequestedMipLevel = 0; _loaded = true; } @@ -310,7 +312,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, _sourceIsKTX(url.path().endsWith(".ktx")), _maxNumPixels(maxNumPixels) { - _textureSource = std::make_shared(); + _textureSource = std::make_shared(url, (int)type); _lowestRequestedMipLevel = 0; _shouldFailOnRedirect = !_sourceIsKTX; @@ -459,15 +461,21 @@ void NetworkTexture::makeRequest() { } +void NetworkTexture::handleLocalRequestCompleted() { + TextureCache::requestCompleted(_self); +} + void NetworkTexture::makeLocalRequest() { const QString scheme = _url.scheme(); QString path; if (scheme == URL_SCHEME_FILE) { - path = _url.toLocalFile(); + path = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile(); } else { path = ":" + _url.path(); } + connect(this, &Resource::finished, this, &NetworkTexture::handleLocalRequestCompleted); + path = FileUtils::selectFile(path); auto storage = std::make_shared(path); @@ -634,8 +642,12 @@ void NetworkTexture::ktxInitialDataRequestFinished() { } if (result == ResourceRequest::Success) { + +// This is an expensive operation that we do not want in release. +#ifdef DEBUG auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString()); qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo); +#endif _ktxHeaderData = _ktxHeaderRequest->getData(); _ktxHighMipData = _ktxMipRequest->getData(); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 71714f0c10..b2740e2ca1 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -71,6 +71,7 @@ public slots: protected: void makeRequest() override; void makeLocalRequest(); + Q_INVOKABLE void handleLocalRequestCompleted(); virtual bool isCacheable() const override { return _loaded; } @@ -134,6 +135,8 @@ private: using NetworkTexturePointer = QSharedPointer; +Q_DECLARE_METATYPE(QWeakPointer) + /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache, public Dependency { Q_OBJECT diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index a8eec66067..52253b1cbc 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -29,42 +29,24 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -#if USE_STABLE_GLOBAL_SERVICES -const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello"; -#else -const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello"; -#endif - +const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; Setting::Handle currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address", DEFAULT_HIFI_ADDRESS); -AddressManager::AddressManager() : - _port(0) -{ - -} - -QString AddressManager::protocolVersion() { - return protocolVersionsSignatureBase64(); -} - bool AddressManager::isConnected() { return DependencyManager::get()->getDomainHandler().isConnected(); } -QUrl AddressManager::currentAddress(bool domainOnly) const { - QUrl hifiURL; +QString AddressManager::getProtocol() const { + return _domainURL.scheme(); +} - hifiURL.setScheme(HIFI_URL_SCHEME); - hifiURL.setHost(_host); - - if (_port != 0 && _port != DEFAULT_DOMAIN_SERVER_PORT) { - hifiURL.setPort(_port); - } - - if (!domainOnly) { +QUrl AddressManager::currentAddress(bool domainOnly) const { + QUrl hifiURL = _domainURL; + + if (!domainOnly && hifiURL.scheme() == URL_SCHEME_HIFI) { hifiURL.setPath(currentPath()); } @@ -73,7 +55,9 @@ QUrl AddressManager::currentAddress(bool domainOnly) const { QUrl AddressManager::currentFacingAddress() const { auto hifiURL = currentAddress(); - hifiURL.setPath(currentFacingPath()); + if (hifiURL.scheme() == URL_SCHEME_HIFI) { + hifiURL.setPath(currentFacingPath()); + } return hifiURL; } @@ -83,7 +67,7 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const { // if we have a shareable place name use that instead of whatever the current host is QUrl hifiURL; - hifiURL.setScheme(HIFI_URL_SCHEME); + hifiURL.setScheme(URL_SCHEME_HIFI); hifiURL.setHost(_shareablePlaceName); if (!domainOnly) { @@ -96,13 +80,37 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const { } } +QUrl AddressManager::currentPublicAddress(bool domainOnly) const { + // return an address that can be used by others to visit this client's current location. If + // in a serverless domain (which can't be visited) return an empty URL. + QUrl shareableAddress = currentShareableAddress(domainOnly); + if (shareableAddress.scheme() != URL_SCHEME_HIFI) { + return QUrl(); // file: urls aren't public + } + return shareableAddress; +} + + QUrl AddressManager::currentFacingShareableAddress() const { auto hifiURL = currentShareableAddress(); - hifiURL.setPath(currentFacingPath()); + if (hifiURL.scheme() == URL_SCHEME_HIFI) { + hifiURL.setPath(currentFacingPath()); + } return hifiURL; } +QUrl AddressManager::currentFacingPublicAddress() const { + // return an address that can be used by others to visit this client's current location. If + // in a serverless domain (which can't be visited) return an empty URL. + QUrl shareableAddress = currentFacingShareableAddress(); + if (shareableAddress.scheme() != URL_SCHEME_HIFI) { + return QUrl(); // file: urls aren't public + } + return shareableAddress; +} + + void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings); @@ -143,11 +151,16 @@ void AddressManager::goForward() { void AddressManager::storeCurrentAddress() { auto url = currentAddress(); - - if (!url.host().isEmpty()) { + + if (url.scheme() == URL_SCHEME_FILE || + (url.scheme() == URL_SCHEME_HIFI && !url.host().isEmpty())) { + // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can + // be loaded over http(s) + // url.scheme() == URL_SCHEME_HTTP || + // url.scheme() == URL_SCHEME_HTTPS || currentAddressHandle.set(url); } else { - qCWarning(networking) << "Ignoring attempt to save current address with an empty host" << url; + qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url; } } @@ -213,7 +226,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { static QString URL_TYPE_DOMAIN_ID = "domain_id"; static QString URL_TYPE_PLACE = "place"; static QString URL_TYPE_NETWORK_ADDRESS = "network_address"; - if (lookupUrl.scheme() == HIFI_URL_SCHEME) { + if (lookupUrl.scheme() == URL_SCHEME_HIFI) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); @@ -293,12 +306,36 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); + return true; + } else if (lookupUrl.scheme() == URL_SCHEME_FILE) { + // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can + // be loaded over http(s) + // lookupUrl.scheme() == URL_SCHEME_HTTP || + // lookupUrl.scheme() == URL_SCHEME_HTTPS || + _previousLookup.clear(); + _shareablePlaceName.clear(); + setDomainInfo(lookupUrl, trigger); + emit lookupResultsFinished(); + handlePath(DOMAIN_SPAWNING_POINT, LookupTrigger::Internal, false); return true; } return false; } +bool isPossiblePlaceName(QString possiblePlaceName) { + bool result { false }; + int length = possiblePlaceName.length(); + static const int MINIMUM_PLACENAME_LENGTH = 1; + static const int MAXIMUM_PLACENAME_LENGTH = 64; + if (possiblePlaceName.toLower() != "localhost" && + length >= MINIMUM_PLACENAME_LENGTH && length <= MAXIMUM_PLACENAME_LENGTH) { + const QRegExp PLACE_NAME_REGEX = QRegExp("^[0-9A-Za-z](([0-9A-Za-z]|-(?!-))*[^\\W_]$|$)"); + result = PLACE_NAME_REGEX.indexIn(possiblePlaceName) == 0; + } + return result; +} + void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) { if (!lookupString.isEmpty()) { // make this a valid hifi URL and handle it off to handleUrl @@ -306,12 +343,16 @@ void AddressManager::handleLookupString(const QString& lookupString, bool fromSu QUrl lookupURL; if (!lookupString.startsWith('/')) { - const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive); + // sometimes we need to handle lookupStrings like hifi:/somewhere + const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_HIFI + ":\\/{1,2}", Qt::CaseInsensitive); sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX); - lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString); + lookupURL = QUrl(sanitizedString); + if (lookupURL.scheme().isEmpty()) { + lookupURL = QUrl("hifi://" + sanitizedString); + } } else { - lookupURL = QUrl(lookupString); + lookupURL = QUrl(sanitizedString); } handleUrl(lookupURL, fromSuggestions ? Suggestions : UserInput); @@ -389,7 +430,11 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const qCDebug(networking) << "Possible domain change required to connect to" << domainHostname << "on" << domainPort; - emit possibleDomainChangeRequired(domainHostname, domainPort, domainID); + QUrl domainURL; + domainURL.setScheme(URL_SCHEME_HIFI); + domainURL.setHost(domainHostname); + domainURL.setPort(domainPort); + emit possibleDomainChangeRequired(domainURL, domainID); } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); @@ -426,15 +471,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const if (setHost(placeName, trigger)) { trigger = LookupTrigger::Internal; } - - _placeName = placeName; } else { if (setHost(domainIDString, trigger)) { trigger = LookupTrigger::Internal; } - - // this isn't a place, so clear the place name - _placeName.clear(); } // check if we had a path to override the path returned @@ -555,13 +595,17 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri if (ipAddressRegex.indexIn(lookupString) != -1) { QString domainIPString = ipAddressRegex.cap(1); - qint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; + quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; if (!ipAddressRegex.cap(2).isEmpty()) { - domainPort = (qint16) ipAddressRegex.cap(2).toInt(); + domainPort = (quint16) ipAddressRegex.cap(2).toInt(); } emit lookupResultsFinished(); - hostChanged = setDomainInfo(domainIPString, domainPort, trigger); + QUrl domainURL; + domainURL.setScheme(URL_SCHEME_HIFI); + domainURL.setHost(domainIPString); + domainURL.setPort(domainPort); + hostChanged = setDomainInfo(domainURL, trigger); return true; } @@ -574,11 +618,15 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; if (!hostnameRegex.cap(2).isEmpty()) { - domainPort = (qint16)hostnameRegex.cap(2).toInt(); + domainPort = (quint16)hostnameRegex.cap(2).toInt(); } emit lookupResultsFinished(); - hostChanged = setDomainInfo(domainHostname, domainPort, trigger); + QUrl domainURL; + domainURL.setScheme(URL_SCHEME_HIFI); + domainURL.setHost(domainHostname); + domainURL.setPort(domainPort); + hostChanged = setDomainInfo(domainURL, trigger); return true; } @@ -647,7 +695,7 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should addCurrentAddressToHistory(trigger); } - if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) { + if (!isNaN(newPosition)) { glm::quat newOrientation; QRegExp orientationRegex(QUAT_REGEX_STRING); @@ -667,11 +715,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should && !isNaN(newOrientation.w)) { orientationChanged = true; } else { - qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change."; + qCDebug(networking) << "Orientation parsed from lookup string is invalid. Won't use for location change."; } } - - emit locationChangeRequired(newPosition, orientationChanged, + + emit locationChangeRequired(newPosition, orientationChanged, trigger == LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation, shouldFace ); @@ -702,18 +750,20 @@ bool AddressManager::handleUsername(const QString& lookupString) { } bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 port) { - if (host != _host || port != _port) { - + if (host != _domainURL.host() || port != _domainURL.port()) { addCurrentAddressToHistory(trigger); - _port = port; + bool emitHostChanged = host != _domainURL.host(); + _domainURL = QUrl(); + _domainURL.setScheme(URL_SCHEME_HIFI); + _domainURL.setHost(host); + _domainURL.setPort(port); // any host change should clear the shareable place name _shareablePlaceName.clear(); - if (host != _host) { - _host = host; - emit hostChanged(_host); + if (emitHostChanged) { + emit hostChanged(host); } return true; @@ -722,20 +772,35 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 return false; } -bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { - bool hostChanged = setHost(hostname, trigger, port); +bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger) { + const QString hostname = domainURL.host(); + quint16 port = domainURL.port(); + bool emitHostChanged { false }; + + if (domainURL != _domainURL) { + addCurrentAddressToHistory(trigger); + emitHostChanged = true; + } + + _domainURL = domainURL; // clear any current place information _rootPlaceID = QUuid(); - _placeName.clear(); - qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; + if (_domainURL.scheme() == URL_SCHEME_HIFI) { + qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; + } else { + qCDebug(networking) << "Possible domain change required to serverless domain: " << domainURL.toString(); + } DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); - emit possibleDomainChangeRequired(hostname, port, QUuid()); + if (emitHostChanged) { + emit hostChanged(domainURL.host()); + } + emit possibleDomainChangeRequired(_domainURL, QUuid()); - return hostChanged; + return emitHostChanged; } void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) { @@ -824,7 +889,7 @@ void AddressManager::lookupShareableNameForDomainID(const QUuid& domainID) { // then use that for Steam join/invite or copiable address // it only makes sense to lookup a shareable default name if we don't have a place name - if (_placeName.isEmpty()) { + if (getPlaceName().isEmpty()) { JSONCallbackParameters callbackParams; // no error callback handling @@ -876,3 +941,12 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { } } +QString AddressManager::getPlaceName() const { + if (!_shareablePlaceName.isEmpty()) { + return _shareablePlaceName; + } + if (isPossiblePlaceName(_domainURL.host())) { + return _domainURL.host(); + } + return QString(); +} diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 6c9f06d2dd..b6a18b117d 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -22,8 +22,6 @@ #include "AccountManager.h" -const QString HIFI_URL_SCHEME = "hifi"; - extern const QString DEFAULT_HIFI_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; @@ -71,15 +69,6 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString domainID READ getDomainID) Q_PROPERTY(QString domainId READ getDomainID) public: - - /**jsdoc - * Get Interface's protocol version. - * @function location.protocolVersion - * @returns {string} A string uniquely identifying the version of the metaverse protocol that Interface is using. - * @deprecated This function is deprecated and will be removed. Use {@link Window.protocolSignature} instead. - */ - Q_INVOKABLE QString protocolVersion(); - using PositionGetter = std::function; using OrientationGetter = std::function; @@ -156,20 +145,22 @@ public: }; bool isConnected(); - const QString& getProtocol() { return HIFI_URL_SCHEME; }; + QString getProtocol() const; QUrl currentAddress(bool domainOnly = false) const; QUrl currentFacingAddress() const; QUrl currentShareableAddress(bool domainOnly = false) const; + QUrl currentPublicAddress(bool domainOnly = false) const; QUrl currentFacingShareableAddress() const; + QUrl currentFacingPublicAddress() const; QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; const QUuid& getRootPlaceID() const { return _rootPlaceID; } - const QString& getPlaceName() const { return _shareablePlaceName.isEmpty() ? _placeName : _shareablePlaceName; } + QString getPlaceName() const; QString getDomainID() const; - const QString& getHost() const { return _host; } + QString getHost() const { return _domainURL.host(); } void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } @@ -179,6 +170,8 @@ public: const QStack& getBackStack() const { return _backStack; } const QStack& getForwardStack() const { return _forwardStack; } + QUrl getDomainURL() { return _domainURL; } + public slots: /**jsdoc * Go to a specified metaverse address. @@ -311,13 +304,12 @@ signals: /**jsdoc * Triggered when a request is made to go to an IP address. * @function location.possibleDomainChangeRequired - * @param {string} hostName - The name of the domain to go do. - * @param {number} port - The integer number of the network port to connect to. + * @param {Url} domainURL - URL for domain * @param {Uuid} domainID - The UUID of the domain to go to. * @returns {Signal} */ // No example because this function isn't typically used in scripts. - void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID); + void possibleDomainChangeRequired(QUrl domainURL, QUuid domainID); /**jsdoc * Triggered when a request is made to go to a named domain or user. @@ -369,7 +361,7 @@ signals: * location.pathChangeRequired.connect(onPathChangeRequired); */ void pathChangeRequired(const QString& newPath); - + /**jsdoc * Triggered when you navigate to a new domain. * @function location.hostChanged @@ -401,7 +393,7 @@ signals: void goBackPossible(bool isPossible); /**jsdoc - * Triggered when there's a change in whether or not there's a forward location that can be navigated to using + * Triggered when there's a change in whether or not there's a forward location that can be navigated to using * {@link location.goForward|goForward}. (Reflects changes in the state of the "Goto" dialog's forward arrow.) * @function location.goForwardPossible * @param {boolean} isPossible - true if there's a forward location to navigate to, otherwise @@ -416,8 +408,6 @@ signals: */ void goForwardPossible(bool isPossible); -protected: - AddressManager(); private slots: void handleAPIResponse(QNetworkReply& requestReply); void handleAPIError(QNetworkReply& errorReply); @@ -429,7 +419,7 @@ private: // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); - bool setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger); + bool setDomainInfo(const QUrl& domainURL, LookupTrigger trigger); const JSONCallbackParameters& apiCallbackParameters(); @@ -447,9 +437,8 @@ private: void addCurrentAddressToHistory(LookupTrigger trigger); - QString _host; - quint16 _port; - QString _placeName; + QUrl _domainURL; + QUuid _rootPlaceID; PositionGetter _positionGetter; OrientationGetter _orientationGetter; @@ -461,7 +450,7 @@ private: quint64 _lastBackPush = 0; QString _newHostLookupPath; - + QUrl _previousLookup; }; diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index d302c6fac6..8d3d313ff9 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -20,6 +20,7 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" +#include "NetworkingConstants.h" #include "ResourceManager.h" diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index decb796fa4..b231339e51 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -233,7 +233,7 @@ Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { QPointer assetRequest = assetClient()->createRequest(hash); Promise deferred = makePromise(__FUNCTION__); - QObject::connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { + QObject::connect(assetRequest, &AssetRequest::finished, assetRequest, [deferred](AssetRequest* request) { // note: we are now on the "Resource Manager" thread Q_ASSERT(QThread::currentThread() == request->thread()); Q_ASSERT(request->getState() == AssetRequest::Finished); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 411f8f5be2..c20d6d73be 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -9,8 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DomainHandler.h" + #include +#include + #include #include @@ -25,8 +29,6 @@ #include "UserActivityLogger.h" #include "NetworkLogging.h" -#include "DomainHandler.h" - DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), @@ -38,7 +40,7 @@ DomainHandler::DomainHandler(QObject* parent) : // if we get a socket that make sure our NetworkPeer ping timer stops connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); - + // setup a timeout for failure on settings requests static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000; _settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); // 5s, Qt::CoarseTimer acceptable @@ -60,11 +62,11 @@ void DomainHandler::disconnect() { if (_isConnected) { sendDisconnectPacket(); } - + // clear member variables that hold the connection state to a domain _uuid = QUuid(); _connectionToken = QUuid(); - + _icePeer.reset(); if (requiresICE()) { @@ -78,10 +80,10 @@ void DomainHandler::disconnect() { void DomainHandler::sendDisconnectPacket() { // The DomainDisconnect packet is not verified - we're relying on the eventual addition of DTLS to the // domain-server connection to stop greifing here - + // construct the disconnect packet once (an empty packet but sourced with our current session UUID) static auto disconnectPacket = NLPacket::create(PacketType::DomainDisconnectRequest, 0); - + // send the disconnect packet to the current domain server auto nodeList = DependencyManager::get(); nodeList->sendUnreliablePacket(*disconnectPacket, _sockAddr); @@ -94,7 +96,7 @@ void DomainHandler::clearSettings() { void DomainHandler::softReset() { qCDebug(networking) << "Resetting current domain connection information."; disconnect(); - + clearSettings(); _connectionDenialsSinceKeypairRegen = 0; @@ -115,8 +117,8 @@ void DomainHandler::hardReset() { qCDebug(networking) << "Hard reset in NodeList DomainHandler."; _pendingDomainID = QUuid(); _iceServerSockAddr = HifiSockAddr(); - _hostname = QString(); _sockAddr.clear(); + _domainURL = QUrl(); _domainConnectionRefusals.clear(); @@ -139,7 +141,10 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos } // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification - _hostname = hostname; + _domainURL = QUrl(); + _domainURL.setScheme(URL_SCHEME_HIFI); + _domainURL.setHost(hostname); + _domainURL.setPort(_sockAddr.getPort()); } void DomainHandler::setUUID(const QUuid& uuid) { @@ -149,36 +154,50 @@ void DomainHandler::setUUID(const QUuid& uuid) { } } -void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) { - +void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { _pendingDomainID = domainID; - if (hostname != _hostname || _sockAddr.getPort() != port) { + if (domainURL.scheme() != URL_SCHEME_HIFI) { + _sockAddr.clear(); + + // if this is a file URL we need to see if it has a ~ for us to expand + if (domainURL.scheme() == URL_SCHEME_FILE) { + domainURL = PathUtils::expandToLocalDataAbsolutePath(domainURL); + } + } + + if (_domainURL != domainURL || _sockAddr.getPort() != domainURL.port()) { // re-set the domain info so that auth information is reloaded hardReset(); - if (hostname != _hostname) { - // set the new hostname - _hostname = hostname; + QString previousHost = _domainURL.host(); + _domainURL = domainURL; - qCDebug(networking) << "Updated domain hostname to" << _hostname; + if (domainURL.scheme() != URL_SCHEME_HIFI) { + setIsConnected(true); + } else if (previousHost != domainURL.host()) { + qCDebug(networking) << "Updated domain hostname to" << domainURL.host(); - // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname - qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); - QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + if (!domainURL.host().isEmpty()) { + // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname + qCDebug(networking, "Looking up DS hostname %s.", domainURL.host().toLocal8Bit().constData()); + QHostInfo::lookupHost(domainURL.host(), this, SLOT(completedHostnameLookup(const QHostInfo&))); - DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainHostname); + DependencyManager::get()->flagTimeForConnectionStep( + LimitedNodeList::ConnectionStep::SetDomainHostname); - UserActivityLogger::getInstance().changedDomain(_hostname); - emit hostnameChanged(_hostname); + UserActivityLogger::getInstance().changedDomain(domainURL.host()); + } } - if (_sockAddr.getPort() != port) { - qCDebug(networking) << "Updated domain port to" << port; + emit domainURLChanged(_domainURL); + + if (_sockAddr.getPort() != domainURL.port()) { + qCDebug(networking) << "Updated domain port to" << domainURL.port(); } // grab the port by reading the string after the colon - _sockAddr.setPort(port); + _sockAddr.setPort(domainURL.port()); } } @@ -187,10 +206,10 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) { // re-set the domain info to connect to new domain hardReset(); - + // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); - + _pendingDomainID = id; HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; @@ -216,14 +235,18 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, void DomainHandler::activateICELocalSocket() { DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getLocalSocket(); - _hostname = _sockAddr.getAddress().toString(); + _domainURL.setScheme(URL_SCHEME_HIFI); + _domainURL.setHost(_sockAddr.getAddress().toString()); + emit domainURLChanged(_domainURL); emit completedSocketDiscovery(); } void DomainHandler::activateICEPublicSocket() { DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getPublicSocket(); - _hostname = _sockAddr.getAddress().toString(); + _domainURL.setScheme(URL_SCHEME_HIFI); + _domainURL.setHost(_sockAddr.getAddress().toString()); + emit domainURLChanged(_domainURL); emit completedSocketDiscovery(); } @@ -234,7 +257,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); - qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(), + qCDebug(networking, "DS at %s is at %s", _domainURL.host().toLocal8Bit().constData(), _sockAddr.getAddress().toString().toLocal8Bit().constData()); emit completedSocketDiscovery(); @@ -261,10 +284,12 @@ void DomainHandler::setIsConnected(bool isConnected) { _isConnected = isConnected; if (_isConnected) { - emit connectedToDomain(_hostname); + emit connectedToDomain(_domainURL); - // we've connected to new domain - time to ask it for global settings - requestDomainSettings(); + if (_domainURL.scheme() == URL_SCHEME_HIFI && !_domainURL.host().isEmpty()) { + // we've connected to new domain - time to ask it for global settings + requestDomainSettings(); + } } else { emit disconnectedFromDomain(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 78f9798089..fbc60e2492 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -25,6 +25,7 @@ #include "NLPacketList.h" #include "Node.h" #include "ReceivedMessage.h" +#include "NetworkingConstants.h" const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; @@ -37,14 +38,14 @@ class DomainHandler : public QObject { Q_OBJECT public: DomainHandler(QObject* parent = 0); - + void disconnect(); void clearSettings(); const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid); - const QString& getHostname() const { return _hostname; } + QString getHostname() const { return _domainURL.host(); } const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -57,7 +58,7 @@ public: const QUuid& getConnectionToken() const { return _connectionToken; } void setConnectionToken(const QUuid& connectionToken) { _connectionToken = connectionToken; } - + const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } @@ -73,11 +74,12 @@ public: bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); + bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } bool hasSettings() const { return !_settingsObject.isEmpty(); } void requestDomainSettings(); const QJsonObject& getSettingsObject() const { return _settingsObject; } - + void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; } const QString& getPendingPath() { return _pendingPath; } void clearPendingPath() { _pendingPath.clear(); } @@ -139,7 +141,7 @@ public: }; public slots: - void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid()); + void setURLAndID(QUrl domainURL, QUuid id); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); void processSettingsPacketList(QSharedPointer packetList); @@ -153,14 +155,14 @@ private slots: void completedIceServerHostnameLookup(); signals: - void hostnameChanged(const QString& hostname); + void domainURLChanged(QUrl domainURL); // NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on void completedSocketDiscovery(); void resetting(); - void connectedToDomain(const QString& hostname); + void connectedToDomain(QUrl domainURL); void disconnectedFromDomain(); void iceSocketAndIDReceived(); @@ -179,7 +181,7 @@ private: void hardReset(); QUuid _uuid; - QString _hostname; + QUrl _domainURL; HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; @@ -200,4 +202,7 @@ private: QTimer _apiRefreshTimer; }; +const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" }; + + #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp index 7cbb401f7f..1a4096bc02 100644 --- a/libraries/networking/src/FileResourceRequest.cpp +++ b/libraries/networking/src/FileResourceRequest.cpp @@ -17,9 +17,11 @@ #include #include +#include #include "NetworkLogging.h" #include "ResourceManager.h" +#include "NetworkingConstants.h" void FileResourceRequest::doSend() { auto statTracker = DependencyManager::get(); @@ -29,7 +31,7 @@ void FileResourceRequest::doSend() { if (_url.scheme() == URL_SCHEME_QRC) { filename = ":/" + _url.path(); } else { - filename = _url.toLocalFile(); + filename = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile(); // sometimes on windows, we see the toLocalFile() return null, // in this case we will attempt to simply use the url as a string if (filename.isEmpty()) { diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 9dbbc570dd..0803e380f2 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -49,19 +49,8 @@ const std::set SOLO_NODE_TYPES = { }; LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : - _sessionUUID(), - _nodeHash(), - _nodeMutex(QReadWriteLock::Recursive), _nodeSocket(this), - _dtlsSocket(NULL), - _localSockAddr(), - _publicSockAddr(), - _stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT), - _packetReceiver(new PacketReceiver(this)), - _numCollectedPackets(0), - _numCollectedBytes(0), - _packetStatTimer(), - _permissions(NodePermissions()) + _packetReceiver(new PacketReceiver(this)) { qRegisterMetaType("ConnectionStep"); auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); @@ -122,13 +111,22 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : } } +QUuid LimitedNodeList::getSessionUUID() const { + QReadLocker lock { &_sessionUUIDLock }; + return _sessionUUID; +} + void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { - QUuid oldUUID = _sessionUUID; - _sessionUUID = sessionUUID; + QUuid oldUUID; + { + QWriteLocker lock { &_sessionUUIDLock }; + oldUUID = _sessionUUID; + _sessionUUID = sessionUUID; + } if (sessionUUID != oldUUID) { qCDebug(networking) << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) - << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); + << "to" << uuidStringWithoutCurlyBraces(sessionUUID); emit uuidChanged(sessionUUID, oldUUID); } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 1b1c809279..7165b3dd63 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -104,7 +104,7 @@ public: }; Q_ENUM(ConnectionStep); - const QUuid& getSessionUUID() const { return _sessionUUID; } + QUuid getSessionUUID() const; void setSessionUUID(const QUuid& sessionUUID); void setPermissions(const NodePermissions& newPermissions); @@ -380,20 +380,19 @@ protected: bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { return findNodeWithAddr(sockAddr) != SharedNodePointer(); } - QUuid _sessionUUID; NodeHash _nodeHash; - mutable QReadWriteLock _nodeMutex; + mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive }; udt::Socket _nodeSocket; - QUdpSocket* _dtlsSocket; + QUdpSocket* _dtlsSocket { nullptr }; HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; - HifiSockAddr _stunSockAddr; + HifiSockAddr _stunSockAddr { STUN_SERVER_HOSTNAME, STUN_SERVER_PORT }; bool _hasTCPCheckedLocalSocket { false }; PacketReceiver* _packetReceiver; - std::atomic _numCollectedPackets; - std::atomic _numCollectedBytes; + std::atomic _numCollectedPackets { 0 }; + std::atomic _numCollectedBytes { 0 }; QElapsedTimer _packetStatTimer; NodePermissions _permissions; @@ -424,6 +423,10 @@ private slots: void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp); void possiblyTimeoutSTUNAddressLookup(); void addSTUNHandlerToUnfiltered(); // called once STUN socket known + +private: + mutable QReadWriteLock _sessionUUIDLock; + QUuid _sessionUUID; }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index 6d0483fe9d..6ef3777d8c 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -23,6 +23,21 @@ #include "Node.h" #include "ReceivedMessage.h" +/**jsdoc + *

The Messages API enables text and data to be sent between scripts over named "channels". A channel can have an arbitrary + * name to help separate messaging between different sets of scripts.

+ * + *

Note: If you want to call a function in another script, you should use one of the following rather than + * sending a message:

+ *
    + *
  • {@link Entities.callEntityClientMethod}
  • + *
  • {@link Entities.callEntityMethod}
  • + *
  • {@link Entities.callEntityServerMethod}
  • + *
  • {@link Script.callEntityScriptMethod}
  • + *
+ * + * @namespace Messages + */ class MessagesClient : public QObject, public Dependency { Q_OBJECT public: @@ -30,10 +45,115 @@ public: void startThread(); + /**jsdoc + * Send a text message on a channel. + * @function Messages.sendMessage + * @param {string} channel - The channel to send the message on. + * @param {string} message - The message to send. + * @param {boolean} [localOnly=false] - If false then the message is sent to all Interface, client entity, + * server entity, and assignment client scripts in the domain.
+ * If true then: if sent from an Interface or client entity script it is received by all Interface and + * client entity scripts; if sent from a server entity script it is received by all entity server scripts; and if sent + * from an assignment client script it is received only by that same assignment client script. + * @example Send and receive a message. + * // Receiving script. + * var channelName = "com.highfidelity.example.messages-example"; + * + * function onMessageReceived(channel, message, sender, localOnly) { + * print("Message received:"); + * print("- channel: " + channel); + * print("- message: " + message); + * print("- sender: " + sender); + * print("- localOnly: " + localOnly); + * } + * + * Messages.subscribe(channelName); + * Messages.messageReceived.connect(onMessageReceived); + * + * Script.scriptEnding.connect(function () { + * Messages.messageReceived.disconnect(onMessageReceived); + * Messages.unsubscribe(channelName); + * }); + * + * + * // Sending script. + * var channelName = "com.highfidelity.example.messages-example"; + * var message = "Hello"; + * Messages.sendMessage(channelName, message); + */ Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); + + /**jsdoc + * Send a text message locally on a channel. + * This is the same as calling {@link Messages.sendMessage|sendMessage} with localOnly set to + * true. + * @function Messages.sendLocalMessage + * @param {string} channel - The channel to send the message on. + * @param {string} message - The message to send. + */ Q_INVOKABLE void sendLocalMessage(QString channel, QString message); + + /**jsdoc + * Send a data message on a channel. + * @function Messages.sendData + * @param {string} channel - The channel to send the data on. + * @param {object} data - The data to send. The data is handled as a byte stream, for example as may be provided via a + * JavaScript Int8Array object. + * @param {boolean} [localOnly=false] - If false then the message is sent to all Interface, client entity, + * server entity, and assignment client scripts in the domain.
+ * If true then: if sent from an Interface or client entity script it is received by all Interface and + * client entity scripts; if sent from a server entity script it is received by all entity server scripts; and if sent + * from an assignment client script it is received only by that same assignment client script. + * @example Send and receive data. + * // Receiving script. + * var channelName = "com.highfidelity.example.messages-example"; + * + * function onDataReceived(channel, data, sender, localOnly) { + * var int8data = new Int8Array(data); + * var dataAsString = ""; + * for (var i = 0; i < int8data.length; i++) { + * if (i > 0) { + * dataAsString += ", "; + * } + * dataAsString += int8data[i]; + * } + * print("Data received:"); + * print("- channel: " + channel); + * print("- data: " + dataAsString); + * print("- sender: " + sender); + * print("- localOnly: " + localOnly); + * } + * + * Messages.subscribe(channelName); + * Messages.dataReceived.connect(onDataReceived); + * + * Script.scriptEnding.connect(function () { + * Messages.dataReceived.disconnect(onDataReceived); + * Messages.unsubscribe(channelName); + * }); + * + * + * // Sending script. + * var channelName = "com.highfidelity.example.messages-example"; + * var int8data = new Int8Array([1, 1, 2, 3, 5, 8, 13]); + * Messages.sendData(channelName, int8data.buffer); + */ Q_INVOKABLE void sendData(QString channel, QByteArray data, bool localOnly = false); + + /**jsdoc + * Subscribe the scripting environment — Interface, the entity script server, or assignment client instance — + * to receive messages on a specific channel. Note that, for example, if there are two Interface scripts that subscribe to + * different channels, both scripts will receive messages on both channels. + * @function Messages.subscribe + * @param {string} channel - The channel to subscribe to. + */ Q_INVOKABLE void subscribe(QString channel); + + /**jsdoc + * Unsubscribe the scripting environment from receiving messages on a specific channel. + * @function Messages.unsubscribe + * @param {string} channel - The channel to unsubscribe from. + */ Q_INVOKABLE void unsubscribe(QString channel); static void decodeMessagesPacket(QSharedPointer receivedMessage, QString& channel, @@ -43,7 +163,34 @@ public: static std::unique_ptr encodeMessagesDataPacket(QString channel, QByteArray data, QUuid senderID); signals: + /**jsdoc + * Triggered when the a text message is received. + * @function Messages.messageReceived + * @param {string} channel - The channel that the message was sent on. You can use this to filter out messages not relevant + * to your script. + * @param {string} message - The message received. + * @param {Uuid} senderID - The UUID of the sender: the user's session UUID if sent by an Interface or client entity + * script, the UUID of the entity script server if sent by a server entity script, or the UUID of the assignment client + * instance if sent by an assignment client script. + * @param {boolean} localOnly - true if the message was sent with localOnly = true. + * @returns {Signal} + */ void messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly); + + /**jsdoc + * Triggered when a data message is received. + * @function Messages.dataReceived + * @param {string} channel - The channel that the message was sent on. You can use this to filter out messages not relevant + * to your script. + * @param {object} data - The data received. The data is handled as a byte stream, for example as may be used by a + * JavaScript Int8Array object. + * @param {Uuid} senderID - The UUID of the sender: the user's session UUID if sent by an Interface or client entity + * script, the UUID of the entity script server if sent by a server entity script, or the UUID of the assignment client + * script, the UUID of the entity script server if sent by a server entity script, or the UUID of the assignment client + * instance if sent by an assignment client script. + * @param {boolean} localOnly - true if the message was sent with localOnly = true. + * @returns {Signal} + */ void dataReceived(QString channel, QByteArray data, QUuid senderUUID, bool localOnly); private slots: diff --git a/libraries/networking/src/NetworkingConstants.cpp b/libraries/networking/src/NetworkingConstants.cpp new file mode 100644 index 0000000000..622c307efa --- /dev/null +++ b/libraries/networking/src/NetworkingConstants.cpp @@ -0,0 +1,24 @@ +// +// NetworkingConstants.cpp +// libraries/networking/src +// +// Created by Seth Alves on 2018-2-28. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "NetworkingConstants.h" + +namespace NetworkingConstants { + // You can change the return of this function if you want to use a custom metaverse URL at compile time + // or you can pass a custom URL via the env variable + QUrl METAVERSE_SERVER_URL() { + const QString HIFI_METAVERSE_URL_ENV = "HIFI_METAVERSE_URL"; + const QUrl serverURL = QProcessEnvironment::systemEnvironment().contains(HIFI_METAVERSE_URL_ENV) + ? QUrl(QProcessEnvironment::systemEnvironment().value(HIFI_METAVERSE_URL_ENV)) + : METAVERSE_SERVER_URL_STABLE; + return serverURL; + }; +} diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index a4726f9b1a..8eb1e71ed6 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -25,18 +25,17 @@ namespace NetworkingConstants { // if you manually generate a personal access token for the domains scope // at https://staging.highfidelity.com/user/tokens/new?for_domain_server=true - const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com"); - const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com"); - - // You can change the return of this function if you want to use a custom metaverse URL at compile time - // or you can pass a custom URL via the env variable - static const QUrl METAVERSE_SERVER_URL() { - static const QString HIFI_METAVERSE_URL_ENV = "HIFI_METAVERSE_URL"; - static const QUrl serverURL = QProcessEnvironment::systemEnvironment().contains(HIFI_METAVERSE_URL_ENV) - ? QUrl(QProcessEnvironment::systemEnvironment().value(HIFI_METAVERSE_URL_ENV)) - : METAVERSE_SERVER_URL_STABLE; - return serverURL; - }; + const QUrl METAVERSE_SERVER_URL_STABLE { "https://metaverse.highfidelity.com" }; + const QUrl METAVERSE_SERVER_URL_STAGING { "https://staging.highfidelity.com" }; + QUrl METAVERSE_SERVER_URL(); } +const QString URL_SCHEME_HIFI = "hifi"; +const QString URL_SCHEME_QRC = "qrc"; +const QString URL_SCHEME_FILE = "file"; +const QString URL_SCHEME_HTTP = "http"; +const QString URL_SCHEME_HTTPS = "https"; +const QString URL_SCHEME_FTP = "ftp"; +const QString URL_SCHEME_ATP = "atp"; + #endif // hifi_NetworkingConstants_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 9ad0cb5e79..cb0d2e4cd5 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -55,7 +55,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) // handle domain change signals from AddressManager connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired, - &_domainHandler, &DomainHandler::setSocketAndID); + &_domainHandler, &DomainHandler::setURLAndID); connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID, &_domainHandler, &DomainHandler::setIceServerHostnameAndID); @@ -91,7 +91,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); // clear out NodeList when login is finished - connect(accountManager.data(), SIGNAL(loginComplete()) , this, SLOT(reset())); + connect(accountManager.data(), SIGNAL(loginComplete(const QUrl&)) , this, SLOT(reset())); // clear our NodeList when logout is requested connect(accountManager.data(), SIGNAL(logoutComplete()) , this, SLOT(reset())); @@ -106,7 +106,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) // setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect) _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings); - connect(&_domainHandler, SIGNAL(connectedToDomain(QString)), &_keepAlivePingTimer, SLOT(start())); + connect(&_domainHandler, SIGNAL(connectedToDomain(QUrl)), &_keepAlivePingTimer, SLOT(start())); connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); // set our sockAddrBelongsToDomainOrNode method as the connection creation filter for the udt::Socket @@ -798,7 +798,7 @@ void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationN void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) { // enumerate the nodes to send a reliable ignore packet to each that can leverage it - if (!nodeID.isNull() && _sessionUUID != nodeID) { + if (!nodeID.isNull() && getSessionUUID() != nodeID) { eachMatchingNode([](const SharedNodePointer& node)->bool { if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) { return true; @@ -851,7 +851,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) { void NodeList::removeFromIgnoreMuteSets(const QUuid& nodeID) { // don't remove yourself, or nobody - if (!nodeID.isNull() && _sessionUUID != nodeID) { + if (!nodeID.isNull() && getSessionUUID() != nodeID) { { QWriteLocker ignoredSetLocker{ &_ignoredSetLock }; _ignoredNodeIDs.unsafe_erase(nodeID); @@ -870,7 +870,7 @@ bool NodeList::isIgnoringNode(const QUuid& nodeID) const { void NodeList::personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled) { // cannot personal mute yourself, or nobody - if (!nodeID.isNull() && _sessionUUID != nodeID) { + if (!nodeID.isNull() && getSessionUUID() != nodeID) { auto audioMixer = soloNodeOfType(NodeType::AudioMixer); if (audioMixer) { if (isIgnoringNode(nodeID)) { @@ -970,7 +970,7 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) { void NodeList::setAvatarGain(const QUuid& nodeID, float gain) { // cannot set gain of yourself - if (_sessionUUID != nodeID) { + if (getSessionUUID() != nodeID) { auto audioMixer = soloNodeOfType(NodeType::AudioMixer); if (audioMixer) { // setup the packet @@ -1013,7 +1013,7 @@ void NodeList::kickNodeBySessionID(const QUuid& nodeID) { // send a request to domain-server to kick the node with the given session ID // the domain-server will handle the persistence of the kick (via username or IP) - if (!nodeID.isNull() && _sessionUUID != nodeID ) { + if (!nodeID.isNull() && getSessionUUID() != nodeID ) { if (getThisNodeCanKick()) { // setup the packet auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID, true); @@ -1036,7 +1036,7 @@ void NodeList::kickNodeBySessionID(const QUuid& nodeID) { void NodeList::muteNodeBySessionID(const QUuid& nodeID) { // cannot mute yourself, or nobody - if (!nodeID.isNull() && _sessionUUID != nodeID ) { + if (!nodeID.isNull() && getSessionUUID() != nodeID ) { if (getThisNodeCanKick()) { auto audioMixer = soloNodeOfType(NodeType::AudioMixer); if (audioMixer) { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index a49cb7c3f5..7e3a7c2bd7 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -176,7 +176,11 @@ private: tbb::concurrent_unordered_map _avatarGainMap; void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode); +#if defined(Q_OS_ANDROID) + Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; +#else Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true }; +#endif #if (PR_BUILD || DEV_BUILD) bool _shouldSendNewerVersion { false }; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 317cabdc61..d06b74b724 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -26,12 +26,14 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" -ResourceManager::ResourceManager() { +ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { _thread.setObjectName("Resource Manager Thread"); - auto assetClient = DependencyManager::set(); - assetClient->moveToThread(&_thread); - QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::initCaching); + if (_atpSupportEnabled) { + auto assetClient = DependencyManager::set(); + assetClient->moveToThread(&_thread); + QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::initCaching); + } _thread.start(); } @@ -111,6 +113,10 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { request = new HTTPResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_ATP) { + if (!_atpSupportEnabled) { + qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url(); + return nullptr; + } request = new AssetResourceRequest(normalizedURL); } else { qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); @@ -146,7 +152,7 @@ bool ResourceManager::resourceExists(const QUrl& url) { reply->deleteLater(); return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; - } else if (scheme == URL_SCHEME_ATP) { + } else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) { auto request = new AssetResourceRequest(url); ByteRange range; range.fromInclusive = 1; diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 5728a7bd32..9fc636f5fe 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -22,19 +22,12 @@ #include "ResourceRequest.h" -const QString URL_SCHEME_QRC = "qrc"; -const QString URL_SCHEME_FILE = "file"; -const QString URL_SCHEME_HTTP = "http"; -const QString URL_SCHEME_HTTPS = "https"; -const QString URL_SCHEME_FTP = "ftp"; -const QString URL_SCHEME_ATP = "atp"; - class ResourceManager: public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: - ResourceManager(); + ResourceManager(bool atpSupportEnabled = true); void setUrlPrefixOverride(const QString& prefix, const QString& replacement); QString normalizeURL(const QString& urlString); @@ -57,6 +50,7 @@ private: using PrefixMap = std::map; + bool _atpSupportEnabled; PrefixMap _prefixMap; QMutex _prefixMapLock; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 77ed589e0b..c1fe6ccd85 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -576,8 +576,9 @@ void Connection::processControl(ControlPacketPointer controlPacket) { // where the other end expired our connection. Let's reset. #ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Got handshake request, stopping SendQueue"; + qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue"; #endif + _hasReceivedHandshakeACK = false; stopSendQueue(); } break; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 5c202fa70c..b49b954b99 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::MaterialEntities); + return static_cast(EntityVersion::MaterialData); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e9fe232335..e0d3bcdb97 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -121,7 +121,7 @@ public: ReplicatedAvatarIdentity, ReplicatedKillAvatar, ReplicatedBulkAvatarData, - OctreeFileReplacementFromUrl, + DomainContentReplacementFromUrl, ChallengeOwnership, EntityScriptCallMethod, ChallengeOwnershipRequest, @@ -171,7 +171,7 @@ public: << PacketTypeEnum::Value::DomainServerPathResponse << PacketTypeEnum::Value::DomainServerAddedNode << PacketTypeEnum::Value::DomainServerConnectionToken << PacketTypeEnum::Value::DomainSettingsRequest << PacketTypeEnum::Value::OctreeDataFileRequest << PacketTypeEnum::Value::OctreeDataFileReply - << PacketTypeEnum::Value::OctreeDataPersist << PacketTypeEnum::Value::OctreeFileReplacementFromUrl + << PacketTypeEnum::Value::OctreeDataPersist << PacketTypeEnum::Value::DomainContentReplacementFromUrl << PacketTypeEnum::Value::DomainSettings << PacketTypeEnum::Value::ICEServerPeerInformation << PacketTypeEnum::Value::ICEServerQuery << PacketTypeEnum::Value::ICEServerHeartbeat << PacketTypeEnum::Value::ICEServerHeartbeatACK << PacketTypeEnum::Value::ICEPing @@ -230,7 +230,9 @@ enum class EntityVersion : PacketVersion { ZoneLightInheritModes = 82, ZoneStageRemoved, SoftEntities, - MaterialEntities + MaterialEntities, + ShadowControl, + MaterialData }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 8b93a05130..4189cb613c 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -402,6 +402,10 @@ void Socket::readPendingDatagrams() { packet->getDataSize(), packet->getPayloadSize())) { // the connection could not be created or indicated that we should not continue processing this packet +#ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) + << ", type" << NLPacket::typeInHeader(*packet); +#endif continue; } } diff --git a/libraries/octree/src/DirtyOctreeElementOperator.cpp b/libraries/octree/src/DirtyOctreeElementOperator.cpp index ed8d26cf72..ae01a3d608 100644 --- a/libraries/octree/src/DirtyOctreeElementOperator.cpp +++ b/libraries/octree/src/DirtyOctreeElementOperator.cpp @@ -14,6 +14,7 @@ DirtyOctreeElementOperator::DirtyOctreeElementOperator(const OctreeElementPointer& element) : _element(element) { assert(_element.get()); + _element->bumpChangedContent(); _point = _element->getAACube().calcCenter(); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 334299185e..dafdfd5bf4 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1685,6 +1685,15 @@ bool Octree::readFromURL(const QString& urlString) { } auto data = request->getData(); + + QByteArray uncompressedJsonData; + bool wasCompressed = gunzip(data, uncompressedJsonData); + + if (wasCompressed) { + QDataStream inputStream(uncompressedJsonData); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); + } + QDataStream inputStream(data); return readFromStream(data.size(), inputStream, marketplaceID); } @@ -1778,7 +1787,7 @@ bool Octree::writeToFile(const char* fileName, const OctreeElementPointer& eleme return success; } -bool Octree::toJSON(QJsonDocument* doc, const OctreeElementPointer& element) { +bool Octree::toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& element) { QVariantMap entityDescription; OctreeElementPointer top; @@ -1804,18 +1813,22 @@ bool Octree::toJSON(QJsonDocument* doc, const OctreeElementPointer& element) { return true; } -bool Octree::toGzippedJSON(QByteArray* data, const OctreeElementPointer& element) { +bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool doGzip) { QJsonDocument doc; - if (!toJSON(&doc, element)) { + if (!toJSONDocument(&doc, element)) { qCritical("Failed to convert Entities to QVariantMap while converting to json."); return false; } - QByteArray jsonData = doc.toJson(); + if (doGzip) { + QByteArray jsonData = doc.toJson(); - if (!gzip(jsonData, *data, -1)) { - qCritical("Unable to gzip data while saving to json."); - return false; + if (!gzip(jsonData, *data, -1)) { + qCritical("Unable to gzip data while saving to json."); + return false; + } + } else { + *data = doc.toJson(); } return true; @@ -1825,7 +1838,7 @@ bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& e qCDebug(octree, "Saving JSON SVO to file %s...", fileName); QByteArray jsonDataForFile; - if (!toGzippedJSON(&jsonDataForFile)) { + if (!toJSON(&jsonDataForFile, element, doGzip)) { return false; } @@ -1837,7 +1850,6 @@ bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& e qCritical("Could not write to JSON description of entities."); } - return success; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index cb281593b1..c4c4508138 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -284,8 +284,8 @@ public: void loadOctreeFile(const char* fileName); // Octree exporters - bool toJSON(QJsonDocument* doc, const OctreeElementPointer& element = nullptr); - bool toGzippedJSON(QByteArray* data, const OctreeElementPointer& element = nullptr); + bool toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& element = nullptr); + bool toJSON(QByteArray* data, const OctreeElementPointer& element = nullptr, bool doGzip = false); bool writeToFile(const char* filename, const OctreeElementPointer& element = nullptr, QString persistAsFileType = "json.gz"); bool writeToJSONFile(const char* filename, const OctreeElementPointer& element = nullptr, bool doGzip = false); virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 9ab5d9429d..514039713b 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -219,6 +219,9 @@ public: int getMyChildContaining(const AABox& box) const; int getMyChildContainingPoint(const glm::vec3& point) const; + void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); } + uint64_t getLastChangedContent() const { return _lastChangedContent; } + protected: void deleteAllChildren(); @@ -235,6 +238,7 @@ protected: } _octalCode; quint64 _lastChanged; /// Client and server, timestamp this node was last changed, 8 bytes + uint64_t _lastChangedContent { 0 }; /// Client and server, pointers to child nodes, various encodings #ifdef SIMPLE_CHILD_ARRAY diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 23d6b6c2aa..e6c28f75e8 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -192,17 +192,12 @@ bool OctreePersistThread::process() { QString lockFileName = _filename + ".lock"; std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate); if (lockFile.is_open()) { - qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName - << "-- Attempting to restore from previous backup file."; - - // This is where we should attempt to find the most recent backup and restore from - // that file as our persist file. - restoreFromMostRecentBackup(); + qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName; lockFile.close(); - qCDebug(octree) << "Loading Octree... lock file closed:" << lockFileName; + qCDebug(octree) << "Removing lock file:" << lockFileName; remove(qPrintable(lockFileName)); - qCDebug(octree) << "Loading Octree... lock file removed:" << lockFileName; + qCDebug(octree) << "Lock file removed:" << lockFileName; } persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); @@ -341,7 +336,7 @@ void OctreePersistThread::sendLatestEntityDataToDS() { const DomainHandler& domainHandler = nodeList->getDomainHandler(); QByteArray data; - if (_tree->toGzippedJSON(&data)) { + if (_tree->toJSON(&data, nullptr, true)) { auto message = NLPacketList::create(PacketType::OctreeDataPersist, QByteArray(), true, true); message->write(data); nodeList->sendPacketList(std::move(message), domainHandler.getSockAddr()); diff --git a/libraries/octree/src/OctreeScriptingInterface.h b/libraries/octree/src/OctreeScriptingInterface.h index c31da94532..e501dd166a 100644 --- a/libraries/octree/src/OctreeScriptingInterface.h +++ b/libraries/octree/src/OctreeScriptingInterface.h @@ -35,48 +35,126 @@ private slots: void cleanupManagedObjects(); public slots: + + /**jsdoc + * Set the maximum number of entity packets that the client can send per second. + * @function Entities.setPacketsPerSecond + * @param {number} packetsPerSecond - Integer maximum number of entity packets that the client can send per second. + */ /// set the max packets per second send rate void setPacketsPerSecond(int packetsPerSecond) { return _packetSender->setPacketsPerSecond(packetsPerSecond); } + /**jsdoc + * Get the maximum number of entity packets that the client can send per second. + * @function Entities.getPacketsPerSecond + * @returns {number} Integer maximum number of entity packets that the client can send per second. + */ /// get the max packets per second send rate int getPacketsPerSecond() const { return _packetSender->getPacketsPerSecond(); } - /// does a particle server exist to send to + /**jsdoc + * Check whether servers exist for the client to send entity packets to, i.e., whether you are connected to a domain and + * its entity server is working. + * @function Entities.serversExist + * @returns {boolean} true if servers exist for the client to send entity packets to, otherwise + * false. + */ + /// does a server exist to send to bool serversExist() const { return _packetSender->serversExist(); } + /**jsdoc + * Check whether the client has entity packets waiting to be sent. + * @function Entities.hasPacketsToSend + * @returns {boolean} true if the client has entity packets waiting to be sent, otherwise false. + */ /// are there packets waiting in the send queue to be sent bool hasPacketsToSend() const { return _packetSender->hasPacketsToSend(); } + /**jsdoc + * Get the number of entity packets the client has waiting to be sent. + * @function Entities.packetsToSendCount + * @returns {number} Integer number of entity packets the client has waiting to be sent. + */ /// how many packets are there in the send queue waiting to be sent int packetsToSendCount() const { return (int)_packetSender->packetsToSendCount(); } + /**jsdoc + * Get the entity packets per second send rate of the client over its lifetime. + * @function Entities.getLifetimePPS + * @returns {number} Entity packets per second send rate of the client over its lifetime. + */ /// returns the packets per second send rate of this object over its lifetime float getLifetimePPS() const { return _packetSender->getLifetimePPS(); } + /**jsdoc + * Get the entity bytes per second send rate of the client over its lifetime. + * @function Entities.getLifetimeBPS + * @returns {number} Entity bytes per second send rate of the client over its lifetime. + */ /// returns the bytes per second send rate of this object over its lifetime float getLifetimeBPS() const { return _packetSender->getLifetimeBPS(); } + /**jsdoc + * Get the entity packets per second queued rate of the client over its lifetime. + * @function Entities.getLifetimePPSQueued + * @returns {number} Entity packets per second queued rate of the client over its lifetime. + */ /// returns the packets per second queued rate of this object over its lifetime float getLifetimePPSQueued() const { return _packetSender->getLifetimePPSQueued(); } + /**jsdoc + * Get the entity bytes per second queued rate of the client over its lifetime. + * @function Entities.getLifetimeBPSQueued + * @returns {number} Entity bytes per second queued rate of the client over its lifetime. + */ /// returns the bytes per second queued rate of this object over its lifetime float getLifetimeBPSQueued() const { return _packetSender->getLifetimeBPSQueued(); } + /**jsdoc + * Get the lifetime of the client from the first entity packet sent until now, in microseconds. + * @function Entities.getLifetimeInUsecs + * @returns {number} Lifetime of the client from the first entity packet sent until now, in microseconds. + */ /// returns lifetime of this object from first packet sent to now in usecs long long unsigned int getLifetimeInUsecs() const { return _packetSender->getLifetimeInUsecs(); } - /// returns lifetime of this object from first packet sent to now in usecs + /**jsdoc + * Get the lifetime of the client from the first entity packet sent until now, in seconds. + * @function Entities.getLifetimeInSeconds + * @returns {number} Lifetime of the client from the first entity packet sent until now, in seconds. + */ + /// returns lifetime of this object from first packet sent to now in secs float getLifetimeInSeconds() const { return _packetSender->getLifetimeInSeconds(); } + /**jsdoc + * Get the total number of entity packets sent by the client over its lifetime. + * @function Entities.getLifetimePacketsSent + * @returns {number} The total number of entity packets sent by the client over its lifetime. + */ /// returns the total packets sent by this object over its lifetime long long unsigned int getLifetimePacketsSent() const { return _packetSender->getLifetimePacketsSent(); } + /**jsdoc + * Get the total bytes of entity packets sent by the client over its lifetime. + * @function Entities.getLifetimeBytesSent + * @returns {number} The total bytes of entity packets sent by the client over its lifetime. + */ /// returns the total bytes sent by this object over its lifetime long long unsigned int getLifetimeBytesSent() const { return _packetSender->getLifetimeBytesSent(); } + /**jsdoc + * Get the total number of entity packets queued by the client over its lifetime. + * @function Entities.getLifetimePacketsQueued + * @returns {number} The total number of entity packets queued by the client over its lifetime. + */ /// returns the total packets queued by this object over its lifetime long long unsigned int getLifetimePacketsQueued() const { return _packetSender->getLifetimePacketsQueued(); } + /**jsdoc + * Get the total bytes of entity packets queued by the client over its lifetime. + * @function Entities.getLifetimeBytesQueued + * @returns {number} The total bytes of entity packets queued by the client over its lifetime. + */ /// returns the total bytes queued by this object over its lifetime long long unsigned int getLifetimeBytesQueued() const { return _packetSender->getLifetimeBytesQueued(); } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index d39930ab76..9b6e9fe7a0 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -756,6 +756,9 @@ void CharacterController::updateState() { SET_STATE(State::Hover, "double jump button"); } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { SET_STATE(State::Hover, "jump button held"); + } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { + // Transition to hover if we are above the fall threshold + SET_STATE(State::Hover, "above fall threshold"); } } break; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 420da5a1e0..c2bacd4949 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,12 +26,7 @@ #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS #include "EntityTree.h" -#endif -const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; -const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; - -#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -46,11 +41,13 @@ bool entityTreeIsLocked() { } #endif +const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; +const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; + EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(nullptr), - _entityPtr(entity), - _entity(entity.get()), + _entity(entity), _serverPosition(0.0f), _serverRotation(), _serverVelocity(0.0f), @@ -60,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverActionData(QByteArray()), _lastVelocity(0.0f), _measuredAcceleration(0.0f), - _nextOwnershipBid(0), + _nextBidExpiry(0), _measuredDeltaTime(0.0f), _lastMeasureStep(0), _lastStep(0), @@ -68,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _accelerationNearlyGravityCount(0), _numInactiveUpdates(1) { + // Why is _numInactiveUpdates initialied to 1? + // Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed, + // which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just + // went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates + // to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case. + _type = MOTIONSTATE_TYPE_ENTITY; assert(_entity); assert(entityTreeIsLocked()); @@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - _outgoingPriority = _entity->getPendingOwnershipPriority(); -} - -EntityMotionState::~EntityMotionState() { - assert(_entity); - _entity = nullptr; -} - -void EntityMotionState::updateServerPhysicsVariables() { - assert(entityTreeIsLocked()); - if (isLocallyOwned()) { - // don't slam these values if we are the simulation owner - return; + _bidPriority = _entity->getPendingOwnershipPriority(); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // client-only entities are always thus, so we cache this fact in _ownershipState + _ownershipState = EntityMotionState::OwnershipState::Unownable; } Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); - _serverVariablesSet = true; _serverPosition = localTransform.getTranslation(); _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getDynamicData(); + +} + +EntityMotionState::~EntityMotionState() { + if (_entity) { + assert(_entity->getPhysicsInfo() == this); + _entity->setPhysicsInfo(nullptr); + _entity.reset(); + } +} + +void EntityMotionState::updateServerPhysicsVariables() { + if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { + // only slam these values if we are NOT the simulation owner + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); + } } void EntityMotionState::handleDeactivation() { - if (_serverVariablesSet) { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero - } + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero } // virtual void EntityMotionState::handleEasyChanges(uint32_t& flags) { - assert(_entity); assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags); if (flags & Simulation::DIRTY_SIMULATOR_ID) { if (_entity->getSimulatorID().isNull()) { - // simulation ownership has been removed by an external simulator + // simulation ownership has been removed if (glm::length2(_entity->getWorldVelocity()) == 0.0f) { - // this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority + // this object is coming to rest --> clear the ACTIVATION flag and _bidPriority flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; _body->setActivationState(WANTS_DEACTIVATION); - _outgoingPriority = 0; + _bidPriority = 0; const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet _body->setDeactivationTime(ACTIVATION_EXPIRY); } else { // disowned object is still moving --> start timer for ownership bid // TODO? put a delay in here proportional to distance from object? - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; _numInactiveUpdates = 0; } else if (isLocallyOwned()) { // we just inherited ownership, make sure our desired priority matches what we have - upgradeOutgoingPriority(_entity->getSimulationPriority()); + upgradeBidPriority(_entity->getSimulationPriority()); } else { - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + // the entity is owned by someone else, so we clear _bidPriority here + // but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation + // in which case we may try to bid again + _bidPriority = 0; + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _numInactiveUpdates = 0; } } @@ -155,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - upgradeOutgoingPriority(newPriority); + upgradeBidPriority(newPriority); // reset bid expiry so that we bid ASAP - _nextOwnershipBid = 0; + _nextBidExpiry = 0; } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { if (_body->isKinematicObject()) { @@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // virtual bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { - assert(_entity); updateServerPhysicsVariables(); return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } @@ -253,7 +267,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { // This callback is invoked by the physics simulation at the end of each simulation step... // iff the corresponding RigidBody is DYNAMIC and ACTIVE. void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { - assert(_entity); assert(entityTreeIsLocked()); measureBodyAcceleration(); @@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); + if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) { + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); } } - - #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); - qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID(); - qCDebug(physics) << " last edited:" << _entity->getLastEdited() - << formatUsecTime(now - _entity->getLastEdited()) << "ago"; - qCDebug(physics) << " last simulated:" << _entity->getLastSimulated() - << formatUsecTime(now - _entity->getLastSimulated()) << "ago"; - qCDebug(physics) << " last updated:" << _entity->getLastUpdated() - << formatUsecTime(now - _entity->getLastUpdated()) << "ago"; - #endif } @@ -323,19 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) { } } -bool EntityMotionState::isCandidateForOwnership() const { - assert(_body); - assert(_entity); - assert(entityTreeIsLocked()); - return _outgoingPriority != 0 - || isLocallyOwned() - || _entity->dynamicDataNeedsTransmit(); -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { - DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // NOTE: we only get here if we think we own the simulation - assert(_body); + DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); + + // Since we own the simulation: make sure _bidPriority is not less than current owned priority + // because: an _bidPriority of zero indicates that we should drop ownership when we have it. + upgradeBidPriority(_entity->getSimulationPriority()); bool parentTransformSuccess; Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); @@ -347,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); } - // if we've never checked before, our _lastStep will be 0, and we need to initialize our state - if (_lastStep == 0) { - btTransform xform = _body->getWorldTransform(); - _serverVariablesSet = true; - _serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin())); - _serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation()); - _serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma()); - _serverAcceleration = Vectors::ZERO; - _serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity())); - _lastStep = simulationStep; - _serverActionData = _entity->getDynamicData(); - _numInactiveUpdates = 1; - return false; - } - - #ifdef WANT_DEBUG - glm::vec3 wasPosition = _serverPosition; - glm::quat wasRotation = _serverRotation; - glm::vec3 wasAngularVelocity = _serverAngularVelocity; - #endif - int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; @@ -378,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _entity->clearSimulationOwnership(); return false; } - // we resend the inactive update every INACTIVE_UPDATE_PERIOD - // until it is removed from the outgoing updates - // (which happens when we don't own the simulation and it isn't touching our simulation) + // we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates + // until it is removed from the owned list + // (which happens when we no longer own the simulation) const float INACTIVE_UPDATE_PERIOD = 0.5f; return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates); } @@ -390,6 +365,11 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return true; } + if (usecTimestampNow() > _entity->getSimulationOwnershipExpiry()) { + // send update every so often else server will revoke our ownership + return true; + } + _lastStep = simulationStep; if (glm::length2(_serverVelocity) > 0.0f) { // the entity-server doesn't know where avatars are, so it doesn't do simple extrapolation for children of @@ -411,14 +391,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { if (_entity->dynamicDataNeedsTransmit()) { uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; - upgradeOutgoingPriority(priority); + upgradeBidPriority(priority); return true; } - if (_entity->shouldSuppressLocationEdits()) { - return false; - } - // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. @@ -440,13 +416,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) { - #ifdef WANT_DEBUG - qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; - qCDebug(physics) << "wasPosition:" << wasPosition; - qCDebug(physics) << "bullet position:" << position; - qCDebug(physics) << "_serverPosition:" << _serverPosition; - qCDebug(physics) << "dx2:" << dx2; - #endif return true; } } @@ -466,22 +435,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); - #ifdef WANT_DEBUG - if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) { - qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ...."; - - qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity; - qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity; - - qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); - qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity); - - qCDebug(physics) << "wasRotation:" << wasRotation; - qCDebug(physics) << "bullet actualRotation:" << actualRotation; - qCDebug(physics) << "_serverRotation:" << _serverRotation; - } - #endif - return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } @@ -489,14 +442,10 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. - assert(_entity); - assert(_body); assert(entityTreeIsLocked()); - if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { - // don't send updates for someone else's avatarEntities - return false; - } + // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor + assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { return true; @@ -506,44 +455,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { return false; } - if (!isLocallyOwned()) { - // we don't own the simulation - - // NOTE: we do not volunteer to own kinematic or static objects - uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0; - - bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND - usecTimestampNow() > _nextOwnershipBid; // it is time to bid again - if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) { - // we are insufficiently interested so clear _outgoingPriority - // and reset the bid expiry - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; - } - return shouldBid; - } else { - // When we own the simulation: make sure _outgoingPriority is not less than current owned priority - // because: an _outgoingPriority of zero indicates that we should drop ownership when we have it. - upgradeOutgoingPriority(_entity->getSimulationPriority()); - } - return remoteSimulationOutOfSync(simulationStep); } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { - DETAILED_PROFILE_RANGE(simulation_physics, "Send"); - assert(_entity); - assert(entityTreeIsLocked()); - +void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { // make sure all derivatives are zero - zeroCleanObjectVelocities(); - _numInactiveUpdates++; + clearObjectVelocities(); + _numInactiveUpdates = 1; } else { glm::vec3 gravity = _entity->getGravity(); - // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let - // the entity server's estimates include gravity. + // if this entity has been accelerated at close to gravity for a certain number of simulation-steps + // let the entity server's estimates include gravity. const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(gravity); @@ -564,13 +488,80 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - zeroCleanObjectVelocities(); + clearObjectVelocities(); } } _numInactiveUpdates = 0; } +} - // remember properties for local server prediction +void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Bid"); + assert(entityTreeIsLocked()); + + updateSendVelocities(); + + EntityItemProperties properties; + Transform localTransform; + glm::vec3 linearVelocity; + glm::vec3 angularVelocity; + _entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity); + properties.setPosition(localTransform.getTranslation()); + properties.setRotation(localTransform.getRotation()); + properties.setVelocity(linearVelocity); + properties.setAcceleration(_entity->getAcceleration()); + properties.setAngularVelocity(angularVelocity); + if (_entity->dynamicDataNeedsTransmit()) { + _entity->setDynamicDataNeedsTransmit(false); + properties.setActionData(_entity->getDynamicData()); + } + + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); + } + + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); + + // we don't own the simulation for this entity yet, but we're sending a bid for it + uint8_t bidPriority = glm::max(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY); + properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); + // copy _bidPriority into pendingPriority... + _entity->setPendingOwnershipPriority(_bidPriority, now); + + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); + _entity->setLastBroadcast(now); // for debug/physics status icons + + // NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent + // then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary. + + _lastStep = step; + _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; + + // finally: clear _bidPriority + // which will may get promoted before next bid + // or maybe we'll win simulation ownership + _bidPriority = 0; +} + +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Send"); + assert(entityTreeIsLocked()); + assert(isLocallyOwned()); + + updateSendVelocities(); + + // remember _serverFoo data for local prediction of server state Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); @@ -579,11 +570,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _serverActionData = _entity->getDynamicData(); EntityItemProperties properties; - - // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); - properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); @@ -592,61 +580,36 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setActionData(_serverActionData); } - if (properties.transformChanged()) { - if (_entity->updateQueryAACube()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - properties.setQueryAACube(_entity->getQueryAACube()); - } + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); - - #ifdef WANT_DEBUG - quint64 lastSimulated = _entity->getLastSimulated(); - qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() - << "---------------------------------------------"; - qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); - #endif //def WANT_DEBUG + _entity->setSimulationOwnershipExpiry(now + MAX_OUTGOING_SIMULATION_UPDATE_PERIOD); if (_numInactiveUpdates > 0) { - // we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID + // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); - _outgoingPriority = 0; - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - } else if (!isLocallyOwned()) { - // we don't own the simulation for this entity yet, but we're sending a bid for it - quint8 bidPriority = glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); - properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); - _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; - // copy _outgoingPriority into pendingPriority... - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - // don't forget to remember that we have made a bid - _entity->rememberHasSimulationOwnershipBid(); - // ...then reset _outgoingPriority - _outgoingPriority = 0; - // _outgoingPrioriuty will be re-computed before next bid, - // or will be set to agree with ownership priority should we win the bid - } else if (_outgoingPriority != _entity->getSimulationPriority()) { - // we own the simulation but our desired priority has changed - if (_outgoingPriority == 0) { + _bidPriority = 0; + _entity->setPendingOwnershipPriority(_bidPriority, now); + } else if (_bidPriority != _entity->getSimulationPriority()) { + // our desired priority has changed + if (_bidPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to change the priority - properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); + properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority); } - _entity->setPendingOwnershipPriority(_outgoingPriority, now); + _entity->setPendingOwnershipPriority(_bidPriority, now); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; - #endif EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -655,7 +618,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); - _entity->setLastBroadcast(now); + _entity->setLastBroadcast(now); // for debug/physics status icons // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -666,13 +629,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(now); + entityDescendant->setLastBroadcast(now); // for debug/physics status icons } } }); @@ -723,6 +685,10 @@ uint8_t EntityMotionState::getSimulationPriority() const { return _entity->getSimulationPriority(); } +void EntityMotionState::slaveBidPriority() { + upgradeBidPriority(_entity->getSimulationPriority()); +} + // virtual QUuid EntityMotionState::getSimulatorID() const { assert(entityTreeIsLocked()); @@ -731,7 +697,7 @@ QUuid EntityMotionState::getSimulatorID() const { void EntityMotionState::bump(uint8_t priority) { assert(priority != 0); - upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -755,13 +721,14 @@ void EntityMotionState::measureBodyAcceleration() { _lastMeasureStep = thisStep; _measuredDeltaTime = dt; - // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt + // Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt glm::vec3 velocity = getBodyLinearVelocityGTSigma(); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { + // we fall in here when _lastMeasureStep is old: the body has just become active _loopsWithoutOwner = 0; _lastStep = ObjectMotionState::getWorldSimulationStep(); _numInactiveUpdates = 0; @@ -805,24 +772,44 @@ QString EntityMotionState::getName() const { // virtual void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const { - assert(_entity); _entity->computeCollisionGroupAndFinalMask(group, mask); } +bool EntityMotionState::shouldSendBid() { + if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) { + return true; + } else { + // NOTE: this 'else' case has a side-effect: it clears _bidPriority + // which may be updated next simulation step (via collision or script event) + _bidPriority = 0; + return false; + } +} + bool EntityMotionState::isLocallyOwned() const { return _entity->getSimulatorID() == Physics::getSessionUUID(); } -bool EntityMotionState::shouldBeLocallyOwned() const { - return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || +bool EntityMotionState::isLocallyOwnedOrShouldBe() const { + return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) || _entity->getSimulatorID() == Physics::getSessionUUID(); } -void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { - _outgoingPriority = glm::max(_outgoingPriority, priority); +void EntityMotionState::initForBid() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::PendingBid; } -void EntityMotionState::zeroCleanObjectVelocities() const { +void EntityMotionState::initForOwned() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; +} + +void EntityMotionState::upgradeBidPriority(uint8_t priority) { + _bidPriority = glm::max(_bidPriority, priority); +} + +void EntityMotionState::clearObjectVelocities() const { // If transform or velocities are flagged as dirty it means a network or scripted change // occured between the beginning and end of the stepSimulation() and we DON'T want to apply // these physics simulation results. diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 784273d600..807acbfe80 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -24,11 +24,17 @@ class EntityMotionState : public ObjectMotionState { public: + enum class OwnershipState { + NotLocallyOwned = 0, + PendingBid, + LocallyOwned, + Unownable + }; + EntityMotionState() = delete; EntityMotionState(btCollisionShape* shape, EntityItemPointer item); virtual ~EntityMotionState(); - void updateServerPhysicsVariables(); void handleDeactivation(); virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; @@ -44,9 +50,8 @@ public: // this relays outgoing position/rotation to the EntityItem virtual void setWorldTransform(const btTransform& worldTrans) override; - bool isCandidateForOwnership() const; - bool remoteSimulationOutOfSync(uint32_t simulationStep); bool shouldSendUpdate(uint32_t simulationStep); + void sendBid(OctreeEditPacketSender* packetSender, uint32_t step); void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); virtual uint32_t getIncomingDirtyFlags() override; @@ -70,7 +75,9 @@ public: virtual QUuid getSimulatorID() const override; virtual void bump(uint8_t priority) override; - EntityItemPointer getEntity() const { return _entityPtr.lock(); } + // getEntity() returns a smart-pointer by reference because it is only ever used + // to insert into lists of smart pointers, and the lists will make their own copies + const EntityItemPointer& getEntity() const { return _entity; } void resetMeasuredBodyAcceleration(); void measureBodyAcceleration(); @@ -79,15 +86,29 @@ public: virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; + bool shouldSendBid(); bool isLocallyOwned() const override; - bool shouldBeLocallyOwned() const override; + bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() friend class PhysicalEntitySimulation; + OwnershipState getOwnershipState() const { return _ownershipState; } protected: - // changes _outgoingPriority only if priority is larger - void upgradeOutgoingPriority(uint8_t priority); - void zeroCleanObjectVelocities() const; + void updateSendVelocities(); + uint64_t getNextBidExpiry() const { return _nextBidExpiry; } + void initForBid(); + void initForOwned(); + void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; } + void updateServerPhysicsVariables(); + bool remoteSimulationOutOfSync(uint32_t simulationStep); + + // changes _bidPriority only if priority is larger + void upgradeBidPriority(uint8_t priority); + + // upgradeBidPriority to value stored in _entity + void slaveBidPriority(); + + void clearObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; @@ -98,17 +119,21 @@ protected: void setShape(const btCollisionShape* shape) override; void setMotionType(PhysicsMotionType motionType) override; - // In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be - // properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that - // state of affairs we can't keep a real EntityItemPointer as data member (it would produce a - // recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while - // still granting us the capability to generate EntityItemPointers as necessary (for external data - // structures that use the MotionState to get to the EntityItem). - EntityItemWeakPointer _entityPtr; - // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. - EntityItem* _entity; + // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR + // and is only cleared in the DTOR + EntityItemPointer _entity; - bool _serverVariablesSet { false }; + // These "_serverFoo" variables represent what we think the server knows. + // They are used in two different modes: + // + // (1) For remotely owned simulation: we store the last values recieved from the server. + // When the body comes to rest and goes inactive we slam its final transforms to agree with the last server + // update. This to reduce state synchronization errors when the local simulation deviated from remote. + // + // (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time + // according to how we think the server doing it. We calculate the error between the true local transform + // and the remote to decide when to send another update. + // glm::vec3 _serverPosition; // in simulation-frame (not world-frame) glm::quat _serverRotation; glm::vec3 _serverVelocity; @@ -119,16 +144,18 @@ protected: glm::vec3 _lastVelocity; glm::vec3 _measuredAcceleration; - quint64 _nextOwnershipBid { 0 }; + quint64 _nextBidExpiry { 0 }; float _measuredDeltaTime; uint32_t _lastMeasureStep; uint32_t _lastStep; // last step of server extrapolation + OwnershipState _ownershipState { OwnershipState::NotLocallyOwned }; uint8_t _loopsWithoutOwner; mutable uint8_t _accelerationNearlyGravityCount; uint8_t _numInactiveUpdates { 1 }; - uint8_t _outgoingPriority { 0 }; + uint8_t _bidPriority { 0 }; + bool _serverVariablesSet { false }; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index c1fb397e19..e90862266b 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -142,6 +142,18 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "offset" {@link Entities.ActionType|ActionType} moves an entity so that it is a set distance away from a + * target point. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Offset + * @property {Vec3} pointToOffsetFrom=0,0,0 - The target point to offset the entity from. + * @property {number} linearDistance=0 - The distance away from the target point to position the entity. + * @property {number} linearTimeScale=34e+38 - Controls how long it takes for the entity's position to catch up with the + * target offset. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * is applied using an exponential decay. + */ QVariantMap ObjectActionOffset::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index 03e6533c87..bc68d6de73 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -307,6 +307,23 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "tractor" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and + * orientation, optionally relative to another entity. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Tractor + * @property {Vec3} targetPosition=0,0,0 - The target position. + * @property {Quat} targetRotation=0,0,0,1 - The target rotation. + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * relative to this entity's position and rotation. + * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the + * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * is applied using an exponential decay. + * @property {number} angularTimeScale=3.4e+38 - Controls how long it takes for the entity's orientation to catch up with the + * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * action is applied using an exponential decay. + */ QVariantMap ObjectActionTractor::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp index 8ab24511d7..accade8695 100644 --- a/libraries/physics/src/ObjectActionTravelOriented.cpp +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -146,6 +146,17 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "travel-oriented" {@link Entities.ActionType|ActionType} orients an entity to align with its direction of + * travel. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-TravelOriented + * @property {Vec3} forward=0,0,0 - The axis of the entity to align with the entity's direction of travel. + * @property {number} angularTimeScale=0.1 - Controls how long it takes for the entity's orientation to catch up with the + * direction of travel. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * action is applied using an exponential decay. + */ QVariantMap ObjectActionTravelOriented::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp index 9dd85954a3..70613d46ae 100644 --- a/libraries/physics/src/ObjectConstraintBallSocket.cpp +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -181,6 +181,15 @@ bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "ball-socket" {@link Entities.ActionType|ActionType} connects two entities with a ball and socket joint. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-BallSocket + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. + * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. + */ QVariantMap ObjectConstraintBallSocket::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index 49f926af81..86f1f21c63 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -261,6 +261,21 @@ bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "cone-twist" {@link Entities.ActionType|ActionType} connects two entities with a joint that can move + * through a cone and can twist. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-ConeTwist + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. + * @property {Vec3} axis=1,0,0 - The axis of the entity that moves through the cone. Must be a non-zero vector. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. + * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. + * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that moves through the cone. Must be a non-zero vector. + * @property {number} swingSpan1=6.238 - The angle through which the joint can move in one axis of the cone, in radians. + * @property {number} swingSpan2=6.238 - The angle through which the joint can move in the other axis of the cone, in radians. + * @property {number} twistSpan=6.238 - The angle through with the joint can twist, in radians. + */ QVariantMap ObjectConstraintConeTwist::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 52be64796a..99ddd45abd 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -245,6 +245,22 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "hinge" {@link Entities.ActionType|ActionType} lets an entity pivot about an axis or connects two entities + * with a hinge joint. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Hinge + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. + * @property {Vec3} axis=1,0,0 - The axis of the entity that it pivots about. Must be a non-zero vector. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If none is + * specified then the first entity simply pivots about its specified axis. + * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. + * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that it pivots about. Must be a non-zero vector. + * @property {number} low=-6.283 - The most negative angle that the hinge can take, in radians. + * @property {number} high=6.283 - The most positive angle that the hinge can take, in radians. + * @property {number} angle=0 - The current angle of the hinge. Read-only. + */ QVariantMap ObjectConstraintHinge::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index ded9ad47e6..c236afc10d 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -261,6 +261,31 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "slider" {@link Entities.ActionType|ActionType} lets an entity slide and rotate along an axis, or connects + * two entities that slide and rotate along a shared axis. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Slider + * @property {Vec3} point=0,0,0 - The local position of a point in the entity that slides along the axis. + * @property {Vec3} axis=1,0,0 - The axis of the entity that slides along the joint. Must be a non-zero vector. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If non is + * specified then the first entity simply slides and rotates about its specified axis. + * @property {Vec3} otherPoint=0,0,0 - The local position of a point in the other entity that slides along the axis. + * @property {Vec3} axis=1,0,0 - The axis of the other entity that slides along the joint. Must be a non-zero vector. + * @property {number} linearLow=1.17e-38 - The most negative linear offset from the entity's initial point that the entity can + * have along the slider. + * @property {number} linearHigh=3.40e+38 - The most positive linear offset from the entity's initial point that the entity can + * have along the slider. + * @property {number} angularLow=-6.283 - The most negative angle that the entity can rotate about the axis if the action + * involves only one entity, otherwise the most negative angle the rotation can be between the two entities. In radians. + * @property {number} angularHigh=6.283 - The most positive angle that the entity can rotate about the axis if the action + * involves only one entity, otherwise the most positive angle the rotation can be between the two entities. In radians. + * @property {number} linearPosition=0 - The current linear offset the entity is from its initial point if the action involves + * only one entity, otherwise the linear offset between the two entities' action points. Read-only. + * @property {number} angularPosition=0 - The current angular offset of the entity from its initial rotation if the action + * involves only one entity, otherwise the angular offset between the two entities. Read-only. + */ QVariantMap ObjectConstraintSlider::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectDynamic.cpp b/libraries/physics/src/ObjectDynamic.cpp index 3deadd6468..5bbb5981d1 100644 --- a/libraries/physics/src/ObjectDynamic.cpp +++ b/libraries/physics/src/ObjectDynamic.cpp @@ -93,6 +93,38 @@ bool ObjectDynamic::updateArguments(QVariantMap arguments) { return somethingChanged; } +/**jsdoc +* Different entity action types have different arguments: some common to all actions (listed below) and some specific to each +* {@link Entities.ActionType|ActionType} (linked to below). The arguments are accessed as an object of property names and +* values. +* +* @typedef {object} Entities.ActionArguments +* @property {Entities.ActionType} type - The type of action. +* @property {string} tag="" - A string that a script can use for its own purposes. +* @property {number} ttl=0 - How long the action should exist, in seconds, before it is automatically deleted. A value of +* 0 means that the action should not be deleted. +* @property {boolean} isMine=true - Is true if you created the action during your current Interface session, +* false otherwise. Read-only. +* @property {boolean} ::no-motion-state - Is present when the entity hasn't been registered with the physics engine yet (e.g., +* if the action hasn't been properly configured), otherwise undefined. Read-only. +* @property {boolean} ::active - Is true when the action is modifying the entity's motion, false +* otherwise. Is present once the entity has been registered with the physics engine, otherwise undefined. +* Read-only. +* @property {Entities.PhysicsMotionType} ::motion-type - How the entity moves with the action. Is present once the entity has +* been registered with the physics engine, otherwise undefined. Read-only. +* +* @see The different action types have additional arguments as follows: +* @see {@link Entities.ActionArguments-FarGrab|ActionArguments-FarGrab} +* @see {@link Entities.ActionArguments-Hold|ActionArguments-Hold} +* @see {@link Entities.ActionArguments-Offset|ActionArguments-Offset} +* @see {@link Entities.ActionArguments-Tractor|ActionArguments-Tractor} +* @see {@link Entities.ActionArguments-TravelOriented|ActionArguments-TravelOriented} +* @see {@link Entities.ActionArguments-Hinge|ActionArguments-Hinge} +* @see {@link Entities.ActionArguments-Slider|ActionArguments-Slider} +* @see {@link Entities.ActionArguments-ConeTwist|ActionArguments-ConeTwist} +* @see {@link Entities.ActionArguments-BallSocket|ActionArguments-BallSocket} +*/ +// Note: The "type" property is set in EntityItem::getActionArguments(). QVariantMap ObjectDynamic::getArguments() { QVariantMap arguments; withReadLock([&]{ diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 0b91ede574..fbda9366fc 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -29,6 +29,23 @@ enum PhysicsMotionType { MOTION_TYPE_KINEMATIC // keyframed motion }; +/**jsdoc + *

An entity's physics motion type may be one of the following:

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
"static"There is no motion because the entity is locked — its locked + * property is set to true.
"kinematic"Motion is applied without physical laws (e.g., damping) because the entity is + * not locked and has its dynamic property set to false.
"dynamic"Motion is applied according to physical laws (e.g., damping) because the entity + * is not locked and has its dynamic property set to true.
+ * @typedef {string} Entities.PhysicsMotionType + */ inline QString motionTypeToString(PhysicsMotionType motionType) { switch(motionType) { case MOTION_TYPE_STATIC: return QString("static"); @@ -131,9 +148,9 @@ public: virtual const QUuid getObjectID() const = 0; - virtual quint8 getSimulationPriority() const { return 0; } + virtual uint8_t getSimulationPriority() const { return 0; } virtual QUuid getSimulatorID() const = 0; - virtual void bump(quint8 priority) {} + virtual void bump(uint8_t priority) {} virtual QString getName() const { return ""; } @@ -147,7 +164,7 @@ public: void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } virtual bool isLocallyOwned() const { return false; } - virtual bool shouldBeLocallyOwned() const { return false; } + virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() friend class PhysicsEngine; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index e4ba47e205..06e7069f72 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init( } // begin EntitySimulation overrides -void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { +void PhysicalEntitySimulation::updateEntitiesInternal(uint64_t now) { // Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere. } @@ -61,33 +61,58 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { if (entity->isSimulated()) { EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); _entitiesToAddToPhysics.remove(entity); EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); - } else { - _entitiesToDelete.insert(entity); + } else if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); } } } -void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { - QMutexLocker lock(&_mutex); - for (auto entity : _entitiesToDelete) { - // this entity is still in its tree, so we insert into the external list - entitiesToDelete.push_back(entity); +void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { + assert(motionState); + if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) { + for (uint32_t i = 0; i < _owned.size(); ++i) { + if (_owned[i] == motionState) { + _owned[i]->clearOwnershipState(); + _owned.remove(i); + } + } + } else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) { + for (uint32_t i = 0; i < _bids.size(); ++i) { + if (_bids[i] == motionState) { + _bids[i]->clearOwnershipState(); + _bids.remove(i); + } + } + } +} - // Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo - // rather than do it here +void PhysicalEntitySimulation::clearOwnershipData() { + for (uint32_t i = 0; i < _owned.size(); ++i) { + _owned[i]->clearOwnershipState(); + } + _owned.clear(); + for (uint32_t i = 0; i < _bids.size(); ++i) { + _bids[i]->clearOwnershipState(); + } + _bids.clear(); +} + +void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) { + QMutexLocker lock(&_mutex); + for (auto entity : _deadEntities) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { _entitiesToRemoveFromPhysics.insert(entity); } } - _entitiesToDelete.clear(); + _deadEntities.swap(deadEntities); + _deadEntities.clear(); } void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { @@ -98,15 +123,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { if (motionState) { if (!entity->shouldBePhysical()) { // the entity should be removed from the physical simulation - _pendingChanges.remove(motionState); + _incomingChanges.remove(motionState); _physicalObjects.remove(motionState); - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); if (entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } } else { - _pendingChanges.insert(motionState); + _incomingChanges.insert(motionState); } } else if (entity->shouldBePhysical()) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. @@ -125,80 +150,68 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // while it is in the middle of a simulation step. As it is, we're probably in shutdown mode // anyway, so maybe the simulation was already properly shutdown? Cross our fingers... - // copy everything into _entitiesToDelete - for (auto stateItr : _physicalObjects) { - EntityMotionState* motionState = static_cast(&(*stateItr)); - _entitiesToDelete.insert(motionState->getEntity()); - } - - // then remove the objects (aka MotionStates) from physics + // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); // delete the MotionStates - // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete - // its own PhysicsInfo rather than do it here - for (auto entity : _entitiesToDelete) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - entity->setPhysicsInfo(nullptr); - delete motionState; - } + for (auto stateItr : _physicalObjects) { + EntityMotionState* motionState = static_cast(&(*stateItr)); + assert(motionState); + EntityItemPointer entity = motionState->getEntity(); + // TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // until then we must do it here + delete motionState; } - - // finally clear all lists maintained by this class _physicalObjects.clear(); + + // clear all other lists specific to this derived class + clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); - _entitiesToRelease.clear(); _entitiesToAddToPhysics.clear(); - _pendingChanges.clear(); - _outgoingChanges.clear(); + _incomingChanges.clear(); } // virtual void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { assert(entity); assert(entity->isDead()); + QMutexLocker lock(&_mutex); entity->clearActions(getThisPointer()); removeEntityInternal(entity); } // end EntitySimulation overrides -void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { - result.clear(); +const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() { QMutexLocker lock(&_mutex); for (auto entity: _entitiesToRemoveFromPhysics) { - // make sure it isn't on any side lists - _entitiesToAddToPhysics.remove(entity); - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - _pendingChanges.remove(motionState); - _outgoingChanges.remove(motionState); - _physicalObjects.remove(motionState); - result.push_back(motionState); - _entitiesToRelease.insert(entity); + assert(motionState); + + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); } - if (entity->isDead()) { - _entitiesToDelete.insert(entity); - } + _incomingChanges.remove(motionState); + removeOwnershipData(motionState); + _physicalObjects.remove(motionState); + + // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) + _objectsToDelete.push_back(motionState); } _entitiesToRemoveFromPhysics.clear(); + return _objectsToDelete; } void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() { QMutexLocker lock(&_mutex); - for (auto entity: _entitiesToRelease) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - assert(motionState); - entity->setPhysicsInfo(nullptr); + for (auto motionState : _objectsToDelete) { + // someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // until then we must do it here + // NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor delete motionState; - - if (entity->isDead()) { - _entitiesToDelete.insert(entity); - } } - _entitiesToRelease.clear(); + _objectsToDelete.clear(); } void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { @@ -248,18 +261,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) { QMutexLocker lock(&_mutex); for (auto object : objectsToChange) { - _pendingChanges.insert(static_cast(object)); + _incomingChanges.insert(static_cast(object)); } } void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - for (auto stateItr : _pendingChanges) { + for (auto stateItr : _incomingChanges) { EntityMotionState* motionState = &(*stateItr); result.push_back(motionState); } - _pendingChanges.clear(); + _incomingChanges.clear(); } void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) { @@ -279,20 +292,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); - // walk the motionStates looking for those that correspond to entities - { - PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - EntityItemPointer entity = entityState->getEntity(); - assert(entity.get()); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + _entitiesToSort.insert(entityState->getEntity()); + if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) { + // NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in + // and is distinct from entityState->isLocallyOwned() which checks the simulation ownership + // properties of the corresponding EntityItem. It is possible for the two states to be out + // of sync. In fact, we're trying to put them back into sync here. + if (entityState->isLocallyOwned()) { + addOwnership(entityState); + } else if (entityState->shouldSendBid()) { + addOwnershipBid(entityState); } - _entitiesToSort.insert(entity); } } } @@ -302,26 +317,78 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta _lastStepSendPackets = numSubsteps; if (Physics::getSessionUUID().isNull()) { - // usually don't get here, but if so --> nothing to do - _outgoingChanges.clear(); - return; + // usually don't get here, but if so clear all ownership + clearOwnershipData(); } + // send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list + sendOwnedUpdates(numSubsteps); + sendOwnershipBids(numSubsteps); + } +} - // look for entities to prune or update - PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); - QSet::iterator stateItr = _outgoingChanges.begin(); - while (stateItr != _outgoingChanges.end()) { - EntityMotionState* state = *stateItr; - if (!state->isCandidateForOwnership()) { - // prune - stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps)) { - // update - state->sendUpdate(_entityPacketSender, numSubsteps); - ++stateItr; - } else { - ++stateItr; +void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) { + motionState->initForBid(); + motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps()); + _bids.push_back(motionState); + _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry()); +} + +void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) { + motionState->initForOwned(); + _owned.push_back(motionState); +} + +void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) { + uint64_t now = usecTimestampNow(); + if (now > _nextBidExpiry) { + PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size()); + _nextBidExpiry = std::numeric_limits::max(); + uint32_t i = 0; + while (i < _bids.size()) { + bool removeBid = false; + if (_bids[i]->isLocallyOwned()) { + // when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored + // in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h) + // therefore we need to immediately send an update so that the values stored are what we're + // "telling" the server rather than what we've been "hearing" from the server. + _bids[i]->slaveBidPriority(); + _bids[i]->sendUpdate(_entityPacketSender, numSubsteps); + + addOwnership(_bids[i]); + removeBid = true; + } else if (!_bids[i]->shouldSendBid()) { + removeBid = true; + _bids[i]->clearOwnershipState(); } + if (removeBid) { + _bids.remove(i); + } else { + if (now > _bids[i]->getNextBidExpiry()) { + _bids[i]->sendBid(_entityPacketSender, numSubsteps); + _nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry()); + } + ++i; + } + } + } +} + +void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) { + PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size()); + uint32_t i = 0; + while (i < _owned.size()) { + if (!_owned[i]->isLocallyOwned()) { + if (_owned[i]->shouldSendBid()) { + addOwnershipBid(_owned[i]); + } else { + _owned[i]->clearOwnershipState(); + } + _owned.remove(i); + } else { + if (_owned[i]->shouldSendUpdate(numSubsteps)) { + _owned[i]->sendUpdate(_entityPacketSender, numSubsteps); + } + ++i; } } } @@ -336,7 +403,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll } } - void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) { if (_physicsEngine) { // FIXME put fine grain locking into _physicsEngine diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index b9acf4cace..7b6fe221fb 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -27,7 +27,19 @@ class PhysicalEntitySimulation; using PhysicalEntitySimulationPointer = std::shared_ptr; using SetOfEntityMotionStates = QSet; +class VectorOfEntityMotionStates: public std::vector { +public: + void remove(uint32_t index) { + assert(index < size()); + if (index < size() - 1) { + (*this)[index] = back(); + } + pop_back(); + } +}; + class PhysicalEntitySimulation : public EntitySimulation { + Q_OBJECT public: PhysicalEntitySimulation(); ~PhysicalEntitySimulation(); @@ -37,21 +49,28 @@ public: virtual void addDynamic(EntityDynamicPointer dynamic) override; virtual void applyDynamicChanges() override; - virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override; + virtual void takeDeadEntities(SetOfEntities& deadEntities) override; + +signals: + void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); protected: // only called by EntitySimulation // overrides for EntitySimulation - virtual void updateEntitiesInternal(const quint64& now) override; + virtual void updateEntitiesInternal(uint64_t now) override; virtual void addEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void clearEntitiesInternal() override; + void removeOwnershipData(EntityMotionState* motionState); + void clearOwnershipData(); + public: virtual void prepareEntityForDelete(EntityItemPointer entity) override; - void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result); + const VectorOfMotionStates& getObjectsToRemoveFromPhysics(); void deleteObjectsRemovedFromPhysics(); + void getObjectsToAddToPhysics(VectorOfMotionStates& result); void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void getObjectsToChange(VectorOfMotionStates& result); @@ -62,19 +81,28 @@ public: EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } -private: - SetOfEntities _entitiesToRemoveFromPhysics; - SetOfEntities _entitiesToRelease; - SetOfEntities _entitiesToAddToPhysics; + void addOwnershipBid(EntityMotionState* motionState); + void addOwnership(EntityMotionState* motionState); + void sendOwnershipBids(uint32_t numSubsteps); + void sendOwnedUpdates(uint32_t numSubsteps); - SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed - SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server +private: + SetOfEntities _entitiesToAddToPhysics; + SetOfEntities _entitiesToRemoveFromPhysics; + + VectorOfMotionStates _objectsToDelete; + + SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources + // and need their RigidBodies updated SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine PhysicsEnginePointer _physicsEngine = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr; + VectorOfEntityMotionStates _owned; + VectorOfEntityMotionStates _bids; + uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 2d84f0cef1..1d4c385f07 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { // modify the logic below. // // We only create events when at least one of the objects is (or should be) owned in the local simulation. - if (motionStateA && (motionStateA->shouldBeLocallyOwned())) { + if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) { QUuid idA = motionStateA->getObjectID(); QUuid idB; if (motionStateB) { @@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { (motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f)); glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); - } else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) { + } else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) { QUuid idB = motionStateB->getObjectID(); QUuid idA; if (motionStateA) { diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 2a8a72f594..47503e8f85 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -42,4 +42,9 @@ std::function Displa hudOperator = _hudOperator; } return hudOperator; -} \ No newline at end of file +} + +glm::mat4 HmdDisplay::getEyeToHeadTransform(Eye eye) const { + static const glm::mat4 xform; + return xform; +} diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 2c717f629c..ff6801109e 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -93,9 +93,7 @@ class HmdDisplay : public StereoDisplay { public: // HMD specific methods // TODO move these into another class? - virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { - static const glm::mat4 transform; return transform; - } + virtual glm::mat4 getEyeToHeadTransform(Eye eye) const; // returns a copy of the most recent head pose, computed via updateHeadPose virtual glm::mat4 getHeadPose() const { @@ -142,9 +140,6 @@ public: virtual void setContext(const gpu::ContextPointer& context) final { _gpuContext = context; } virtual void submitFrame(const gpu::FramePointer& newFrame) = 0; - // Does the rendering surface have current focus? - virtual bool hasFocus() const = 0; - // The size of the rendering target (may be larger than the device size due to distortion) virtual glm::uvec2 getRecommendedRenderSize() const = 0; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index dbd6845676..e9c084e132 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -96,7 +96,9 @@ const LoaderList& getLoadedPlugins() { static std::once_flag once; static LoaderList loadedPlugins; std::call_once(once, [&] { -#ifdef Q_OS_MAC +#if defined(Q_OS_ANDROID) + QString pluginPath = QCoreApplication::applicationDirPath() + "/"; +#elif defined(Q_OS_MAC) QString pluginPath = QCoreApplication::applicationDirPath() + "/../PlugIns/"; #else QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins/"; @@ -106,6 +108,10 @@ const LoaderList& getLoadedPlugins() { pluginDir.setFilter(QDir::Files); if (pluginDir.exists()) { qInfo() << "Loading runtime plugins from " << pluginPath; +#if defined(Q_OS_ANDROID) + // Can be a better filter and those libs may have a better name to destinguish them from qt plugins + pluginDir.setNameFilters(QStringList() << "libplugins_lib*.so"); +#endif auto candidates = pluginDir.entryList(); for (auto plugin : candidates) { qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin); diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index 10c9d6cf84..49a039935c 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -37,7 +37,7 @@ template class PickCacheOptimizer { public: - void update(std::unordered_map>& picks, bool shouldPickHUD); + void update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); protected: typedef std::unordered_map> PickCache; @@ -67,66 +67,84 @@ void PickCacheOptimizer::cacheResult(const bool intersects, const PickResultP } template -void PickCacheOptimizer::update(std::unordered_map>& picks, bool shouldPickHUD) { +void PickCacheOptimizer::update(std::unordered_map>& picks, + uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) { PickCache results; - for (const auto& pickPair : picks) { - std::shared_ptr> pick = std::static_pointer_cast>(pickPair.second); - + const uint32_t INVALID_PICK_ID = 0; + auto itr = picks.begin(); + if (nextToUpdate != INVALID_PICK_ID) { + itr = picks.find(nextToUpdate); + if (itr == picks.end()) { + itr = picks.begin(); + } + } + uint32_t numUpdates = 0; + while(numUpdates < picks.size()) { + std::shared_ptr> pick = std::static_pointer_cast>(itr->second); T mathematicalPick = pick->getMathematicalPick(); PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap()); if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f || !mathematicalPick) { pick->setPickResult(res); - continue; - } - - if (pick->getFilter().doesPickEntities()) { - PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; - if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { - PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); - if (entityRes) { - cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); - } - } - } - - if (pick->getFilter().doesPickOverlays()) { - PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; - if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { - PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); - if (overlayRes) { - cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); - } - } - } - - if (pick->getFilter().doesPickAvatars()) { - PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; - if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { - PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); - if (avatarRes) { - cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); - } - } - } - - // Can't intersect with HUD in desktop mode - if (pick->getFilter().doesPickHUD() && shouldPickHUD) { - PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector(), QVector() }; - if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { - PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); - if (hudRes) { - cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); - } - } - } - - if (pick->getMaxDistance() == 0.0f || (pick->getMaxDistance() > 0.0f && res->checkOrFilterAgainstMaxDistance(pick->getMaxDistance()))) { - pick->setPickResult(res); } else { - pick->setPickResult(pick->getDefaultResult(mathematicalPick.toVariantMap())); + if (pick->getFilter().doesPickEntities()) { + PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; + if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { + PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); + if (entityRes) { + cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); + } + } + } + + if (pick->getFilter().doesPickOverlays()) { + PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; + if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { + PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); + if (overlayRes) { + cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + } + } + } + + if (pick->getFilter().doesPickAvatars()) { + PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; + if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { + PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); + if (avatarRes) { + cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + } + } + } + + // Can't intersect with HUD in desktop mode + if (pick->getFilter().doesPickHUD() && shouldPickHUD) { + PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector(), QVector() }; + if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { + PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); + if (hudRes) { + cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); + } + } + } + + if (pick->getMaxDistance() == 0.0f || (pick->getMaxDistance() > 0.0f && res->checkOrFilterAgainstMaxDistance(pick->getMaxDistance()))) { + pick->setPickResult(res); + } else { + pick->setPickResult(pick->getDefaultResult(mathematicalPick.toVariantMap())); + } + } + + ++itr; + if (itr == picks.end()) { + itr = picks.begin(); + } + nextToUpdate = itr->first; + ++numUpdates; + if (usecTimestampNow() > expiry) { + break; } } } -#endif // hifi_PickCacheOptimizer_h \ No newline at end of file +#endif // hifi_PickCacheOptimizer_h diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index 92fec014da..ba8fa814f0 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -89,14 +89,17 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector& includ } void PickManager::update() { + uint64_t expiry = usecTimestampNow() + _perFrameTimeBudget; std::unordered_map>> cachedPicks; withReadLock([&] { cachedPicks = _picks; }); bool shouldPickHUD = _shouldPickHUDOperator(); - _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD); - _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], false); + // we pass the same expiry to both updates, but the stylus updates are relatively cheap + // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget + _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); + _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); } bool PickManager::isLeftHand(unsigned int uid) { @@ -121,4 +124,4 @@ bool PickManager::isMouse(unsigned int uid) { return pick->isMouse(); } return false; -} \ No newline at end of file +} diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 5b069879a8..3b466be2bc 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -14,6 +14,8 @@ #include "Pick.h" #include "PickCacheOptimizer.h" +#include + class PickManager : public Dependency, protected ReadWriteLockable { SINGLETON_DEPENDENCY @@ -48,17 +50,24 @@ public: static const unsigned int INVALID_PICK_ID { 0 }; + unsigned int getPerFrameTimeBudget() const { return _perFrameTimeBudget; } + void setPerFrameTimeBudget(unsigned int numUsecs) { _perFrameTimeBudget = numUsecs; } + protected: std::function _shouldPickHUDOperator; std::function _calculatePos2DFromHUDOperator; std::shared_ptr findPick(unsigned int uid) const; std::unordered_map>> _picks; + unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 }; std::unordered_map _typeMap; unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; PickCacheOptimizer _stylusPickCacheOptimizer; + + static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; + unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; }; -#endif // hifi_PickManager_h \ No newline at end of file +#endif // hifi_PickManager_h diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 5307e17355..031baece5f 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,8 +68,9 @@ void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { auto pickResult = getPrevPickResult(); - updateVisuals(pickResult); - generatePointerEvents(pointerID, pickResult); + auto visualPickResult = getVisualPickResult(pickResult); + updateVisuals(visualPickResult); + generatePointerEvents(pointerID, visualPickResult); }); } diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 3197c80cad..0c842dbd88 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -89,6 +89,7 @@ protected: virtual bool shouldHover(const PickResultPointer& pickResult) { return true; } virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; } + virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; }; static const float POINTER_MOVE_DELAY; static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED; diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 56b2c13e95..9c1bb79355 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -166,27 +166,49 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) case QEvent::TouchUpdate: case QEvent::TouchEnd: { QTouchEvent *originalEvent = static_cast(event); - QTouchEvent fakeEvent(*originalEvent); - auto newTouchPoints = fakeEvent.touchPoints(); - for (size_t i = 0; i < newTouchPoints.size(); ++i) { - const auto &originalPoint = originalEvent->touchPoints()[i]; - auto &newPoint = newTouchPoints[i]; - newPoint.setPos(originalPoint.pos()); + QEvent::Type fakeMouseEventType = QEvent::None; + Qt::MouseButton fakeMouseButton = Qt::LeftButton; + Qt::MouseButtons fakeMouseButtons = Qt::NoButton; + switch (event->type()) { + case QEvent::TouchBegin: + fakeMouseEventType = QEvent::MouseButtonPress; + fakeMouseButtons = Qt::LeftButton; + break; + case QEvent::TouchUpdate: + fakeMouseEventType = QEvent::MouseMove; + fakeMouseButtons = Qt::LeftButton; + break; + case QEvent::TouchEnd: + fakeMouseEventType = QEvent::MouseButtonRelease; + fakeMouseButtons = Qt::NoButton; + break; } - fakeEvent.setTouchPoints(newTouchPoints); - if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeEvent)) { - qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeEvent.type() - << "_quickWindow handled it... accepted:" << fakeEvent.isAccepted(); - return false; //event->isAccepted(); + // Same case as OffscreenUi.cpp::eventFilter: touch events are always being accepted so we now use mouse events and consider one touch, touchPoints()[0]. + QMouseEvent fakeMouseEvent(fakeMouseEventType, originalEvent->touchPoints()[0].pos(), fakeMouseButton, fakeMouseButtons, Qt::NoModifier); + fakeMouseEvent.ignore(); + if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeMouseEvent)) { + /*qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeMouseEvent.type() + << "_quickWindow handled it... accepted:" << fakeMouseEvent.isAccepted();*/ + return fakeMouseEvent.isAccepted(); } break; } case QEvent::InputMethod: case QEvent::InputMethodQuery: { - if (_sharedObject->getWindow() && _sharedObject->getWindow()->activeFocusItem()) { + auto window = getWindow(); + if (window && window->activeFocusItem()) { event->ignore(); - if (QCoreApplication::sendEvent(_sharedObject->getWindow()->activeFocusItem(), event)) { - return event->isAccepted(); + if (QCoreApplication::sendEvent(window->activeFocusItem(), event)) { + bool eventAccepted = event->isAccepted(); + if (event->type() == QEvent::InputMethodQuery) { + QInputMethodQueryEvent *imqEvent = static_cast(event); + // this block disables the selection cursor in android which appears in + // the top-left corner of the screen + if (imqEvent->queries() & Qt::ImEnabled) { + imqEvent->setValue(Qt::ImEnabled, QVariant(false)); + } + } + return eventAccepted; } return false; } @@ -334,6 +356,11 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, // Make sure we make items focusable (critical for // supporting keyboard shortcuts) newItem->setFlag(QQuickItem::ItemIsFocusScope, true); +#ifdef DEBUG + for (auto frame : newObject->findChildren("Frame")) { + frame->setProperty("qmlFile", qmlComponent->url()); + } +#endif } bool rootCreated = getRootItem() != nullptr; diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index f2af5bd036..d593169d94 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -37,9 +37,8 @@ static const int MIN_TIMER_MS = 5; using namespace hifi::qml; using namespace hifi::qml::impl; -TextureCache offscreenTextures; - TextureCache& SharedObject::getTextureCache() { + static TextureCache offscreenTextures; return offscreenTextures; } @@ -69,6 +68,7 @@ SharedObject::SharedObject() { _quickWindow->setColor(QColor(255, 255, 255, 0)); _quickWindow->setClearBeforeRendering(true); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } @@ -124,6 +124,7 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); + // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -152,9 +153,16 @@ void SharedObject::destroy() { QObject::disconnect(_renderControl); QObject::disconnect(qApp); - QMutexLocker lock(&_mutex); - _quit = true; - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit)); + { + QMutexLocker lock(&_mutex); + _quit = true; + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + } + // Block until the rendering thread has stopped + // FIXME this is undesirable because this is blocking the main thread, + // but I haven't found a reliable way to do this only at application + // shutdown + _renderThread->wait(); } @@ -234,7 +242,7 @@ void SharedObject::releaseTextureAndFence() { QMutexLocker lock(&_mutex); // If the most recent texture was unused, we can directly recycle it if (_latestTextureAndFence.first) { - offscreenTextures.releaseTexture(_latestTextureAndFence); + getTextureCache().releaseTexture(_latestTextureAndFence); _latestTextureAndFence = TextureAndFence{ 0, 0 }; } } @@ -298,7 +306,10 @@ bool SharedObject::preRender() { void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size) { QMutexLocker locker(&_mutex); if (size != QSize(0, 0)) { - offscreenTextures.releaseSize(size); + getTextureCache().releaseSize(size); + if (_latestTextureAndFence.first) { + getTextureCache().releaseTexture(_latestTextureAndFence); + } } _renderControl->invalidate(); canvas.doneCurrent(); @@ -394,7 +405,7 @@ void SharedObject::onRender() { } void SharedObject::onTimer() { - offscreenTextures.report(); + getTextureCache().report(); if (!_renderRequested) { return; } @@ -427,7 +438,7 @@ void SharedObject::updateTextureAndFence(const TextureAndFence& newTextureAndFen QMutexLocker locker(&_mutex); // If the most recent texture was unused, we can directly recycle it if (_latestTextureAndFence.first) { - offscreenTextures.releaseTexture(_latestTextureAndFence); + getTextureCache().releaseTexture(_latestTextureAndFence); _latestTextureAndFence = { 0, 0 }; } diff --git a/libraries/qml/src/qml/impl/TextureCache.cpp b/libraries/qml/src/qml/impl/TextureCache.cpp index c649a36594..7af8fa1ac9 100644 --- a/libraries/qml/src/qml/impl/TextureCache.cpp +++ b/libraries/qml/src/qml/impl/TextureCache.cpp @@ -11,10 +11,6 @@ using namespace hifi::qml::impl; -#if defined(Q_OS_ANDROID) -#define USE_GLES 1 -#endif - uint64_t uvec2ToUint64(const QSize& size) { uint64_t result = size.width(); result <<= 32; @@ -31,26 +27,23 @@ void TextureCache::acquireSize(const QSize& size) { void TextureCache::releaseSize(const QSize& size) { auto sizeKey = uvec2ToUint64(size); - ValueList texturesToDelete; { Lock lock(_mutex); assert(_textures.count(sizeKey)); auto& textureSet = _textures[sizeKey]; if (0 == --textureSet.clientCount) { - texturesToDelete.swap(textureSet.returnedTextures); + for (const auto& textureAndFence : textureSet.returnedTextures) { + destroy(textureAndFence); + } _textures.erase(sizeKey); } } - for (const auto& textureAndFence : texturesToDelete) { - destroy(textureAndFence); - } } uint32_t TextureCache::acquireTexture(const QSize& size) { Lock lock(_mutex); recycle(); - ++_activeTextureCount; auto sizeKey = uvec2ToUint64(size); assert(_textures.count(sizeKey)); @@ -83,7 +76,12 @@ void TextureCache::report() { } size_t TextureCache::getUsedTextureMemory() { - return _totalTextureUsage; + size_t toReturn; + { + Lock lock(_mutex); + toReturn = _totalTextureUsage; + } + return toReturn; } size_t TextureCache::getMemoryForSize(const QSize& size) { @@ -122,8 +120,6 @@ uint32_t TextureCache::createTexture(const QSize& size) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); #if !defined(USE_GLES) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); #endif diff --git a/libraries/qml/src/qml/impl/TextureCache.h b/libraries/qml/src/qml/impl/TextureCache.h index 572e1cadea..c146d0bdbf 100644 --- a/libraries/qml/src/qml/impl/TextureCache.h +++ b/libraries/qml/src/qml/impl/TextureCache.h @@ -41,7 +41,6 @@ public: ValueList returnedTextures; }; - void releaseSize(const QSize& size); void acquireSize(const QSize& size); uint32_t acquireTexture(const QSize& size); diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 7fece45b2f..319b6ad415 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -8,6 +8,9 @@ include_hifi_library_headers(audio) include_hifi_library_headers(networking) include_hifi_library_headers(octree) +# tell CMake to exclude qrc_fonts.cpp for policy CMP0071 +set_property(SOURCE qrc_fonts.cpp PROPERTY SKIP_AUTOMOC ON) + if (NOT ANDROID) target_nsight() endif () diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 015f5678c8..c526f16b75 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -385,7 +385,7 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte auto firstHBlurPipeline = getHBlurPipeline(); auto lastVBlurPipeline = getVBlurPipeline(); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("AmbientOcclusionEffect::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); _gpuTimer->begin(batch); @@ -518,7 +518,7 @@ void DebugAmbientOcclusion::run(const render::RenderContextPointer& renderContex auto debugPipeline = getDebugPipeline(); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("DebugAmbientOcclusion::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(sourceViewport); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 02ff234c01..90424b04b2 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -9,6 +9,7 @@ #include "AnimDebugDraw.h" +#include #include #include @@ -21,7 +22,6 @@ #include "animdebugdraw_vert.h" #include "animdebugdraw_frag.h" - class AnimDebugDrawData { public: diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index bdd8f19a5c..ba5036ad68 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "AntialiasingEffect.h" #include "StencilMaskPass.h" @@ -22,7 +23,11 @@ #include "DependencyManager.h" #include "ViewFrustum.h" #include "GeometryCache.h" +#include "FramebufferCache.h" +#define ANTIALIASING_USE_TAA 1 + +#if !ANTIALIASING_USE_TAA #include "fxaa_vert.h" #include "fxaa_frag.h" #include "fxaa_blend_frag.h" @@ -108,7 +113,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); @@ -165,3 +170,374 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); }); } +#else + +#include "taa_frag.h" +#include "fxaa_blend_frag.h" +#include "taa_blend_frag.h" + +const int AntialiasingPass_ParamsSlot = 0; +const int AntialiasingPass_FrameTransformSlot = 1; + +const int AntialiasingPass_HistoryMapSlot = 0; +const int AntialiasingPass_SourceMapSlot = 1; +const int AntialiasingPass_VelocityMapSlot = 2; +const int AntialiasingPass_DepthMapSlot = 3; + +const int AntialiasingPass_NextMapSlot = 4; + + +Antialiasing::Antialiasing() { + _antialiasingBuffers = std::make_shared(2U); +} + +Antialiasing::~Antialiasing() { + _antialiasingBuffers.reset(); + _antialiasingTextures[0].reset(); + _antialiasingTextures[1].reset(); +} + +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { + + if (!_antialiasingPipeline) { + + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = taa_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); + + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + PrepareStencil::testNoAA(*state); + + // Good to go add the brand new pipeline + _antialiasingPipeline = gpu::Pipeline::create(program, state); + } + + return _antialiasingPipeline; +} + +const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { + if (!_blendPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = fxaa_blend_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), AntialiasingPass_NextMapSlot)); + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testNoAA(*state); + + + // Good to go add the brand new pipeline + _blendPipeline = gpu::Pipeline::create(program, state); + _sharpenLoc = program->getUniforms().findLocation("sharpenIntensity"); + + } + return _blendPipeline; +} + +const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() { + if (!_debugBlendPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = taa_blend_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("nextMap"), AntialiasingPass_NextMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); + + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testNoAA(*state); + + + // Good to go add the brand new pipeline + _debugBlendPipeline = gpu::Pipeline::create(program, state); + } + return _debugBlendPipeline; +} + +void Antialiasing::configure(const Config& config) { + _sharpen = config.sharpen; + _params.edit().blend = config.blend; + _params.edit().covarianceGamma = config.covarianceGamma; + + _params.edit().setConstrainColor(config.constrainColor); + _params.edit().setFeedbackColor(config.feedbackColor); + + _params.edit().debugShowVelocityThreshold = config.debugShowVelocityThreshold; + + _params.edit().regionInfo.x = config.debugX; + _params.edit().regionInfo.z = config.debugFXAAX; + + _params.edit().setDebug(config.debug); + _params.edit().setShowDebugCursor(config.showCursorPixel); + _params.edit().setDebugCursor(config.debugCursorTexcoord); + _params.edit().setDebugOrbZoom(config.debugOrbZoom); + + _params.edit().setShowClosestFragment(config.showClosestFragment); +} + + +void Antialiasing::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + auto& deferredFrameTransform = inputs.get0(); + auto& sourceBuffer = inputs.get1(); + auto& linearDepthBuffer = inputs.get2(); + auto& velocityBuffer = inputs.get3(); + + int width = sourceBuffer->getWidth(); + int height = sourceBuffer->getHeight(); + + if (_antialiasingBuffers->get(0)) { + if (_antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) {// || (sourceBuffer && (_antialiasingBuffer->getRenderBuffer(1) != sourceBuffer->getRenderBuffer(0)))) { + _antialiasingBuffers->edit(0).reset(); + _antialiasingBuffers->edit(1).reset(); + _antialiasingTextures[0].reset(); + _antialiasingTextures[1].reset(); + } + } + + if (!_antialiasingBuffers->get(0)) { + // Link the antialiasing FBO to texture + for (int i = 0; i < 2; i++) { + auto& antiAliasingBuffer = _antialiasingBuffers->edit(i); + antiAliasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + _antialiasingTextures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + antiAliasingBuffer->setRenderBuffer(0, _antialiasingTextures[i]); + } + } + + gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + + // TAA step + getAntialiasingPipeline(); + batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_HistoryMapSlot, _antialiasingBuffers, 0); + batch.setResourceTexture(AntialiasingPass_SourceMapSlot, sourceBuffer->getRenderBuffer(0)); + batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, velocityBuffer->getVelocityTexture()); + // This is only used during debug + batch.setResourceTexture(AntialiasingPass_DepthMapSlot, linearDepthBuffer->getLinearDepthTexture()); + + batch.setUniformBuffer(AntialiasingPass_ParamsSlot, _params); + batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, deferredFrameTransform->getFrameTransformBuffer()); + + batch.setFramebufferSwapChain(_antialiasingBuffers, 1); + batch.setPipeline(getAntialiasingPipeline()); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + // Blend step + batch.setResourceTexture(AntialiasingPass_SourceMapSlot, nullptr); + + batch.setFramebuffer(sourceBuffer); + if (_params->isDebug()) { + batch.setPipeline(getDebugBlendPipeline()); + } else { + batch.setPipeline(getBlendPipeline()); + // Disable sharpen if FXAA + batch._glUniform1f(_sharpenLoc, _sharpen * _params.get().regionInfo.z); + } + batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_NextMapSlot, _antialiasingBuffers, 1); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.advance(_antialiasingBuffers); + + batch.setUniformBuffer(AntialiasingPass_ParamsSlot, nullptr); + batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, nullptr); + + batch.setResourceTexture(AntialiasingPass_DepthMapSlot, nullptr); + batch.setResourceTexture(AntialiasingPass_HistoryMapSlot, nullptr); + batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr); + batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr); + }); + + args->popViewFrustum(); +} + + +void JitterSampleConfig::setIndex(int current) { + _index = (current) % JitterSample::SEQUENCE_LENGTH; + emit dirty(); +} + +int JitterSampleConfig::cycleStopPauseRun() { + _state = (_state + 1) % 3; + switch (_state) { + case 0: { + return none(); + break; + } + case 1: { + return pause(); + break; + } + case 2: + default: { + return play(); + break; + } + } + return _state; +} + +int JitterSampleConfig::prev() { + setIndex(_index - 1); + return _index; +} + +int JitterSampleConfig::next() { + setIndex(_index + 1); + return _index; +} + +int JitterSampleConfig::none() { + _state = 0; + stop = true; + freeze = false; + setIndex(-1); + return _state; +} + +int JitterSampleConfig::pause() { + _state = 1; + stop = false; + freeze = true; + setIndex(0); + return _state; +} + + +int JitterSampleConfig::play() { + _state = 2; + stop = false; + freeze = false; + setIndex(0); + return _state; +} + +template +class Halton { +public: + + float eval(int index) const { + float f = 1.0f; + float r = 0.0f; + float invB = 1.0f / (float)B; + index++; // Indices start at 1, not 0 + + while (index > 0) { + f = f * invB; + r = r + f * (float)(index % B); + index = index / B; + + } + + return r; + } + +}; + + +JitterSample::SampleSequence::SampleSequence(){ + // Halton sequence (2,3) + Halton<2> genX; + Halton<3> genY; + + for (int i = 0; i < SEQUENCE_LENGTH; i++) { + offsets[i] = glm::vec2(genX.eval(i), genY.eval(i)); + offsets[i] -= vec2(0.5f); + } + offsets[SEQUENCE_LENGTH] = glm::vec2(0.0f); +} + +void JitterSample::configure(const Config& config) { + _freeze = config.freeze; + if (config.stop || _freeze) { + auto pausedIndex = config.getIndex(); + if (_sampleSequence.currentIndex != pausedIndex) { + _sampleSequence.currentIndex = pausedIndex; + } + } else { + if (_sampleSequence.currentIndex < 0) { + _sampleSequence.currentIndex = config.getIndex(); + } + } + _scale = config.scale; +} + +void JitterSample::run(const render::RenderContextPointer& renderContext) { + auto& current = _sampleSequence.currentIndex; + if (!_freeze) { + if (current >= 0) { + current = (current + 1) % SEQUENCE_LENGTH; + } else { + current = -1; + } + } + auto args = renderContext->args; + auto viewFrustum = args->getViewFrustum(); + + auto jit = _sampleSequence.offsets[(current < 0 ? SEQUENCE_LENGTH : current)]; + auto width = (float)args->_viewport.z; + auto height = (float)args->_viewport.w; + + auto jx = 2.0f * jit.x / width; + auto jy = 2.0f * jit.y / height; + + if (!args->isStereo()) { + auto projMat = viewFrustum.getProjection(); + + projMat[2][0] += jx; + projMat[2][1] += jy; + + viewFrustum.setProjection(projMat); + viewFrustum.calculate(); + args->pushViewFrustum(viewFrustum); + } else { + mat4 projMats[2]; + args->_context->getStereoProjections(projMats); + + jx *= 2.0f; + + for (int i = 0; i < 2; i++) { + auto& projMat = projMats[i]; + projMat[2][0] += jx; + projMat[2][1] += jy; + } + + args->_context->setStereoProjections(projMats); + } +} + + +#endif diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index cec2554a3b..03fdb9d9a4 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -15,7 +15,184 @@ #include #include "render/DrawTask.h" +#include "DeferredFrameTransform.h" +#include "VelocityBufferPass.h" + +class JitterSampleConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float scale MEMBER scale NOTIFY dirty) + Q_PROPERTY(bool freeze MEMBER freeze NOTIFY dirty) + Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty) + Q_PROPERTY(int index READ getIndex NOTIFY dirty) +public: + JitterSampleConfig() : render::Job::Config(true) {} + + float scale{ 0.5f }; + bool stop{ false }; + bool freeze{ false }; + + void setIndex(int current); + +public slots: + int cycleStopPauseRun(); + int prev(); + int next(); + int none(); + int pause(); + int play(); + + int getIndex() const { return _index; } + int getState() const { return _state; } +signals: + void dirty(); + +private: + int _state{ 0 }; + int _index{ 0 }; + +}; + + +class JitterSample { +public: + + enum { + SEQUENCE_LENGTH = 128 + }; + + using Config = JitterSampleConfig; + using JobModel = render::Job::Model; + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext); + +private: + + struct SampleSequence { + SampleSequence(); + + glm::vec2 offsets[SEQUENCE_LENGTH + 1]; + int sequenceLength{ SEQUENCE_LENGTH }; + int currentIndex{ 0 }; + }; + + SampleSequence _sampleSequence; + float _scale{ 1.0 }; + bool _freeze{ false }; +}; + + +class AntialiasingConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float blend MEMBER blend NOTIFY dirty) + Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty) + Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty) + + Q_PROPERTY(bool constrainColor MEMBER constrainColor NOTIFY dirty) + Q_PROPERTY(bool feedbackColor MEMBER feedbackColor NOTIFY dirty) + + Q_PROPERTY(bool debug MEMBER debug NOTIFY dirty) + Q_PROPERTY(float debugX MEMBER debugX NOTIFY dirty) + Q_PROPERTY(float debugFXAAX MEMBER debugFXAAX NOTIFY dirty) + Q_PROPERTY(float debugShowVelocityThreshold MEMBER debugShowVelocityThreshold NOTIFY dirty) + Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) + Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) + Q_PROPERTY(float debugOrbZoom MEMBER debugOrbZoom NOTIFY dirty) + + Q_PROPERTY(bool showClosestFragment MEMBER showClosestFragment NOTIFY dirty) + +public: + AntialiasingConfig() : render::Job::Config(true) {} + + float blend{ 0.05f }; + float sharpen{ 0.15f }; + + bool constrainColor{ true }; + float covarianceGamma{ 0.9f }; + bool feedbackColor{ false }; + + float debugX{ 0.0f }; + float debugFXAAX{ 1.0f }; + float debugShowVelocityThreshold{ 1.0f }; + glm::vec2 debugCursorTexcoord{ 0.5f, 0.5f }; + float debugOrbZoom{ 2.0f }; + + bool debug { false }; + bool showCursorPixel { false }; + bool showClosestFragment{ false }; + +signals: + void dirty(); +}; + +#define SET_BIT(bitfield, bitIndex, value) bitfield = ((bitfield) & ~(1 << (bitIndex))) | ((value) << (bitIndex)) +#define GET_BIT(bitfield, bitIndex) ((bitfield) & (1 << (bitIndex))) + +struct TAAParams { + float nope{ 0.0f }; + float blend{ 0.05f }; + float covarianceGamma{ 1.0f }; + float debugShowVelocityThreshold{ 1.0f }; + + glm::ivec4 flags{ 0 }; + glm::vec4 pixelInfo{ 0.5f, 0.5f, 2.0f, 0.0f }; + glm::vec4 regionInfo{ 0.0f, 0.0f, 1.0f, 0.0f }; + + void setConstrainColor(bool enabled) { SET_BIT(flags.y, 1, enabled); } + bool isConstrainColor() const { return (bool)GET_BIT(flags.y, 1); } + + void setFeedbackColor(bool enabled) { SET_BIT(flags.y, 4, enabled); } + bool isFeedbackColor() const { return (bool)GET_BIT(flags.y, 4); } + + void setDebug(bool enabled) { SET_BIT(flags.x, 0, enabled); } + bool isDebug() const { return (bool) GET_BIT(flags.x, 0); } + + void setShowDebugCursor(bool enabled) { SET_BIT(flags.x, 1, enabled); } + bool showDebugCursor() const { return (bool)GET_BIT(flags.x, 1); } + + void setDebugCursor(glm::vec2 debugCursor) { pixelInfo.x = debugCursor.x; pixelInfo.y = debugCursor.y; } + glm::vec2 getDebugCursor() const { return glm::vec2(pixelInfo.x, pixelInfo.y); } + + void setDebugOrbZoom(float orbZoom) { pixelInfo.z = orbZoom; } + float getDebugOrbZoom() const { return pixelInfo.z; } + + void setShowClosestFragment(bool enabled) { SET_BIT(flags.x, 3, enabled); } + +}; +using TAAParamsBuffer = gpu::StructBuffer; + +class Antialiasing { +public: + using Inputs = render::VaryingSet4 < DeferredFrameTransformPointer, gpu::FramebufferPointer, LinearDepthFramebufferPointer, VelocityFramebufferPointer > ; + using Config = AntialiasingConfig; + using JobModel = render::Job::ModelI; + + Antialiasing(); + ~Antialiasing(); + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + + const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getBlendPipeline(); + const gpu::PipelinePointer& getDebugBlendPipeline(); + +private: + + gpu::FramebufferSwapChainPointer _antialiasingBuffers; + gpu::TexturePointer _antialiasingTextures[2]; + + gpu::PipelinePointer _antialiasingPipeline; + gpu::PipelinePointer _blendPipeline; + gpu::PipelinePointer _debugBlendPipeline; + + TAAParamsBuffer _params; + float _sharpen{ 0.15f }; + int _sharpenLoc{ -1 }; +}; + + +/* class AntiAliasingConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled) @@ -27,27 +204,28 @@ class Antialiasing { public: using Config = AntiAliasingConfig; using JobModel = render::Job::ModelI; - + Antialiasing(); ~Antialiasing(); void configure(const Config& config) {} void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer); - - const gpu::PipelinePointer& getAntialiasingPipeline(RenderArgs* args); + + const gpu::PipelinePointer& getAntialiasingPipeline(); const gpu::PipelinePointer& getBlendPipeline(); - + private: - + // Uniforms for AA gpu::int32 _texcoordOffsetLoc; - + gpu::FramebufferPointer _antialiasingBuffer; - + gpu::TexturePointer _antialiasingTexture; - + gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; int _geometryId { 0 }; }; +*/ #endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 493c28d840..886795ec79 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -93,7 +93,7 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, PerformanceTimer perfTimer("skybox"); auto args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawBackgroundStage::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.enableSkybox(true); diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 89a83a651a..ddd63f012f 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -75,7 +75,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BloomThreshold::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(viewport); @@ -135,7 +135,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In const auto blur2FB = inputs.get3(); const glm::ivec4 viewport{ 0, 0, framebufferSize.x, framebufferSize.y }; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BloomApply::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(frameBuffer); @@ -180,7 +180,7 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp _pipeline = gpu::Pipeline::create(program, state); } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BloomDraw::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(frameBuffer); @@ -238,7 +238,7 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In _pipeline = gpu::Pipeline::create(program, state); } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DebugBloom::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(frameBuffer); diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 6806b41647..1596f7ba83 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -62,6 +62,7 @@ void CauterizedModel::createVisibleRenderItemSet() { Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); + _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); Transform transform; @@ -86,6 +87,7 @@ void CauterizedModel::createVisibleRenderItemSet() { for (int partIndex = 0; partIndex < numParts; partIndex++) { auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); + _modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName()); _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } @@ -214,6 +216,7 @@ void CauterizedModel::updateRenderItems() { bool isWireframe = self->isWireframe(); bool isVisible = self->isVisible(); + bool canCastShadow = self->canCastShadow(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); bool enableCauterization = self->getEnableCauterization(); @@ -231,7 +234,7 @@ void CauterizedModel::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, - isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, canCastShadow, enableCauterization](CauterizedMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions, cauterizedMeshState.clusterDualQuaternions); @@ -273,7 +276,7 @@ void CauterizedModel::updateRenderItems() { data.updateTransformForCauterizedMesh(renderTransform); data.setEnableCauterization(enableCauterization); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, render::ItemKey::TAG_BITS_ALL); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, render::ItemKey::TAG_BITS_ALL); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); }); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 24cffe2fb8..c17044be6d 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -53,7 +53,8 @@ enum TextureSlot { DiffusedCurvature, Scattering, AmbientOcclusion, - AmbientOcclusionBlurred + AmbientOcclusionBlurred, + Velocity, }; enum ParamSlot { @@ -254,6 +255,12 @@ static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{ " }" }; +static const std::string DEFAULT_VELOCITY_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(vec2(texture(velocityMap, uv).xy), 0.0, 1.0);" + " }" +}; + static const std::string DEFAULT_CUSTOM_SHADER { "vec4 getFragmentColor() {" " return vec4(1.0, 0.0, 0.0, 1.0);" @@ -341,6 +348,8 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust return DEFAULT_AMBIENT_OCCLUSION_SHADER; case AmbientOcclusionBlurredMode: return DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER; + case VelocityMode: + return DEFAULT_VELOCITY_SHADER; case CustomMode: return getFileContent(customFile, DEFAULT_CUSTOM_SHADER); default: @@ -402,6 +411,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str slotBindings.insert(gpu::Shader::Binding("diffusedCurvatureMap", DiffusedCurvature)); slotBindings.insert(gpu::Shader::Binding("scatteringMap", Scattering)); slotBindings.insert(gpu::Shader::Binding("occlusionBlurredMap", AmbientOcclusionBlurred)); + slotBindings.insert(gpu::Shader::Binding("velocityMap", Velocity)); gpu::Shader::makeProgram(*program, slotBindings); auto pipeline = gpu::Pipeline::create(program, std::make_shared()); @@ -439,9 +449,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I auto& linearDepthTarget = inputs.get1(); auto& surfaceGeometryFramebuffer = inputs.get2(); auto& ambientOcclusionFramebuffer = inputs.get3(); - auto& frameTransform = inputs.get4(); + auto& velocityFramebuffer = inputs.get4(); + auto& frameTransform = inputs.get5(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DebugDeferredBuffer::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); @@ -468,6 +479,9 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Depth, deferredFramebuffer->getPrimaryDepthTexture()); batch.setResourceTexture(Lighting, deferredFramebuffer->getLightingTexture()); } + if (velocityFramebuffer) { + batch.setResourceTexture(Velocity, velocityFramebuffer->getVelocityTexture()); + } auto lightStage = renderContext->_scene->getStage(); assert(lightStage); @@ -515,5 +529,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(AmbientOcclusion, nullptr); batch.setResourceTexture(AmbientOcclusionBlurred, nullptr); + batch.setResourceTexture(Velocity, nullptr); + }); } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 8227c4f7a3..5384a77b76 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -19,6 +19,7 @@ #include "DeferredFramebuffer.h" #include "SurfaceGeometryPass.h" #include "AmbientOcclusionEffect.h" +#include "VelocityBufferPass.h" class DebugDeferredBufferConfig : public render::Job::Config { Q_OBJECT @@ -38,7 +39,7 @@ signals: class DebugDeferredBuffer { public: - using Inputs = render::VaryingSet5; + using Inputs = render::VaryingSet6; using Config = DebugDeferredBufferConfig; using JobModel = render::Job::ModelI; @@ -81,6 +82,7 @@ protected: ScatteringDebugMode, AmbientOcclusionMode, AmbientOcclusionBlurredMode, + VelocityMode, CustomMode, // Needs to stay last NumModes, diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 3cbed3fcef..f7ab0957cc 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -118,7 +118,7 @@ DeferredFragment unpackDeferredFragmentNoPositionNoAmbient(vec2 texcoord) { <@include DeferredTransform.slh@> <$declareDeferredFrameTransform()$> -vec4 unpackDeferredPosition(DeferredFrameTransform deferredTransform, float depthValue, vec2 texcoord) { +vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { int side = 0; if (isStereo()) { if (texcoord.x > 0.5) { @@ -127,9 +127,14 @@ vec4 unpackDeferredPosition(DeferredFrameTransform deferredTransform, float dept } texcoord.x *= 2.0; } - float Zeye = evalZeyeFromZdb(depthValue); - return vec4(evalEyePositionFromZeye(side, Zeye, texcoord), 1.0); + return vec4(evalEyePositionFromZdb(side, depthValue, texcoord), 1.0); +} + +// This method to unpack position is fastesst +vec4 unpackDeferredPositionFromZdb(vec2 texcoord) { + float Zdb = texture(depthMap, texcoord).x; + return unpackDeferredPosition(Zdb, texcoord); } vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { @@ -152,7 +157,7 @@ DeferredFragment unpackDeferredFragment(DeferredFrameTransform deferredTransform DeferredFragment frag = unpackDeferredFragmentNoPosition(texcoord); frag.depthVal = depthValue; - frag.position = unpackDeferredPosition(deferredTransform, frag.depthVal, texcoord); + frag.position = unpackDeferredPosition(frag.depthVal, texcoord); return frag; } diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp index baf523312c..d1c51bf46f 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.cpp +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -38,12 +38,13 @@ void DeferredFrameTransform::update(RenderArgs* args) { args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.projectionMono); - // Running in stero ? + // Running in stereo ? bool isStereo = args->isStereo(); if (!isStereo) { frameTransformBuffer.projection[0] = frameTransformBuffer.projectionMono; frameTransformBuffer.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / args->_viewport.z, 1.0f / args->_viewport.w, 0.0f, 0.0f); + frameTransformBuffer.invProjection[0] = glm::inverse(frameTransformBuffer.projection[0]); } else { mat4 projMats[2]; @@ -55,6 +56,7 @@ void DeferredFrameTransform::update(RenderArgs* args) { // Compose the mono Eye space to Stereo clip space Projection Matrix auto sideViewMat = projMats[i] * eyeViews[i]; frameTransformBuffer.projection[i] = sideViewMat; + frameTransformBuffer.invProjection[i] = glm::inverse(sideViewMat); } frameTransformBuffer.stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index 93e194f052..8c2f0a7321 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -45,6 +45,8 @@ protected: glm::vec4 stereoInfo{ 0.0 }; // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space glm::mat4 projection[2]; + // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 invProjection[2]; // THe mono projection for sure glm::mat4 projectionMono; // Inv View matrix from eye space (mono) to world space diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index e2c0073d2f..956b6c4a58 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -431,7 +431,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input outputs.edit0() = _deferredFramebuffer; outputs.edit1() = _deferredFramebuffer->getLightingFramebuffer(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("PrepareDeferred::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -492,7 +492,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, deferredFramebuffer->getPrimaryDepthTexture()); // FIXME: Different render modes should have different tasks - if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled()) { + if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled() && ambientOcclusionFramebuffer) { batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, ambientOcclusionFramebuffer->getOcclusionTexture()); } else { // need to assign the white texture if ao is off @@ -532,20 +532,30 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, } } - auto& program = deferredLightingEffect->_directionalSkyboxLight; + auto program = deferredLightingEffect->_directionalSkyboxLight; LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; auto keyLight = lightAndShadow.first; - graphics::LightPointer keyAmbientLight; + graphics::LightPointer ambientLight; if (lightStage && lightStage->_currentFrame._ambientLights.size()) { - keyAmbientLight = lightStage->getLight(lightStage->_currentFrame._ambientLights.front()); + ambientLight = lightStage->getLight(lightStage->_currentFrame._ambientLights.front()); } - bool hasAmbientMap = (keyAmbientLight != nullptr); + bool hasAmbientMap = (ambientLight != nullptr); // Setup the global directional pass pipeline { - if (deferredLightingEffect->_shadowMapEnabled) { + // Check if keylight casts shadows + bool keyLightCastShadows { false }; + + if (lightStage && lightStage->_currentFrame._sunLights.size()) { + graphics::LightPointer keyLight = lightStage->getLight(lightStage->_currentFrame._sunLights.front()); + if (keyLight) { + keyLightCastShadows = keyLight->getCastShadows(); + } + } + + if (deferredLightingEffect->_shadowMapEnabled && keyLightCastShadows) { // If the keylight has an ambient Map then use the Skybox version of the pass // otherwise use the ambient sphere version diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 1b776e6409..ce7ecacbbe 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -61,7 +61,7 @@ public: private: DeferredLightingEffect() = default; - bool _shadowMapEnabled{ false }; + bool _shadowMapEnabled{ true }; // note that this value is overwritten in the ::configure method bool _ambientOcclusionEnabled{ false }; graphics::MeshPointer _pointLightMesh; diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index e7771821dc..6b0e1cd642 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -16,6 +16,9 @@ struct CameraCorrection { mat4 _correction; mat4 _correctionInverse; + + mat4 _prevView; + mat4 _prevViewInverse; }; uniform cameraCorrectionBuffer { @@ -28,6 +31,7 @@ struct DeferredFrameTransform { vec4 _depthInfo; vec4 _stereoInfo; mat4 _projection[2]; + mat4 _invProjection[2]; mat4 _projectionMono; mat4 _viewInverse; mat4 _view; @@ -37,13 +41,6 @@ uniform deferredFrameTransformBuffer { DeferredFrameTransform frameTransform; }; -DeferredFrameTransform getDeferredFrameTransform() { - DeferredFrameTransform result = frameTransform; - result._view = result._view * cameraCorrection._correctionInverse; - result._viewInverse = result._viewInverse * cameraCorrection._correction; - return result; -} - vec2 getWidthHeight(int resolutionLevel) { return vec2(ivec2(frameTransform._pixelInfo.zw) >> resolutionLevel); } @@ -79,11 +76,26 @@ float getPosLinearDepthFar() { } mat4 getViewInverse() { - return frameTransform._viewInverse * cameraCorrection._correction; + return frameTransform._viewInverse * cameraCorrection._correctionInverse; } mat4 getView() { - return frameTransform._view * cameraCorrection._correctionInverse; + return cameraCorrection._correction * frameTransform._view; +} + +mat4 getPreviousView() { + return cameraCorrection._prevView; +} + +mat4 getPreviousViewInverse() { + return cameraCorrection._prevViewInverse; +} + +DeferredFrameTransform getDeferredFrameTransform() { + DeferredFrameTransform result = frameTransform; + result._view = getView(); + result._viewInverse = getViewInverse(); + return result; } bool isStereo() { @@ -110,17 +122,25 @@ float evalZeyeFromZdb(float depth) { return frameTransform._depthInfo.x / (depth * frameTransform._depthInfo.y + frameTransform._depthInfo.z); } +float evalZdbFromZeye(float Zeye) { + return (frameTransform._depthInfo.x - Zeye * frameTransform._depthInfo.z) / (Zeye * frameTransform._depthInfo.y); +} + vec3 evalEyeNormal(vec3 C) { - //return normalize(cross(dFdy(C), dFdx(C))); return normalize(cross(dFdx(C), dFdy(C))); } -vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { +vec3 evalEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { // compute the view space position using the depth - // basically manually pick the proj matrix components to do the inverse - float Xe = (-Zeye * (texcoord.x * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][0] - frameTransform._projection[side][3][0]) / frameTransform._projection[side][0][0]; - float Ye = (-Zeye * (texcoord.y * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][1] - frameTransform._projection[side][3][1]) / frameTransform._projection[side][1][1]; - return vec3(Xe, Ye, Zeye); + vec3 clipPos; + clipPos.xyz = vec3(texcoord.xy, Zdb) * 2.0 - 1.0; + vec4 eyePos = frameTransform._invProjection[side] * vec4(clipPos.xyz, 1.0); + return eyePos.xyz / eyePos.w; +} + +vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { + float Zdb = evalZdbFromZeye(Zeye); + return evalEyePositionFromZdb(side, Zdb, texcoord); } ivec2 getPixelPosTexcoordPosAndSide(in vec2 glFragCoord, out ivec2 pixelPos, out vec2 texcoordPos, out ivec4 stereoSide) { diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index c0db1597ca..78569b2837 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -155,7 +155,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu auto sourceFramebufferSize = glm::ivec2(inputBuffer->getDimensions()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHaze::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(outputBuffer); diff --git a/libraries/render-utils/src/FadeEffectJobs.cpp b/libraries/render-utils/src/FadeEffectJobs.cpp index da3f8dddc0..1e2ea2bc15 100644 --- a/libraries/render-utils/src/FadeEffectJobs.cpp +++ b/libraries/render-utils/src/FadeEffectJobs.cpp @@ -39,43 +39,52 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F auto scene = renderContext->_scene; if (_isEditEnabled) { - float minIsectDistance = std::numeric_limits::max(); - auto& itemBounds = inputs.get0(); - auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance); - render::Transaction transaction; - bool hasTransaction{ false }; + static const std::string selectionName("TransitionEdit"); + auto scene = renderContext->_scene; + if (!scene->isSelectionEmpty(selectionName)) { + auto selection = scene->getSelection(selectionName); + auto editedItem = selection.getItems().front(); + render::Transaction transaction; + bool hasTransaction{ false }; - if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) { - // Remove transition from previously edited item as we've changed edited item - hasTransaction = true; + if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) { + // Remove transition from previously edited item as we've changed edited item + hasTransaction = true; + transaction.removeTransitionFromItem(_editedItem); + } + _editedItem = editedItem; + + if (render::Item::isValidID(_editedItem)) { + static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = { + render::Transition::ELEMENT_ENTER_DOMAIN, + render::Transition::BUBBLE_ISECT_OWNER, + render::Transition::BUBBLE_ISECT_TRESPASSER, + render::Transition::USER_ENTER_DOMAIN, + render::Transition::AVATAR_CHANGE + }; + + auto transitionType = categoryToTransition[inputs.get1()]; + + transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) { + if (transition == nullptr || transition->isFinished || transition->eventType != transitionType) { + // Relaunch transition + render::Transaction transaction; + transaction.addTransitionToItem(id, transitionType); + scene->enqueueTransaction(transaction); + } + }); + hasTransaction = true; + } + + if (hasTransaction) { + scene->enqueueTransaction(transaction); + } + } else if (render::Item::isValidID(_editedItem)) { + // Remove transition from previously edited item as we've disabled fade edition + render::Transaction transaction; transaction.removeTransitionFromItem(_editedItem); - } - _editedItem = editedItem; - - if (render::Item::isValidID(_editedItem)) { - static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = { - render::Transition::ELEMENT_ENTER_DOMAIN, - render::Transition::BUBBLE_ISECT_OWNER, - render::Transition::BUBBLE_ISECT_TRESPASSER, - render::Transition::USER_ENTER_DOMAIN, - render::Transition::AVATAR_CHANGE - }; - - auto transitionType = categoryToTransition[inputs.get1()]; - - transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) { - if (transition == nullptr || transition->isFinished || transition->eventType!=transitionType) { - // Relaunch transition - render::Transaction transaction; - transaction.addTransitionToItem(id, transitionType); - scene->enqueueTransaction(transaction); - } - }); - hasTransaction = true; - } - - if (hasTransaction) { scene->enqueueTransaction(transaction); + _editedItem = render::Item::INVALID_ITEM_ID; } } else if (render::Item::isValidID(_editedItem)) { @@ -87,28 +96,6 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F } } -render::ItemID FadeEditJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const { - const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition(); - const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection(); - BoxFace face; - glm::vec3 normal; - float isectDistance; - render::ItemID nearestItem = render::Item::INVALID_ITEM_ID; - const float minDistance = 1.f; - const float maxDistance = 50.f; - - for (const auto& itemBound : inputs) { - if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) { - auto& item = renderContext->_scene->getItem(itemBound.id); - if (item.getKey().isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistancemanualFade && (state.threshold != jobConfig->threshold)) { + if (isFirstItem && (state.threshold != jobConfig->threshold)) { jobConfig->setProperty("threshold", state.threshold); isFirstItem = false; } diff --git a/libraries/render-utils/src/FadeEffectJobs.h b/libraries/render-utils/src/FadeEffectJobs.h index 783f026bcd..449995dba5 100644 --- a/libraries/render-utils/src/FadeEffectJobs.h +++ b/libraries/render-utils/src/FadeEffectJobs.h @@ -160,8 +160,8 @@ public: float manualThreshold{ 0.f }; bool manualFade{ false }; - Q_INVOKABLE void save() const; - Q_INVOKABLE void load(); + Q_INVOKABLE void save(const QString& filePath) const; + Q_INVOKABLE void load(const QString& filePath); static QString eventNames[FADE_CATEGORY_COUNT]; @@ -190,7 +190,6 @@ private: bool _isEditEnabled{ false }; render::ItemID _editedItem{ render::Item::INVALID_ITEM_ID }; - render::ItemID findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const; }; class FadeJob { diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 7455da13b6..3fd18a0eaf 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -75,32 +75,6 @@ static std::array MAPPING GeometryCache::Cylinder, } }; -/**jsdoc -*

{@link Entities} and {@link Overlays} may have the following geometrical shapes:

-* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -*
ValueDescription
LineA 1D line oriented in 3 dimensions.
TriangleA triangular prism.
QuadA 2D square oriented in 3 dimensions.
HexagonA hexagonal prism.
OctagonAn octagonal prism.
CircleA 2D circle oriented in 3 dimensions.
CubeA cube.
SphereA sphere.
TetrahedronA tetrahedron.
OctahedronAn octahedron.
DodecahedronA dodecahedron.
IcosahedronAn icosahedron.
TorusA torus. Not implemented.
ConeA cone.
CylinderA cylinder.
-* @typedef {string} Shape -*/ static const std::array GEOCACHE_SHAPE_STRINGS{ { "Line", "Triangle", @@ -126,6 +100,8 @@ static const int VERTICES_PER_TRIANGLE = 3; static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element TEXCOORD0_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV }; +static const gpu::Element TANGENT_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; static const gpu::Element TEXCOORD4_ELEMENT { gpu::VEC4, gpu::FLOAT, gpu::XYZW }; @@ -133,8 +109,10 @@ static gpu::Stream::FormatPointer SOLID_STREAM_FORMAT; static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT; static gpu::Stream::FormatPointer INSTANCED_SOLID_FADE_STREAM_FORMAT; -static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals -static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); +static const uint SHAPE_VERTEX_STRIDE = sizeof(GeometryCache::ShapeVertex); // position, normal, texcoords, tangent +static const uint SHAPE_NORMALS_OFFSET = offsetof(GeometryCache::ShapeVertex, normal); +static const uint SHAPE_TEXCOORD0_OFFSET = offsetof(GeometryCache::ShapeVertex, uv); +static const uint SHAPE_TANGENT_OFFSET = offsetof(GeometryCache::ShapeVertex, tangent); void GeometryCache::computeSimpleHullPointListForShape(const int entityShape, const glm::vec3 &entityExtents, QVector &outPointList) { @@ -193,16 +171,20 @@ std::vector polygon() { return result; } -void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices) { +void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const std::vector& vertices) { gpu::Buffer::Size offset = vertexBuffer->getSize(); vertexBuffer->append(vertices); - gpu::Buffer::Size viewSize = vertices.size() * sizeof(glm::vec3); + gpu::Buffer::Size viewSize = vertices.size() * sizeof(ShapeVertex); _positionView = gpu::BufferView(vertexBuffer, offset, viewSize, SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); _normalView = gpu::BufferView(vertexBuffer, offset + SHAPE_NORMALS_OFFSET, viewSize, SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); + _texCoordView = gpu::BufferView(vertexBuffer, offset + SHAPE_TEXCOORD0_OFFSET, + viewSize, SHAPE_VERTEX_STRIDE, TEXCOORD0_ELEMENT); + _tangentView = gpu::BufferView(vertexBuffer, offset + SHAPE_TANGENT_OFFSET, + viewSize, SHAPE_VERTEX_STRIDE, TANGENT_ELEMENT); } void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices) { @@ -228,6 +210,8 @@ void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, con void GeometryCache::ShapeData::setupBatch(gpu::Batch& batch) const { batch.setInputBuffer(gpu::Stream::POSITION, _positionView); batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); + batch.setInputBuffer(gpu::Stream::TEXCOORD, _texCoordView); + batch.setInputBuffer(gpu::Stream::TANGENT, _tangentView); batch.setIndexBuffer(_indicesView); } @@ -294,14 +278,14 @@ static IndexPair indexToken(geometry::Index a, geometry::Index b) { template void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { using namespace geometry; - VertexVector vertices; + std::vector vertices; IndexVector solidIndices, wireIndices; IndexPairs wireSeenIndices; size_t faceCount = shape.faces.size(); size_t faceIndexCount = triangulatedFaceIndexCount(); - vertices.reserve(N * faceCount * 2); + vertices.reserve(N * faceCount); solidIndices.reserve(faceIndexCount * faceCount); Index baseVertex = 0; @@ -310,11 +294,35 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { using namespace geometry; - VertexVector vertices; - vertices.reserve(shape.vertices.size() * 2); + std::vector vertices; + vertices.reserve(shape.vertices.size()); for (const auto& vertex : shape.vertices) { - vertices.push_back(vertex); - vertices.push_back(vertex); + // We'll fill in the correct tangents later, once we correct the UVs + vertices.emplace_back(vertex, vertex, calculateSphereTexCoord(vertex), vec3(0.0f)); + } + + // We need to fix up the sphere's UVs because it's actually a tesselated icosahedron. See http://mft-dev.dk/uv-mapping-sphere/ + size_t faceCount = shape.faces.size(); + for (size_t f = 0; f < faceCount; f++) { + // Fix zipper + { + float& u1 = vertices[shape.faces[f][0]].uv.x; + float& u2 = vertices[shape.faces[f][1]].uv.x; + float& u3 = vertices[shape.faces[f][2]].uv.x; + + if (glm::isnan(u1)) { + u1 = (u2 + u3) / 2.0f; + } + if (glm::isnan(u2)) { + u2 = (u1 + u3) / 2.0f; + } + if (glm::isnan(u3)) { + u3 = (u1 + u2) / 2.0f; + } + + const float U_THRESHOLD = 0.25f; + float max = glm::max(u1, glm::max(u2, u3)); + float min = glm::min(u1, glm::min(u2, u3)); + + if (max - min > U_THRESHOLD) { + if (u1 < U_THRESHOLD) { + u1 += 1.0f; + } + if (u2 < U_THRESHOLD) { + u2 += 1.0f; + } + if (u3 < U_THRESHOLD) { + u3 += 1.0f; + } + } + } + + // Fix swirling at poles + for (Index i = 0; i < N; i++) { + Index originalIndex = shape.faces[f][i]; + if (shape.vertices[originalIndex].y == 1.0f || shape.vertices[originalIndex].y == -1.0f) { + float uSum = 0.0f; + for (Index i2 = 1; i2 <= N - 1; i2++) { + float u = vertices[shape.faces[f][(i + i2) % N]].uv.x; + uSum += u; + } + uSum /= (float)(N - 1); + vertices[originalIndex].uv.x = uSum; + break; + } + } + + // Fill in tangents + for (Index i = 0; i < N; i++) { + vec3 tangent = calculateSphereTangent(vertices[shape.faces[f][i]].uv.x); + vertices[shape.faces[f][i]].tangent = tangent; + } } IndexVector solidIndices, wireIndices; IndexPairs wireSeenIndices; - size_t faceCount = shape.faces.size(); size_t faceIndexCount = triangulatedFaceIndexCount(); solidIndices.reserve(faceIndexCount * faceCount); @@ -391,25 +473,22 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid template void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer, bool isConical = false) { using namespace geometry; - VertexVector vertices; + std::vector vertices; IndexVector solidIndices, wireIndices; // Top (if not conical) and bottom faces std::vector shape = polygon(); if (isConical) { for (uint32_t i = 0; i < N; i++) { - vertices.push_back(vec3(0.0f, 0.5f, 0.0f)); - vertices.push_back(vec3(0.0f, 1.0f, 0.0f)); + vertices.emplace_back(vec3(0.0f, 0.5f, 0.0f), vec3(0.0f, 1.0f, 0.0f), vec2((float)i / (float)N, 1.0f), vec3(0.0f)); } } else { for (const vec3& v : shape) { - vertices.push_back(vec3(v.x, 0.5f, v.z)); - vertices.push_back(vec3(0.0f, 1.0f, 0.0f)); + vertices.emplace_back(vec3(v.x, 0.5f, v.z), vec3(0.0f, 1.0f, 0.0f), vec2(v.x, v.z) + vec2(0.5f), vec3(1.0f, 0.0f, 0.0f)); } } for (const vec3& v : shape) { - vertices.push_back(vec3(v.x, -0.5f, v.z)); - vertices.push_back(vec3(0.0f, -1.0f, 0.0f)); + vertices.emplace_back(vec3(v.x, -0.5f, v.z), vec3(0.0f, -1.0f, 0.0f), vec2(-v.x, v.z) + vec2(0.5f), vec3(-1.0f, 0.0f, 0.0f)); } Index baseVertex = 0; for (uint32_t i = 2; i < N; i++) { @@ -438,15 +517,16 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver vec3 topRight = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(right.x, 0.5f, right.z)); vec3 bottomLeft = vec3(left.x, -0.5f, left.z); vec3 bottomRight = vec3(right.x, -0.5f, right.z); + vec3 tangent = glm::normalize(bottomLeft - bottomRight); - vertices.push_back(topLeft); - vertices.push_back(normal); - vertices.push_back(bottomLeft); - vertices.push_back(normal); - vertices.push_back(topRight); - vertices.push_back(normal); - vertices.push_back(bottomRight); - vertices.push_back(normal); + // Our tex coords go in the opposite direction as our vertices + float u = 1.0f - (float)i / (float)N; + float u2 = 1.0f - (float)(i + 1) / (float)N; + + vertices.emplace_back(topLeft, normal, vec2(u, 0.0f), tangent); + vertices.emplace_back(bottomLeft, normal, vec2(u, 1.0f), tangent); + vertices.emplace_back(topRight, normal, vec2(u2, 0.0f), tangent); + vertices.emplace_back(bottomRight, normal, vec2(u2, 1.0f), tangent); solidIndices.push_back(baseVertex + 0); solidIndices.push_back(baseVertex + 2); @@ -465,41 +545,6 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); } -void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { - // Draw a circle with radius 1/4th the size of the bounding box - using namespace geometry; - - VertexVector vertices; - IndexVector solidIndices, wireIndices; - const int NUM_CIRCLE_VERTICES = 64; - - std::vector shape = polygon(); - for (const vec3& v : shape) { - vertices.push_back(vec3(v.x, 0.0f, v.z)); - vertices.push_back(vec3(0.0f, 0.0f, 0.0f)); - } - - Index baseVertex = 0; - for (uint32_t i = 2; i < NUM_CIRCLE_VERTICES; i++) { - solidIndices.push_back(baseVertex + 0); - solidIndices.push_back(baseVertex + i); - solidIndices.push_back(baseVertex + i - 1); - solidIndices.push_back(baseVertex + NUM_CIRCLE_VERTICES); - solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES - 1); - solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES); - } - - for (uint32_t i = 1; i <= NUM_CIRCLE_VERTICES; i++) { - wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES)); - wireIndices.push_back(baseVertex + i - 1); - wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES) + NUM_CIRCLE_VERTICES); - wireIndices.push_back(baseVertex + (i - 1) + NUM_CIRCLE_VERTICES); - } - - shapeData.setupVertices(vertexBuffer, vertices); - shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); -} - // FIXME solids need per-face vertices, but smooth shaded // components do not. Find a way to support using draw elements // or draw arrays as appropriate @@ -532,9 +577,9 @@ void GeometryCache::buildShapes() { // Line { ShapeData& shapeData = _shapes[Line]; - shapeData.setupVertices(_shapeVertices, VertexVector { - vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f), - vec3(0.5f, 0.0f, 0.0f), vec3(0.5f, 0.0f, 0.0f) + shapeData.setupVertices(_shapeVertices, std::vector { + ShapeVertex(vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f), vec2(0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f)), + ShapeVertex(vec3(0.5f, 0.0f, 0.0f), vec3(0.5f, 0.0f, 0.0f), vec2(0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f)) }); IndexVector wireIndices; // Only two indices @@ -598,6 +643,8 @@ gpu::Stream::FormatPointer& getSolidStreamFormat() { SOLID_STREAM_FORMAT = std::make_shared(); // 1 for everyone SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT); SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT); + SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD0, gpu::Stream::TEXCOORD0, TEXCOORD0_ELEMENT); + SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::TANGENT, gpu::Stream::TANGENT, TANGENT_ELEMENT); } return SOLID_STREAM_FORMAT; } @@ -607,6 +654,8 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat() { INSTANCED_SOLID_STREAM_FORMAT = std::make_shared(); // 1 for everyone INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT); INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT); + INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD0, gpu::Stream::TEXCOORD0, TEXCOORD0_ELEMENT); + INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::TANGENT, gpu::Stream::TANGENT, TANGENT_ELEMENT); INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE); } return INSTANCED_SOLID_STREAM_FORMAT; @@ -617,6 +666,8 @@ gpu::Stream::FormatPointer& getInstancedSolidFadeStreamFormat() { INSTANCED_SOLID_FADE_STREAM_FORMAT = std::make_shared(); // 1 for everyone INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT); INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD0, gpu::Stream::TEXCOORD0, TEXCOORD0_ELEMENT); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TANGENT, gpu::Stream::TANGENT, TANGENT_ELEMENT); INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE); INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD2, gpu::Stream::TEXCOORD2, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE); INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD3, gpu::Stream::TEXCOORD3, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE); @@ -919,6 +970,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET); + // TODO: circle3D overlays use this to define their vertices, so they need tex coords details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); details.stream->addBuffer(details.verticesBuffer, 0, details.streamFormat->getChannels().at(0)._stride); @@ -2136,7 +2188,7 @@ static void buildWebShader(const gpu::ShaderPointer& vertShader, const gpu::Shad gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShapeNoAA(*state); + PrepareStencil::testMaskDrawShape(*state); pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); } @@ -2148,11 +2200,11 @@ void GeometryCache::bindWebBrowserProgram(gpu::Batch& batch, bool transparent) { gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent) { static std::once_flag once; std::call_once(once, [&]() { - buildWebShader(simple_vert::getShader(), simple_opaque_web_browser_frag::getShader(), false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipelineNoAA); - buildWebShader(simple_vert::getShader(), simple_transparent_web_browser_frag::getShader(), true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipelineNoAA); + buildWebShader(simple_vert::getShader(), simple_opaque_web_browser_frag::getShader(), false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline); + buildWebShader(simple_vert::getShader(), simple_transparent_web_browser_frag::getShader(), true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline); }); - return transparent ? _simpleTransparentWebBrowserPipelineNoAA : _simpleOpaqueWebBrowserPipelineNoAA; + return transparent ? _simpleTransparentWebBrowserPipeline : _simpleOpaqueWebBrowserPipeline; } void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool isAntiAliased) { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 998043b80e..e0ba99b09e 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -354,13 +354,24 @@ public: /// Set a batch to the simple pipeline, returning the previous pipeline void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false); + struct ShapeVertex { + ShapeVertex(const vec3& pos, const vec3& normal, const vec2& uv, const vec3& tangent) : pos(pos), normal(normal), uv(uv), tangent(tangent) {} + + vec3 pos; + vec3 normal; + vec2 uv; + vec3 tangent; + }; + struct ShapeData { gpu::BufferView _positionView; gpu::BufferView _normalView; + gpu::BufferView _texCoordView; + gpu::BufferView _tangentView; gpu::BufferView _indicesView; gpu::BufferView _wireIndicesView; - void setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices); + void setupVertices(gpu::BufferPointer& vertexBuffer, const std::vector& vertices); void setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices); void setupBatch(gpu::Batch& batch) const; void draw(gpu::Batch& batch) const; @@ -475,9 +486,9 @@ private: static QHash _simplePrograms; gpu::ShaderPointer _simpleOpaqueWebBrowserShader; - gpu::PipelinePointer _simpleOpaqueWebBrowserPipelineNoAA; + gpu::PipelinePointer _simpleOpaqueWebBrowserPipeline; gpu::ShaderPointer _simpleTransparentWebBrowserShader; - gpu::PipelinePointer _simpleTransparentWebBrowserPipelineNoAA; + gpu::PipelinePointer _simpleTransparentWebBrowserPipeline; static render::ShapePipelinePointer getShapePipeline(bool textured = false, bool transparent = false, bool culled = true, bool unlit = false, bool depthBias = false); diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 0bf8e7fa71..d151da766b 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -161,7 +161,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c // Clear the framebuffer without stereo // Needs to be distinct from the other batch because using the clear call // while stereo is enabled triggers a warning - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlightMask::run::begin", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(ressources->getDepthFramebuffer()); batch.clearDepthStencilFramebuffer(1.0f, 0); @@ -174,7 +174,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c render::ItemBounds itemBounds; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlightMask::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); @@ -212,7 +212,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c _boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlightMask::run::end", args->_context, [&](gpu::Batch& batch) { // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); @@ -284,7 +284,7 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const shaderParameters._size.y = size; } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(destinationFrameBuffer); @@ -357,7 +357,7 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DebugHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); batch.setFramebuffer(highlightRessources->getColorFramebuffer()); diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index b3662385b0..26e809e4ba 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -48,6 +48,74 @@ TexMapArray getTexMapArray() { <@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@> +<@include gpu/TextureTable.slh@> + +#ifdef GPU_TEXTURE_TABLE_BINDLESS + +TextureTable(0, matTex); + + +<@if withAlbedo@> +#define albedoMap 0 +vec4 fetchAlbedoMap(vec2 uv) { + return tableTexValue(matTex, albedoMap, uv); +} +<@endif@> + +<@if withRoughness@> +#define roughnessMap 4 +float fetchRoughnessMap(vec2 uv) { + return tableTexValue(matTex, roughnessMap, uv).r; +} +<@endif@> + +<@if withNormal@> +#define normalMap 1 +vec3 fetchNormalMap(vec2 uv) { + return tableTexValue(matTex, normalMap, uv).xyz; +} +<@endif@> + +<@if withMetallic@> +#define metallicMap 2 +float fetchMetallicMap(vec2 uv) { + return tableTexValue(matTex, metallicMap, uv).r; +} +<@endif@> + +<@if withEmissive@> +#define emissiveMap 3 +vec3 fetchEmissiveMap(vec2 uv) { + return tableTexValue(matTex, emissiveMap, uv).rgb; +} +<@endif@> + +<@if withOcclusion@> +#define occlusionMap 5 +float fetchOcclusionMap(vec2 uv) { + return tableTexValue(matTex, occlusionMap, uv).r; +} +<@endif@> + +<@if withScattering@> +#define scatteringMap 6 +float fetchScatteringMap(vec2 uv) { + float scattering = texture(tableTex(matTex, scatteringMap), uv).r; // boolean scattering for now + return max(((scattering - 0.1) / 0.9), 0.0); + return tableTexValue(matTex, scatteringMap, uv).r; // boolean scattering for now +} +<@endif@> + +#else + <@if withAlbedo@> uniform sampler2D albedoMap; vec4 fetchAlbedoMap(vec2 uv) { @@ -102,6 +170,8 @@ float fetchScatteringMap(vec2 uv) { } <@endif@> +#endif + <@endfunc@> @@ -148,25 +218,25 @@ vec3 fetchLightmapMap(vec2 uv) { } <@endfunc@> -<@func tangentToViewSpace(fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> +<@func evalMaterialNormal(fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> { vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz); vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz); - vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 normalizedBitangent = cross(normalizedNormal, normalizedTangent); vec3 localNormal = <$fetchedNormal$>; - <$normal$> = vec3(normalizedTangent * localNormal.x + normalizedNormal * localNormal.y + normalizedBitangent * localNormal.z); + <$normal$> = vec3(normalizedBitangent * localNormal.x + normalizedNormal * localNormal.y + normalizedTangent * localNormal.z); } <@endfunc@> -<@func tangentToViewSpaceLOD(fragPos, fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> +<@func evalMaterialNormalLOD(fragPos, fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> { vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz); vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz); - vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 normalizedBitangent = cross(normalizedNormal, normalizedTangent); // attenuate the normal map divergence from the mesh normal based on distance - // THe attenuation range [20,100] meters from the eye is arbitrary for now + // The attenuation range [20,100] meters from the eye is arbitrary for now vec3 localNormal = mix(<$fetchedNormal$>, vec3(0.0, 1.0, 0.0), smoothstep(20.0, 100.0, (-<$fragPos$>).z)); - <$normal$> = vec3(normalizedTangent * localNormal.x + normalizedNormal * localNormal.y + normalizedBitangent * localNormal.z); + <$normal$> = vec3(normalizedBitangent * localNormal.x + normalizedNormal * localNormal.y + normalizedTangent * localNormal.z); } <@endfunc@> diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 5231b3e4d0..72a4b7e0a4 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -47,6 +47,8 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr } } +const graphics::MaterialPointer MeshPartPayload::DEFAULT_MATERIAL = std::make_shared(); + MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material) { updateMeshPart(mesh, partIndex); addMaterial(graphics::MaterialLayer(material, 0)); @@ -77,7 +79,7 @@ void MeshPartPayload::removeMaterial(graphics::MaterialPointer material) { _drawMaterials.remove(material); } -void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -91,11 +93,15 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, builder.withLayered(); } + if (canCastShadow) { + builder.withShadowCaster(); + } + if (isGroupCulled) { builder.withSubMetaCulled(); } - if (_drawMaterials.top().material) { + if (topMaterialExists()) { auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); @@ -115,7 +121,7 @@ Item::Bound MeshPartPayload::getBound() const { ShapeKey MeshPartPayload::getShapeKey() const { graphics::MaterialKey drawMaterialKey; - if (_drawMaterials.top().material) { + if (topMaterialExists()) { drawMaterialKey = _drawMaterials.top().material->getKey(); } @@ -146,7 +152,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputStream(0, _drawMesh->getVertexStream()); } -void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { + void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { batch.setModelTransform(_drawTransform); } @@ -167,7 +173,7 @@ void MeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); + RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! @@ -325,7 +331,8 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +// Note that this method is called for models but not for shapes +void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -339,6 +346,10 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withLayered(); } + if (canCastShadow) { + builder.withShadowCaster(); + } + if (isGroupCulled) { builder.withSubMetaCulled(); } @@ -347,7 +358,7 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withDeformed(); } - if (_drawMaterials.top().material) { + if (topMaterialExists()) { auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); @@ -378,7 +389,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe } graphics::MaterialKey drawMaterialKey; - if (_drawMaterials.top().material) { + if (topMaterialExists()) { drawMaterialKey = _drawMaterials.top().material->getKey(); } @@ -460,7 +471,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); + RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 3ad222d90c..08ad7a8311 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -32,7 +32,7 @@ public: typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false); + virtual void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false); virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); @@ -65,15 +65,18 @@ public: graphics::Mesh::Part _drawPart; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureSize() : 0; } - int getMaterialTextureCount() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureCount() : 0; } - bool hasTextureInfo() const { return _drawMaterials.top().material ? _drawMaterials.top().material->hasTextureInfo() : false; } + size_t getMaterialTextureSize() { return topMaterialExists() ? _drawMaterials.top().material->getTextureSize() : 0; } + int getMaterialTextureCount() { return topMaterialExists() ? _drawMaterials.top().material->getTextureCount() : 0; } + bool hasTextureInfo() const { return topMaterialExists() ? _drawMaterials.top().material->hasTextureInfo() : false; } void addMaterial(graphics::MaterialLayer material); void removeMaterial(graphics::MaterialPointer material); protected: + static const graphics::MaterialPointer DEFAULT_MATERIAL; render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() }; + + bool topMaterialExists() const { return !_drawMaterials.empty() && _drawMaterials.top().material; } }; namespace render { @@ -92,7 +95,7 @@ public: void notifyLocationChanged() override; - void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false) override; + void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false) override; // matrix palette skinning void updateClusterBuffer(const std::vector& clusterMatrices); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 12d6659849..70f873734a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -104,6 +104,7 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), _isVisible(true), + _canCastShadow(false), _blendNumber(0), _appliedBlendNumber(0), _isWireframe(false) @@ -270,6 +271,7 @@ void Model::updateRenderItems() { bool isWireframe = self->isWireframe(); bool isVisible = self->isVisible(); + bool canCastShadow = self->canCastShadow(); uint8_t viewTagBits = self->getViewTagBits(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); @@ -288,7 +290,7 @@ void Model::updateRenderItems() { transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, invalidatePayloadShapeKey, isWireframe, isVisible, - viewTagBits, isLayeredInFront, + canCastShadow, viewTagBits, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); @@ -313,7 +315,7 @@ void Model::updateRenderItems() { } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); }); @@ -684,12 +686,20 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); + int shapeID = 0; for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& fbxMesh = geometry.meshes.at(i); if (auto mesh = fbxMesh._mesh) { result.append(mesh); + + int numParts = (int)mesh->getNumParts(); + for (int partIndex = 0; partIndex < numParts; partIndex++) { + result.appendMaterial(graphics::MaterialLayer(getGeometry()->getShapeMaterial(shapeID), 0), shapeID, _modelMeshMaterialNames[shapeID]); + shapeID++; + } } } + result.appendMaterialNames(_modelMeshMaterialNames); return result; } @@ -773,46 +783,66 @@ void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, bool isLayeredInFront = _isLayeredInFront; bool isLayeredInHUD = _isLayeredInHUD; - + bool canCastShadow = _canCastShadow; render::Transaction transaction; foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); }); } scene->enqueueTransaction(transaction); } } +void Model::setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled) { + if (_canCastShadow != canCastShadow) { + _canCastShadow = canCastShadow; + + bool isVisible = _isVisible; + bool isLayeredInFront = _isLayeredInFront; + bool isLayeredInHUD = _isLayeredInHUD; + + render::Transaction transaction; + foreach (auto item, _modelMeshRenderItemsMap.keys()) { + transaction.updateItem(item, + [isVisible, viewTagBits, canCastShadow, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, viewTagBits, canCastShadow, isLayeredInFront || isLayeredInHUD, isGroupCulled); + }); + } + + scene->enqueueTransaction(transaction); + } +} void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene) { if (_isLayeredInFront != isLayeredInFront) { _isLayeredInFront = isLayeredInFront; bool isVisible = _isVisible; + bool canCastShadow = _canCastShadow; uint8_t viewTagBits = _viewTagBits; bool isLayeredInHUD = _isLayeredInHUD; bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } @@ -825,22 +855,23 @@ void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& sce _isLayeredInHUD = isLayeredInHUD; bool isVisible = _isVisible; + bool canCastShadow = _canCastShadow; uint8_t viewTagBits = _viewTagBits; bool isLayeredInFront = _isLayeredInFront; bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } @@ -1573,7 +1604,8 @@ void Model::createVisibleRenderItemSet() { int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); - _modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName()); + auto material = getGeometry()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } @@ -1650,15 +1682,16 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par uint8_t viewTagBits = getViewTagBits(); bool layeredInFront = isLayeredInFront(); bool layeredInHUD = isLayeredInHUD(); + bool canCastShadow = _canCastShadow; bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; - transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, + transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, canCastShadow, invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.addMaterial(material); // if the material changed, we might need to update our item key or shape key - data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); + data.updateKey(visible, layeredInFront || layeredInHUD, canCastShadow, viewTagBits); data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); }); } @@ -1676,15 +1709,16 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string uint8_t viewTagBits = getViewTagBits(); bool layeredInFront = isLayeredInFront(); bool layeredInHUD = isLayeredInHUD(); + bool canCastShadow = _canCastShadow; bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; - transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, + transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, canCastShadow, invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.removeMaterial(material); // if the material changed, we might need to update our item key or shape key - data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); + data.updateKey(visible, layeredInFront || layeredInHUD, canCastShadow, viewTagBits); data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 9c98ac2b8b..2b14a7c055 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -88,6 +88,10 @@ public: // new Scene/Engine rendering support void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); + + bool canCastShadow() const { return _canCastShadow; } + void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); + void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); bool needsFixupInScene() const; @@ -403,6 +407,8 @@ protected: bool _isVisible; uint8_t _viewTagBits{ render::ItemKey::TAG_BITS_ALL }; + bool _canCastShadow; + gpu::Buffers _blendedVertexBuffers; QVector > > _dilatedTextures; diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index a824a2221b..d54cefa5c7 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -8,53 +8,23 @@ #include "RenderCommonTask.h" -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include - -#include "LightingModel.h" -#include "StencilMaskPass.h" -#include "DebugDeferredBuffer.h" -#include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" -#include "SurfaceGeometryPass.h" -#include "FramebufferCache.h" -#include "TextureCache.h" -#include "ZoneRenderer.h" -#include "FadeEffect.h" #include "RenderUtilsLogging.h" -#include "AmbientOcclusionEffect.h" -#include "AntialiasingEffect.h" -#include "ToneMappingEffect.h" -#include "SubsurfaceScattering.h" -#include "DrawHaze.h" -#include "BloomEffect.h" -#include "HighlightEffect.h" - -#include - using namespace render; extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { timer = _gpuTimer; - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BeginGPURangeTimer", renderContext->args->_context, [&](gpu::Batch& batch) { _gpuTimer->begin(batch); }); } void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer) { - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("EndGPURangeTimer", renderContext->args->_context, [&](gpu::Batch& batch) { timer->end(batch); }); @@ -87,14 +57,14 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& // Needs to be distinct from the other batch because using the clear call // while stereo is enabled triggers a warning if (_opaquePass) { - gpu::doInBatch(args->_context, [&](gpu::Batch& batch){ + gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch){ batch.enableStereo(false); batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); }); } // Render the items - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawOverlay3D::main", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -127,7 +97,13 @@ void CompositeHUD::run(const RenderContextPointer& renderContext) { // Grab the HUD texture #if !defined(DISABLE_QML) - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("CompositeHUD", renderContext->args->_context, [&](gpu::Batch& batch) { + glm::mat4 projMat; + Transform viewMat; + renderContext->args->getViewFrustum().evalProjectionMatrix(projMat); + renderContext->args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, true); if (renderContext->args->_hudOperator) { renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); } @@ -154,7 +130,7 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer // Blit primary to blit FBO auto primaryFbo = srcFramebuffer; - gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("Blit", renderArgs->_context, [&](gpu::Batch& batch) { batch.setFramebuffer(blitFbo); if (renderArgs->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index f262307944..2377f5131f 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -34,6 +34,7 @@ #include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" #include "SurfaceGeometryPass.h" +#include "VelocityBufferPass.h" #include "FramebufferCache.h" #include "TextureCache.h" #include "ZoneRenderer.h" @@ -94,9 +95,12 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren fadeEffect->build(task, opaques); + task.addJob("JitterCam"); + // Prepare deferred, generate the shared Deferred Frame Transform const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); const auto lightingModel = task.addJob("LightingModel"); + // GPU jobs: Start preparing the primary, deferred and lighting buffer const auto primaryFramebuffer = task.addJob("PreparePrimaryBuffer"); @@ -142,13 +146,17 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN(0); const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN(1); - + // Velocity + const auto velocityBufferInputs = VelocityBufferPass::Inputs(deferredFrameTransform, deferredFramebuffer).asVarying(); + const auto velocityBufferOutputs = task.addJob("VelocityBuffer", velocityBufferInputs); + const auto velocityBuffer = velocityBufferOutputs.getN(0); + + // Clear Light, Haze and Skybox Stages and render zones from the general metas bucket + const auto zones = task.addJob("ZoneRenderer", metas); + // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. task.addJob("DrawLight", lights); - // Filter zones from the general metas bucket - const auto zones = task.addJob("ZoneRenderer", metas); - // Light Clustering // Create the cluster grid of lights, cpu job for now const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget).asVarying(); @@ -163,6 +171,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("RenderDeferred", deferredLightingInputs); + // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); @@ -221,10 +230,30 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawSelectionBounds", selectedItems); } - // Debugging stages + // Layered Overlays + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); + const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); + + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); + task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer + task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); + task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); + } + + // AA job + const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, primaryFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); + task.addJob("Antialiasing", antialiasingInputs); + + // Debugging stages { // Debugging Deferred buffer job - const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, deferredFrameTransform)); + const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, velocityBuffer, deferredFrameTransform)); task.addJob("DebugDeferredBuffer", debugFramebuffers); const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, @@ -251,25 +280,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZoneStack", deferredFrameTransform); } - // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); - const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); - const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); - - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); - task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); - task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); - - { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer - task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); - task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); - } - - // AA job to be revisited - task.addJob("Antialiasing", primaryFramebuffer); - // Composite the HUD and HUD overlays task.addJob("HUD"); @@ -305,7 +315,7 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawDeferred::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; // Setup camera, projection and viewport for all items @@ -372,7 +382,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawStateSortDeferred::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; // Setup camera, projection and viewport for all items @@ -410,4 +420,3 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const config->setNumDrawn((int)inItems.size()); } - diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 4829e5c85b..63370109e0 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -37,6 +37,7 @@ using namespace render; extern void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { auto items = input.get(); @@ -45,6 +46,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Prepare the ShapePipelines ShapePlumberPointer shapePlumber = std::make_shared(); initForwardPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + initOverlay3DPipelines(*shapePlumber); // Extract opaques / transparents / lights / metas / overlays / background const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; @@ -98,7 +100,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Lighting Buffer ready for tone mapping // Forward rendering on GLES doesn't support tonemapping to and from the same FBO, so we specify // the output FBO as null, which causes the tonemapping to target the blit framebuffer - const auto toneMappingInputs = ToneMappingDeferred::Inputs(framebuffer, nullptr).asVarying(); + const auto toneMappingInputs = ToneMappingDeferred::Inputs(framebuffer, static_cast(nullptr) ).asVarying(); task.addJob("ToneMapping", toneMappingInputs); // Layered Overlays @@ -134,7 +136,7 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::Fra } auto args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("PrepareFramebuffer::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -150,7 +152,8 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::Fra void PrepareForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + + gpu::doInBatch("RenderForward::Draw::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; graphics::LightPointer keySunLight; @@ -184,7 +187,7 @@ void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& i const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawForward::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index aca8439547..a3abb24afe 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -32,6 +32,7 @@ #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" #include "model_translucent_vert.h" +#include "model_translucent_normal_map_vert.h" #include "skin_model_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" #include "skin_model_fade_dq_vert.h" @@ -68,11 +69,13 @@ #include "model_lightmap_normal_map_frag.h" #include "model_translucent_frag.h" #include "model_translucent_unlit_frag.h" +#include "model_translucent_normal_map_frag.h" #include "model_lightmap_fade_frag.h" #include "model_lightmap_normal_map_fade_frag.h" #include "model_translucent_fade_frag.h" #include "model_translucent_unlit_fade_frag.h" +#include "model_translucent_normal_map_fade_frag.h" #include "overlay3D_vert.h" #include "overlay3D_frag.h" @@ -187,6 +190,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelLightmapVertex = model_lightmap_vert::getShader(); auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader(); auto modelTranslucentVertex = model_translucent_vert::getShader(); + auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader(); auto modelShadowVertex = model_shadow_vert::getShader(); auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); @@ -227,6 +231,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelNormalMapPixel = model_normal_map_frag::getShader(); auto modelTranslucentPixel = model_translucent_frag::getShader(); auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader(); + auto modelTranslucentNormalMapPixel = model_translucent_normal_map_frag::getShader(); auto modelShadowPixel = model_shadow_frag::getShader(); auto modelLightmapPixel = model_lightmap_frag::getShader(); auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader(); @@ -239,6 +244,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelShadowFadePixel = model_shadow_fade_frag::getShader(); auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader(); auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader(); + auto modelTranslucentNormalMapFadePixel = model_translucent_normal_map_fade_frag::getShader(); auto simpleFadePixel = simple_textured_fade_frag::getShader(); auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader(); auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader(); @@ -296,7 +302,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentNormalMapVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), @@ -316,7 +322,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withFade(), - modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + modelTranslucentNormalMapVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(), @@ -358,14 +364,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(), skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); // dual quaternion skinned addPipeline( @@ -388,14 +394,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(), skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); // Depth-only addPipeline( @@ -612,7 +618,8 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { #include "RenderPipelines.h" #include -void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) { +// FIXME find a better way to setup the default textures +void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) { if (!material) { return; } @@ -630,84 +637,65 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc numUnlit++; } - if (!enableTextures) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); - return; - } + const auto& drawMaterialTextures = material->getTextureTable(); // Albedo if (materialKey.isAlbedoMap()) { auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); } } // Roughness map if (materialKey.isRoughnessMap()) { auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, textureCache->getWhiteTexture()); } } // Normal map if (materialKey.isNormalMap()) { auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, textureCache->getBlueTexture()); } } // Metallic map if (materialKey.isMetallicMap()) { auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, textureCache->getBlackTexture()); } } // Occlusion map if (materialKey.isOcclusionMap()) { auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, textureCache->getWhiteTexture()); } } // Scattering map if (materialKey.isScatteringMap()) { auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, textureCache->getWhiteTexture()); } } @@ -715,18 +703,19 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc if (materialKey.isLightmapMap()) { auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); } } else if (materialKey.isEmissiveMap()) { auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); - - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); } } + + batch.setResourceTextureTable(material->getTextureTable()); } diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h index 9b9ad2c001..b7d22bc72d 100644 --- a/libraries/render-utils/src/RenderPipelines.h +++ b/libraries/render-utils/src/RenderPipelines.h @@ -15,7 +15,7 @@ class RenderPipelines { public: - static void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures); + static void bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures); }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b5a329cc91..69c5b3c689 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -140,7 +140,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con args->popViewFrustum(); args->pushViewFrustum(adjustedShadowFrustum); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("RenderShadowMap::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.enableStereo(false); @@ -231,7 +231,10 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); + + // Enable models to not cast shadows (otherwise, models will always cast shadows) + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); @@ -310,8 +313,14 @@ void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { } void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { + // Abort all jobs if not casting shadows auto lightStage = renderContext->_scene->getStage(); assert(lightStage); + if (!lightStage->getCurrentKeyLight() || !lightStage->getCurrentKeyLight()->getCastShadows()) { + renderContext->taskFlow.abortTask(); + return; + } + // Cache old render args RenderArgs* args = renderContext->args; @@ -391,12 +400,13 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); + // Cache old render args RenderArgs* args = renderContext->args; const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 7f127a558c..98b70c0c9f 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -38,7 +38,7 @@ class RenderShadowTaskConfig : public render::Task::Config::Persistent { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty) public: - RenderShadowTaskConfig() : render::Task::Config::Persistent(QStringList() << "Render" << "Engine" << "Shadows", false) {} + RenderShadowTaskConfig() : render::Task::Config::Persistent(QStringList() << "Render" << "Engine" << "Shadows", true) {} signals: void dirty(); diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index f9af157e7c..b9b8274039 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -81,7 +81,7 @@ void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::F return; } - doInBatch(args->_context, [&](gpu::Batch& batch) { + doInBatch("PrepareStencil::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index ff415accc3..50067d003f 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -322,7 +322,7 @@ void diffuseProfileGPU(gpu::TexturePointer& profileMap, RenderArgs* args) { auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("diffuseProfile")); makeFramebuffer->setRenderBuffer(0, profileMap); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SubsurfaceScattering::diffuseProfileGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(glm::ivec4(0, 0, width, height)); @@ -359,7 +359,7 @@ void diffuseScatterGPU(const gpu::TexturePointer& profileMap, gpu::TexturePointe auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("diffuseScatter")); makeFramebuffer->setRenderBuffer(0, lut); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SubsurfaceScattering::diffuseScatterGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(glm::ivec4(0, 0, width, height)); @@ -396,7 +396,7 @@ void computeSpecularBeckmannGPU(gpu::TexturePointer& beckmannMap, RenderArgs* ar auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("computeSpecularBeckmann")); makeFramebuffer->setRenderBuffer(0, beckmannMap); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SubsurfaceScattering::computeSpecularBeckmannGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(glm::ivec4(0, 0, width, height)); @@ -537,7 +537,7 @@ void DebugSubsurfaceScattering::run(const render::RenderContextPointer& renderCo // const auto light = DependencyManager::get()->getLightStage()->getLight(0); const auto light = lightStage->getLight(0); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("DebugSubsurfaceScattering::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index afed9ee8fd..cfdb67ecb6 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -174,7 +174,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con auto halfViewport = depthViewport >> 1; float clearLinearDepth = args->getViewFrustum().getFarClip() * 2.0f; - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("LinearDepthPass::run", args->_context, [=](gpu::Batch& batch) { _gpuTimer->begin(batch); batch.enableStereo(false); @@ -466,7 +466,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, _diffusePass.getParameters()->setLinearDepthPosFar(args->getViewFrustum().getFarClip()); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SurfaceGeometryPass::run", args->_context, [=](gpu::Batch& batch) { _gpuTimer->begin(batch); batch.enableStereo(false); diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 0cc5be5ff9..6857de62a7 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -68,7 +68,7 @@ void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& ligh } auto framebufferSize = glm::ivec2(lightingBuffer->getDimensions()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("ToneMappingEffect::render", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(destinationFramebuffer); diff --git a/libraries/render-utils/src/VelocityBufferPass.cpp b/libraries/render-utils/src/VelocityBufferPass.cpp new file mode 100644 index 0000000000..78471d48af --- /dev/null +++ b/libraries/render-utils/src/VelocityBufferPass.cpp @@ -0,0 +1,173 @@ +// +// VelocityBufferPass.cpp +// libraries/render-utils/src/ +// +// Created by Sam Gateau 8/15/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "VelocityBufferPass.h" + +#include + +#include +#include +#include "StencilMaskPass.h" + +const int VelocityBufferPass_FrameTransformSlot = 0; +const int VelocityBufferPass_DepthMapSlot = 0; + + +#include "velocityBuffer_cameraMotion_frag.h" + +VelocityFramebuffer::VelocityFramebuffer() { +} + + +void VelocityFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) { + //If the depth buffer or size changed, we need to delete our FBOs + bool reset = false; + if ((_primaryDepthTexture != depthBuffer)) { + _primaryDepthTexture = depthBuffer; + reset = true; + } + if (_primaryDepthTexture) { + auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions()); + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + _halfFrameSize = newFrameSize >> 1; + + reset = true; + } + } + + if (reset) { + clear(); + } +} + +void VelocityFramebuffer::clear() { + _velocityFramebuffer.reset(); + _velocityTexture.reset(); +} + +void VelocityFramebuffer::allocate() { + + auto width = _frameSize.x; + auto height = _frameSize.y; + + // For Velocity Buffer: + _velocityTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::VEC2, gpu::HALF, gpu::RGB), width, height, gpu::Texture::SINGLE_MIP, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR)); + _velocityFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("velocity")); + _velocityFramebuffer->setRenderBuffer(0, _velocityTexture); + _velocityFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); +} + +gpu::FramebufferPointer VelocityFramebuffer::getVelocityFramebuffer() { + if (!_velocityFramebuffer) { + allocate(); + } + return _velocityFramebuffer; +} + +gpu::TexturePointer VelocityFramebuffer::getVelocityTexture() { + if (!_velocityTexture) { + allocate(); + } + return _velocityTexture; +} + +VelocityBufferPass::VelocityBufferPass() { +} + +void VelocityBufferPass::configure(const Config& config) { +} + +void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + const auto& frameTransform = inputs.get0(); + const auto& deferredFramebuffer = inputs.get1(); + + if (!_gpuTimer) { + _gpuTimer = std::make_shared < gpu::RangeTimer>(__FUNCTION__); + } + + if (!_velocityFramebuffer) { + _velocityFramebuffer = std::make_shared(); + } + _velocityFramebuffer->updatePrimaryDepth(deferredFramebuffer->getPrimaryDepthTexture()); + + auto depthBuffer = deferredFramebuffer->getPrimaryDepthTexture(); + + auto velocityFBO = _velocityFramebuffer->getVelocityFramebuffer(); + auto velocityTexture = _velocityFramebuffer->getVelocityTexture(); + + outputs.edit0() = _velocityFramebuffer; + outputs.edit1() = velocityFBO; + outputs.edit2() = velocityTexture; + + auto cameraMotionPipeline = getCameraMotionPipeline(); + + auto fullViewport = args->_viewport; + + gpu::doInBatch("VelocityBufferPass::run", args->_context, [=](gpu::Batch& batch) { + _gpuTimer->begin(batch); + batch.enableStereo(false); + + batch.setViewportTransform(fullViewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_velocityFramebuffer->getDepthFrameSize(), fullViewport)); + + batch.setUniformBuffer(VelocityBufferPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + + // Velocity buffer camera motion + batch.setFramebuffer(velocityFBO); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)); + batch.setPipeline(cameraMotionPipeline); + batch.setResourceTexture(VelocityBufferPass_DepthMapSlot, depthBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + _gpuTimer->end(batch); + }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setGPUBatchRunTime(_gpuTimer->getGPUAverage(), _gpuTimer->getBatchAverage()); +} + + +const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline() { + if (!_cameraMotionPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = velocityBuffer_cameraMotion_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), VelocityBufferPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), VelocityBufferPass_DepthMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + // PrepareStencil::testShape(*state); + + state->setColorWriteMask(true, true, false, false); + + // Good to go add the brand new pipeline + _cameraMotionPipeline = gpu::Pipeline::create(program, state); + } + + return _cameraMotionPipeline; +} + + + diff --git a/libraries/render-utils/src/VelocityBufferPass.h b/libraries/render-utils/src/VelocityBufferPass.h new file mode 100644 index 0000000000..fb2b729368 --- /dev/null +++ b/libraries/render-utils/src/VelocityBufferPass.h @@ -0,0 +1,89 @@ +// +// VelocityBufferPass.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 8/15/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_VelocityBufferPass_h +#define hifi_VelocityBufferPass_h + +#include "SurfaceGeometryPass.h" + + +// VelocityFramebuffer is a helper class gathering in one place theframebuffers and targets describing the surface geometry linear depth +// from a z buffer +class VelocityFramebuffer { +public: + VelocityFramebuffer(); + + gpu::FramebufferPointer getVelocityFramebuffer(); + gpu::TexturePointer getVelocityTexture(); + + // Update the depth buffer which will drive the allocation of all the other resources according to its size. + void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer); + + gpu::TexturePointer getPrimaryDepthTexture(); + const glm::ivec2& getDepthFrameSize() const { return _frameSize; } + + void setResolutionLevel(int level); + int getResolutionLevel() const { return _resolutionLevel; } + +protected: + void clear(); + void allocate(); + + gpu::TexturePointer _primaryDepthTexture; + + gpu::FramebufferPointer _velocityFramebuffer; + gpu::TexturePointer _velocityTexture; + + glm::ivec2 _frameSize; + glm::ivec2 _halfFrameSize; + int _resolutionLevel{ 0 }; +}; + +using VelocityFramebufferPointer = std::shared_ptr; + +class VelocityBufferPassConfig : public render::GPUJobConfig { + Q_OBJECT + Q_PROPERTY(float depthThreshold MEMBER depthThreshold NOTIFY dirty) + +public: + VelocityBufferPassConfig() : render::GPUJobConfig(true) {} + + float depthThreshold{ 5.0f }; + +signals: + void dirty(); +}; + +class VelocityBufferPass { +public: + using Inputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; + using Config = VelocityBufferPassConfig; + using JobModel = render::Job::ModelIO; + + VelocityBufferPass(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + typedef gpu::BufferView UniformBufferView; + + VelocityFramebufferPointer _velocityFramebuffer; + + const gpu::PipelinePointer& getCameraMotionPipeline(); + gpu::PipelinePointer _cameraMotionPipeline; + + gpu::RangeTimerPointer _gpuTimer; +}; + + +#endif // hifi_VelocityBufferPass_h diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index b6d0e61577..51939efd4f 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -171,7 +171,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I } - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("DebugZoneLighting::run", args->_context, [=](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); auto viewFrustum = args->getViewFrustum(); diff --git a/libraries/render-utils/src/animdebugdraw.slv b/libraries/render-utils/src/animdebugdraw.slv index ffa44b6cee..3255c6783c 100644 --- a/libraries/render-utils/src/animdebugdraw.slv +++ b/libraries/render-utils/src/animdebugdraw.slv @@ -17,7 +17,7 @@ out vec4 _color; void main(void) { // pass along the color - _color = colorToLinearRGBA(inColor.rgba); + _color = color_sRGBAToLinear(inColor.rgba); TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index 426de623a1..fded04ca87 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -22,6 +22,7 @@ uniform sampler2D halfNormalMap; uniform sampler2D occlusionMap; uniform sampler2D occlusionBlurredMap; uniform sampler2D scatteringMap; +uniform sampler2D velocityMap; <@include ShadowCore.slh@> diff --git a/libraries/render-utils/src/forward_model_normal_map.slf b/libraries/render-utils/src/forward_model_normal_map.slf index b32ed862d6..0ba464d3f0 100644 --- a/libraries/render-utils/src/forward_model_normal_map.slf +++ b/libraries/render-utils/src/forward_model_normal_map.slf @@ -58,7 +58,7 @@ void main(void) { vec3 fragPosition = _position.xyz; vec3 fragNormal; - <$tangentToViewSpace(normalTex, _normal, _tangent, fragNormal)$> + <$evalMaterialNormal(normalTex, _normal, _tangent, fragNormal)$> TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 7b241a3ebf..94fa75c47f 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -23,72 +23,112 @@ precision mediump int; #endif uniform sampler2D colorTexture; +//uniform sampler2D historyTexture; uniform vec2 texcoordOffset; -in vec2 varTexcoord; -out vec4 outFragColor; +in vec2 varTexCoord0; +layout(location = 0) out vec4 outFragColor; +//layout(location = 0) out vec4 outFragHistory; void main() { - // filter width limit for dependent "two-tap" texture samples - float FXAA_SPAN_MAX = 8.0; + outFragColor = vec4(texture(colorTexture, varTexCoord0).xyz, 1.0/8.0); - // local contrast multiplier for performing AA - // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail - // see "fxaaQualityEdgeThreshold" - float FXAA_REDUCE_MUL = 1.0 / 8.0; + // v2 + /* float ModulationFactor = 1.0 / 8.0; - // luminance threshold for processing dark colors - // see "fxaaQualityEdgeThresholdMin" - float FXAA_REDUCE_MIN = 1.0 / 128.0; + vec3 History = textureLod(historyTexture, varTexCoord0, 0.0).rgb; + vec3 CurrentSubpixel = textureLod(colorTexture, varTexCoord0, 0.0).rgb; + /* + vec3 NearColor0 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(1, 0)).xyz; + vec3 NearColor1 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(0, 1)).xyz; + vec3 NearColor2 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(-1, 0)).xyz; + vec3 NearColor3 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(0, -1)).xyz; - // fetch raw RGB values for nearby locations - // sampling pattern is "five on a die" (each diagonal direction and the center) - // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed - vec3 rgbNW = texture(colorTexture, varTexcoord + (vec2(-1.0, -1.0) * texcoordOffset)).xyz; - vec3 rgbNE = texture(colorTexture, varTexcoord + (vec2(+1.0, -1.0) * texcoordOffset)).xyz; - vec3 rgbSW = texture(colorTexture, varTexcoord + (vec2(-1.0, +1.0) * texcoordOffset)).xyz; - vec3 rgbSE = texture(colorTexture, varTexcoord + (vec2(+1.0, +1.0) * texcoordOffset)).xyz; - vec3 rgbM = texture(colorTexture, varTexcoord).xyz; - - // convert RGB values to luminance - vec3 luma = vec3(0.299, 0.587, 0.114); - float lumaNW = dot(rgbNW, luma); - float lumaNE = dot(rgbNE, luma); - float lumaSW = dot(rgbSW, luma); - float lumaSE = dot(rgbSE, luma); - float lumaM = dot( rgbM, luma); - - // luma range of local neighborhood - float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); - float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - - // direction perpendicular to local luma gradient - vec2 dir; - dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); - dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + vec3 BoxMin = min(CurrentSubpixel, min(NearColor0, min(NearColor1, min(NearColor2, NearColor3)))); + vec3 BoxMax = max(CurrentSubpixel, max(NearColor0, max(NearColor1, max(NearColor2, NearColor3))));; - // compute clamped direction offset for additional "two-tap" samples - // longer vector = blurry, shorter vector = sharp - float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); - float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); - dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), - max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; - - // perform additional texture sampling perpendicular to gradient - vec3 rgbA = (1.0 / 2.0) * ( - texture(colorTexture, varTexcoord + dir * (1.0 / 3.0 - 0.5)).xyz + - texture(colorTexture, varTexcoord + dir * (2.0 / 3.0 - 0.5)).xyz); - vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( - texture(colorTexture, varTexcoord + dir * (0.0 / 3.0 - 0.5)).xyz + - texture(colorTexture, varTexcoord + dir * (3.0 / 3.0 - 0.5)).xyz); - float lumaB = dot(rgbB, luma); + if (gl_FragCoord.x > 800) { + History = clamp(History, BoxMin, BoxMax); + } - // compare luma of new samples to the luma range of the original neighborhood - // if the new samples exceed this range, just use the first two samples instead of all four - if (lumaB < lumaMin || lumaB > lumaMax) { - outFragColor.xyz=rgbA; - } else { - outFragColor.xyz=rgbB; + History = mix(CurrentSubpixel, History, ModulationFactor); + + /* outFragHistory.xyz = History; + outFragHistory.w = ModulationFactor + + outFragColor.xyz = History; + outFragColor.w = 1.0;*/ + + + + /* } else { + outFragColor.xyz = CurrentSubpixel; + outFragColor.w = 1.0; + + }*/ + if (gl_FragCoord.x > 800) { + /* // filter width limit for dependent "two-tap" texture samples + float FXAA_SPAN_MAX = 8.0; + + // local contrast multiplier for performing AA + // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail + // see "fxaaQualityEdgeThreshold" + float FXAA_REDUCE_MUL = 1.0 / 8.0; + + // luminance threshold for processing dark colors + // see "fxaaQualityEdgeThresholdMin" + float FXAA_REDUCE_MIN = 1.0 / 128.0; + + // fetch raw RGB values for nearby locations + // sampling pattern is "five on a die" (each diagonal direction and the center) + // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed + vec3 rgbNW = texture(colorTexture, varTexCoord0 + (vec2(-1.0, -1.0) * texcoordOffset)).xyz; + vec3 rgbNE = texture(colorTexture, varTexCoord0 + (vec2(+1.0, -1.0) * texcoordOffset)).xyz; + vec3 rgbSW = texture(colorTexture, varTexCoord0 + (vec2(-1.0, +1.0) * texcoordOffset)).xyz; + vec3 rgbSE = texture(colorTexture, varTexCoord0 + (vec2(+1.0, +1.0) * texcoordOffset)).xyz; + vec3 rgbM = texture(colorTexture, varTexCoord0).xyz; + + // convert RGB values to luminance + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot(rgbM, luma); + + // luma range of local neighborhood + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + // direction perpendicular to local luma gradient + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + // compute clamped direction offset for additional "two-tap" samples + // longer vector = blurry, shorter vector = sharp + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; + + // perform additional texture sampling perpendicular to gradient + vec3 rgbA = (1.0 / 2.0) * ( + texture(colorTexture, varTexCoord0 + dir * (1.0 / 3.0 - 0.5)).xyz + + texture(colorTexture, varTexCoord0 + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( + texture(colorTexture, varTexCoord0 + dir * (0.0 / 3.0 - 0.5)).xyz + + texture(colorTexture, varTexCoord0 + dir * (3.0 / 3.0 - 0.5)).xyz); + float lumaB = dot(rgbB, luma); + + // compare luma of new samples to the luma range of the original neighborhood + // if the new samples exceed this range, just use the first two samples instead of all four + if (lumaB < lumaMin || lumaB > lumaMax) { + outFragColor.xyz = rgbA; + } + else { + outFragColor.xyz = rgbB; + }*/ + outFragColor.a = 1.0; } - outFragColor.a = 1.0; } diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index d5819cc9a6..7a10cecb94 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -14,11 +14,27 @@ <@include DeferredBufferWrite.slh@> -in vec2 varTexcoord; +in vec2 varTexCoord0; out vec4 outFragColor; uniform sampler2D colorTexture; +uniform float sharpenIntensity; void main(void) { - outFragColor = texture(colorTexture, varTexcoord); + vec4 pixels[9]; + vec4 sharpenedPixel; + pixels[0] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(-1,-1), 0); + pixels[1] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(0,-1), 0); + pixels[2] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,-1), 0); + + pixels[3] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(-1,0), 0); + pixels[4] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy), 0); + pixels[5] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,0), 0); + + pixels[6] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(-1,1), 0); + pixels[7] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(0,1), 0); + pixels[8] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,1), 0); + + sharpenedPixel = pixels[4]*7.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; + outFragColor = mix(pixels[4], sharpenedPixel, sharpenIntensity); } diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index 739709418d..d4d97c5b16 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -32,7 +32,7 @@ void main(void) { // Grab the fragment data from the uv vec2 texCoord = varTexCoord0.st; - vec4 fragEyePos = unpackDeferredPositionFromZeye(texCoord); + vec4 fragEyePos = unpackDeferredPositionFromZdb(texCoord); vec4 fragWorldPos = getViewInverse() * fragEyePos; // From frag world pos find the cluster diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index ee2e6e0ccc..c51d45ed44 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -29,7 +29,7 @@ void main(void) { // Grab the fragment data from the uv vec2 texCoord = varTexCoord0.st; - vec4 fragEyePos = unpackDeferredPositionFromZeye(texCoord); + vec4 fragEyePos = unpackDeferredPositionFromZdb(texCoord); vec4 fragWorldPos = getViewInverse() * fragEyePos; // From frag world pos find the cluster diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 06f6030e77..ccedff9b61 100644 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -27,7 +27,7 @@ out vec4 _position; out vec3 _normal; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_fade.slv b/libraries/render-utils/src/model_fade.slv index 4c6bc534a9..61b8e9e1b6 100644 --- a/libraries/render-utils/src/model_fade.slv +++ b/libraries/render-utils/src/model_fade.slv @@ -28,7 +28,7 @@ out vec3 _normal; out vec3 _color; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index 161ceed14c..e00fcb708e 100644 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -28,7 +28,7 @@ out vec3 _color; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); // and the texture coordinates TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_lightmap_fade.slv b/libraries/render-utils/src/model_lightmap_fade.slv index 561049d614..d1a1194de1 100644 --- a/libraries/render-utils/src/model_lightmap_fade.slv +++ b/libraries/render-utils/src/model_lightmap_fade.slv @@ -29,7 +29,7 @@ out vec4 _worldPosition; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); // and the texture coordinates TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slf b/libraries/render-utils/src/model_lightmap_normal_map.slf index 8734ea74b8..eecde59e54 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map.slf @@ -33,11 +33,11 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> - vec3 viewNormal; - <$tangentToViewSpaceLOD(_position, normalTexel, _normal, _tangent, viewNormal)$> + vec3 fragNormal; + <$evalMaterialNormalLOD(_position, normalTexel, _normal, _tangent, fragNormal)$> packDeferredFragmentLightmap( - normalize(viewNormal.xyz), + normalize(fragNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), getMaterialAlbedo(mat) * albedo.rgb * _color, getMaterialRoughness(mat) * roughness, diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index 5fb60d9227..3b1ecaab0c 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -29,7 +29,7 @@ out vec3 _color; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slf b/libraries/render-utils/src/model_lightmap_normal_map_fade.slf index e6cb35ec4f..af497f90c6 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map_fade.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slf @@ -43,11 +43,11 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> - vec3 viewNormal; - <$tangentToViewSpaceLOD(_position, normalTexel, _normal, _tangent, viewNormal)$> + vec3 fragNormal; + <$evalMaterialNormalLOD(_position, normalTexel, _normal, _tangent, fragNormal)$> packDeferredFragmentLightmap( - normalize(viewNormal.xyz), + normalize(fragNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), getMaterialAlbedo(mat) * albedo.rgb * _color, getMaterialRoughness(mat) * roughness, diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv index 4049fb0077..5c1212b1b7 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv @@ -30,7 +30,7 @@ out vec4 _worldPosition; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index 82f667bf73..49613eca6a 100644 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -46,8 +46,8 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 viewNormal; - <$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$> + vec3 fragNormal; + <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; @@ -56,7 +56,7 @@ void main(void) { <$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>; packDeferredFragment( - normalize(viewNormal.xyz), + normalize(fragNormal.xyz), opacity, albedo, roughness, diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index 9e674d93fc..a84f8c5e2a 100644 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -30,7 +30,7 @@ out float _alpha; void main(void) { // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_normal_map_fade.slf b/libraries/render-utils/src/model_normal_map_fade.slf index 67bea98cf0..bf6222652c 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slf +++ b/libraries/render-utils/src/model_normal_map_fade.slf @@ -56,8 +56,8 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 viewNormal; - <$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$> + vec3 fragNormal; + <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; @@ -65,7 +65,7 @@ void main(void) { float scattering = getMaterialScattering(mat); packDeferredFragment( - normalize(viewNormal.xyz), + normalize(fragNormal.xyz), opacity, albedo, roughness, diff --git a/libraries/render-utils/src/model_normal_map_fade.slv b/libraries/render-utils/src/model_normal_map_fade.slv index a71900d5c3..6a6142d317 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slv +++ b/libraries/render-utils/src/model_normal_map_fade.slv @@ -31,7 +31,7 @@ out float _alpha; void main(void) { // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_translucent.slv b/libraries/render-utils/src/model_translucent.slv index 305aba06c3..7a57a4baec 100644 --- a/libraries/render-utils/src/model_translucent.slv +++ b/libraries/render-utils/src/model_translucent.slv @@ -28,7 +28,7 @@ out vec3 _normal; out vec3 _color; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 759007d93e..52015660c6 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -61,7 +61,7 @@ void main(void) { vec3 fragPosition = _position.xyz; vec3 fragNormal; - <$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, fragNormal)$> + <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> TransformCamera cam = getTransformCamera(); vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); diff --git a/libraries/render-utils/src/model_translucent_normal_map.slv b/libraries/render-utils/src/model_translucent_normal_map.slv index db824a3709..981a03627b 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slv +++ b/libraries/render-utils/src/model_translucent_normal_map.slv @@ -29,7 +29,7 @@ out vec3 _tangent; out vec3 _color; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index 204b5ac56b..c6c0e16812 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -71,7 +71,7 @@ void main(void) { vec3 fragPosition = _position.xyz; // Lighting is done in world space vec3 fragNormal; - <$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, fragNormal)$> + <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> TransformCamera cam = getTransformCamera(); vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); diff --git a/libraries/render-utils/src/overlay3D.slv b/libraries/render-utils/src/overlay3D.slv index ee28367413..7184f923e4 100644 --- a/libraries/render-utils/src/overlay3D.slv +++ b/libraries/render-utils/src/overlay3D.slv @@ -23,7 +23,7 @@ out vec4 _position; out vec3 _normal; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; _texCoord0 = inTexCoord0.st; diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 64d3e24192..0ce6505a65 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -25,7 +25,7 @@ out vec2 _texCoord0; out vec4 _position; void main(void) { - _color = colorToLinearRGBA(inColor); + _color = color_sRGBAToLinear(inColor); _texCoord0 = inTexCoord0.st; _position = inPosition; _modelNormal = inNormal.xyz; diff --git a/libraries/render-utils/src/simple_fade.slv b/libraries/render-utils/src/simple_fade.slv index 3d9eb2c812..85946045ac 100644 --- a/libraries/render-utils/src/simple_fade.slv +++ b/libraries/render-utils/src/simple_fade.slv @@ -29,7 +29,7 @@ out vec4 _position; out vec4 _worldPosition; void main(void) { - _color = colorToLinearRGBA(inColor); + _color = color_sRGBAToLinear(inColor); _texCoord0 = inTexCoord0.st; _position = inPosition; _modelNormal = inNormal.xyz; diff --git a/libraries/render-utils/src/simple_opaque_web_browser.slf b/libraries/render-utils/src/simple_opaque_web_browser.slf index 3acf104b55..af7ef78682 100644 --- a/libraries/render-utils/src/simple_opaque_web_browser.slf +++ b/libraries/render-utils/src/simple_opaque_web_browser.slf @@ -25,6 +25,6 @@ in vec2 _texCoord0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); packDeferredFragmentUnlit(normalize(_normal), 1.0, _color.rgb * texel.rgb); } diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 34fcbc77dc..4fd734aad5 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -28,7 +28,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_textured_fade.slf b/libraries/render-utils/src/simple_textured_fade.slf index 2061cabdfc..d378e7a5c1 100644 --- a/libraries/render-utils/src/simple_textured_fade.slf +++ b/libraries/render-utils/src/simple_textured_fade.slf @@ -40,7 +40,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_textured_unlit.slf b/libraries/render-utils/src/simple_textured_unlit.slf index d261fb343a..1daea2f5c7 100644 --- a/libraries/render-utils/src/simple_textured_unlit.slf +++ b/libraries/render-utils/src/simple_textured_unlit.slf @@ -27,7 +27,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_textured_unlit_fade.slf b/libraries/render-utils/src/simple_textured_unlit_fade.slf index 6f03c6746f..b3c5a914b2 100644 --- a/libraries/render-utils/src/simple_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_textured_unlit_fade.slf @@ -39,7 +39,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 30c420233f..c6b0d83914 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -34,7 +34,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float opacity = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); opacity = -_color.a; } opacity *= texel.a; diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index a8a5875a4b..a5103660b9 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -46,7 +46,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float opacity = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); opacity = -_color.a; } opacity *= texel.a; diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit.slf b/libraries/render-utils/src/simple_transparent_textured_unlit.slf index 693d7be2db..e9c1104cf0 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit.slf @@ -29,7 +29,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } _fragColor0 = vec4(_color.rgb * texel.rgb, colorAlpha * texel.a); diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf index 1c42a1f724..093b70755f 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf @@ -40,7 +40,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } _fragColor0 = vec4(_color.rgb * texel.rgb+fadeEmissive, colorAlpha * texel.a); diff --git a/libraries/render-utils/src/simple_transparent_web_browser.slf b/libraries/render-utils/src/simple_transparent_web_browser.slf index 19079f5d92..414f3f683f 100644 --- a/libraries/render-utils/src/simple_transparent_web_browser.slf +++ b/libraries/render-utils/src/simple_transparent_web_browser.slf @@ -25,7 +25,7 @@ in vec2 _texCoord0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); packDeferredFragmentTranslucent( normalize(_normal), _color.a, diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index bd1655fc40..480e48a5d4 100644 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -37,7 +37,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_dq.slv b/libraries/render-utils/src/skin_model_dq.slv index 96f9b4a713..6e369c7c82 100644 --- a/libraries/render-utils/src/skin_model_dq.slv +++ b/libraries/render-utils/src/skin_model_dq.slv @@ -37,7 +37,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_fade.slv b/libraries/render-utils/src/skin_model_fade.slv index b14bf1532e..4f459d75f3 100644 --- a/libraries/render-utils/src/skin_model_fade.slv +++ b/libraries/render-utils/src/skin_model_fade.slv @@ -38,7 +38,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_fade_dq.slv b/libraries/render-utils/src/skin_model_fade_dq.slv index 4f8a923a03..232170d714 100644 --- a/libraries/render-utils/src/skin_model_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_fade_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_fade.vert +// skin_model_fade_dq.vert // vertex shader // // Created by Olivier Prat on 06/045/17. @@ -38,7 +38,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index 666bdf865f..b54c84e5b3 100644 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -39,7 +39,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map_dq.slv b/libraries/render-utils/src/skin_model_normal_map_dq.slv index 02b3742f6f..b34f68d291 100644 --- a/libraries/render-utils/src/skin_model_normal_map_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_dq.slv @@ -39,7 +39,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map_fade.slv b/libraries/render-utils/src/skin_model_normal_map_fade.slv index d72e47702d..0e788b81b5 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade.slv @@ -40,7 +40,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv index d6e07575b1..230077ba3b 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_normal_map.vert +// skin_model_normal_map_fade_dq.vert // vertex shader // // Created by Andrzej Kapolka on 10/29/13. @@ -40,7 +40,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/standardTransformPNTC.slv b/libraries/render-utils/src/standardTransformPNTC.slv index 0ced5ba6e2..8ec685cea0 100644 --- a/libraries/render-utils/src/standardTransformPNTC.slv +++ b/libraries/render-utils/src/standardTransformPNTC.slv @@ -24,7 +24,7 @@ out vec4 varColor; void main(void) { varTexCoord0 = inTexCoord0.st; - varColor = colorToLinearRGBA(inColor); + varColor = color_sRGBAToLinear(inColor); // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/taa.slf b/libraries/render-utils/src/taa.slf new file mode 100644 index 0000000000..8d172871d4 --- /dev/null +++ b/libraries/render-utils/src/taa.slf @@ -0,0 +1,51 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// taa.frag +// fragment shader +// +// Created by Sam Gateau on 8/14/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +<@include taa.slh@> + +in vec2 varTexCoord0; +layout(location = 0) out vec4 outFragColor; + +void main() { + vec2 fragUV = varTexCoord0; + + // Debug region before debug or fxaa region X + float distToRegionFXAA = fragUV.x - taa_getRegionFXAA().x; + if (distToRegionFXAA > 0.0) { + outFragColor = vec4(taa_evalFXAA(fragUV), 1.0); + return; + } + + vec2 fragVel = taa_fetchVelocityMapBest(fragUV).xy; + + vec3 sourceColor; + vec3 historyColor; + vec2 prevFragUV = taa_fetchSourceAndHistory(fragUV, fragVel, sourceColor, historyColor); + + vec3 nextColor = sourceColor; + + if (taa_constrainColor()) { + // clamp history to neighbourhood of current sample + historyColor = taa_evalConstrainColor(sourceColor, fragUV, fragVel, historyColor); + } + + if (taa_feedbackColor()) { + nextColor = taa_evalFeedbackColor(sourceColor, historyColor, params.blend); + } else { + nextColor = mix(historyColor, sourceColor, params.blend); + } + + outFragColor = vec4(taa_resolveColor(nextColor), 1.0); +} diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh new file mode 100644 index 0000000000..583e2978c4 --- /dev/null +++ b/libraries/render-utils/src/taa.slh @@ -0,0 +1,529 @@ +// Generated on <$_SCRIBE_DATE$> +// +// TAA.slh +// Common component needed by TemporalAntialiasing fragment shader +// +// Created by Sam Gateau on 8/17/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +<@include gpu/Color.slh@> + +uniform sampler2D depthMap; +uniform sampler2D sourceMap; +uniform sampler2D historyMap; +uniform sampler2D velocityMap; +uniform sampler2D nextMap; + +struct TAAParams +{ + float none; + float blend; + float covarianceGamma; + float debugShowVelocityThreshold; + ivec4 flags; + vec4 pixelInfo_orbZoom; + vec4 regionInfo; +}; + +layout(std140) uniform taaParamsBuffer { + TAAParams params; +}; + +#define GET_BIT(bitfield, bitIndex) bool((bitfield) & (1 << (bitIndex))) + +bool taa_isDebugEnabled() { + return GET_BIT(params.flags.x, 0); +} + +bool taa_showDebugCursor() { + return GET_BIT(params.flags.x, 1); +} + +bool taa_showClosestFragment() { + return GET_BIT(params.flags.x, 3); +} + +bool taa_constrainColor() { + return GET_BIT(params.flags.y, 1); +} + +bool taa_feedbackColor() { + return GET_BIT(params.flags.y, 4); +} + +vec2 taa_getDebugCursorTexcoord() { + return params.pixelInfo_orbZoom.xy; +} + +float taa_getDebugOrbZoom() { + return params.pixelInfo_orbZoom.z; +} + +vec2 taa_getRegionDebug() { + return params.regionInfo.xy; +} + +vec2 taa_getRegionFXAA() { + return params.regionInfo.zw; +} +#define USE_YCOCG 1 + +vec4 taa_fetchColor(sampler2D map, vec2 uv) { +#if USE_YCOCG + vec4 c = texture(map, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(map, uv); +#endif +} + +vec3 taa_resolveColor(vec3 color) { +#if USE_YCOCG + return color_YCoCgToLinear(color); +#else + return color; +#endif +} + +vec4 taa_fetchSourceMap(vec2 uv) { +#if USE_YCOCG + vec4 c = texture(sourceMap, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(sourceMap, uv); +#endif +} + +vec4 taa_fetchHistoryMap(vec2 uv) { +#if USE_YCOCG + vec4 c = texture(historyMap, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(historyMap, uv); +#endif +} + +vec4 taa_fetchNextMap(vec2 uv) { +#if USE_YCOCG + vec4 c = texture(nextMap, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(nextMap, uv); +#endif +} + +vec2 taa_fetchVelocityMap(vec2 uv) { + return texture(velocityMap, uv).xy; +} + +float taa_fetchDepth(vec2 uv) { + return -texture(depthMap, vec2(uv), 0).x; +} + + +#define ZCMP_GT(a, b) (a > b) + +vec2 taa_getImageSize() { + vec2 imageSize = getWidthHeight(0); + if (isStereo()) { + imageSize.x *= 2.0; + } + return imageSize; +} + +vec2 taa_getTexelSize() { + vec2 texelSize = getInvWidthHeight(); + if (isStereo()) { + texelSize.x *= 0.5; + } + return texelSize; +} + +vec3 taa_findClosestFragment3x3(vec2 uv) +{ + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); + + vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); + vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); + vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); + + vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); + vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); + vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); + + vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); + vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); + vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); + + vec3 dmin = dtl; + if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; + if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; + + if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; + if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; + if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; + + if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; + if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; + if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; + + return vec3(uv + dd.xy * dmin.xy, dmin.z); +} + +vec2 taa_fetchVelocityMapBest(vec2 uv) { + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); + + vec2 dtl = taa_fetchVelocityMap(uv - dv - du); + vec2 dtc = taa_fetchVelocityMap(uv - dv); + vec2 dtr = taa_fetchVelocityMap(uv - dv + du); + + vec2 dml = taa_fetchVelocityMap(uv - du); + vec2 dmc = taa_fetchVelocityMap(uv); + vec2 dmr = taa_fetchVelocityMap(uv + du); + + vec2 dbl = taa_fetchVelocityMap(uv + dv - du); + vec2 dbc = taa_fetchVelocityMap(uv + dv); + vec2 dbr = taa_fetchVelocityMap(uv + dv + du); + + vec3 best = vec3(dtl, dot(dtl,dtl)); + + float testSpeed = dot(dtc,dtc); + if (testSpeed > best.z) { best = vec3(dtc, testSpeed); } + testSpeed = dot(dtr,dtr); + if (testSpeed > best.z) { best = vec3(dtr, testSpeed); } + + testSpeed = dot(dml,dml); + if (testSpeed > best.z) { best = vec3(dml, testSpeed); } + testSpeed = dot(dmc,dmc); + if (testSpeed > best.z) { best = vec3(dmc, testSpeed); } + testSpeed = dot(dmr,dmr); + if (testSpeed > best.z) { best = vec3(dmr, testSpeed); } + + testSpeed = dot(dbl,dbl); + if (testSpeed > best.z) { best = vec3(dbl, testSpeed); } + testSpeed = dot(dbc,dbc); + if (testSpeed > best.z) { best = vec3(dbc, testSpeed); } + testSpeed = dot(dbr,dbr); + if (testSpeed > best.z) { best = vec3(dbr, testSpeed); } + + return best.xy; +} + +vec2 taa_fromFragUVToEyeUVAndSide(vec2 fragUV, out int stereoSide) { + vec2 eyeUV = fragUV; + stereoSide = 0; + if (isStereo()) { + if (eyeUV.x > 0.5) { + eyeUV.x -= 0.5; + stereoSide = 1; + } + eyeUV.x *= 2.0; + } + return eyeUV; +} + +vec2 taa_fromEyeUVToFragUV(vec2 eyeUV, int stereoSide) { + vec2 fragUV = eyeUV; + if (isStereo()) { + fragUV.x *= 0.5; + fragUV.x += stereoSide*0.5; + } + return fragUV; +} + +vec2 taa_computePrevFragAndEyeUV(vec2 fragUV, vec2 fragVelocity, out vec2 prevEyeUV) { + int stereoSide = 0; + vec2 eyeUV = taa_fromFragUVToEyeUVAndSide(fragUV, stereoSide); + prevEyeUV = eyeUV - fragVelocity; + return taa_fromEyeUVToFragUV(prevEyeUV, stereoSide); +} + +vec2 taa_fetchSourceAndHistory(vec2 fragUV, vec2 fragVelocity, out vec3 sourceColor, out vec3 historyColor) { + vec2 prevEyeUV; + vec2 prevFragUV = taa_computePrevFragAndEyeUV(fragUV, fragVelocity, prevEyeUV); + sourceColor = taa_fetchSourceMap(fragUV).xyz; + + historyColor = sourceColor; + if (!(any(lessThan(prevEyeUV, vec2(0.0))) || any(greaterThan(prevEyeUV, vec2(1.0))))) { + historyColor = taa_fetchHistoryMap(prevFragUV).xyz; + } + return prevFragUV; +} + +float Luminance(vec3 rgb) { + return rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0; +} + +#define MINMAX_3X3_ROUNDED 1 + +mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelocity) { + vec2 texelSize = taa_getTexelSize(); + + + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); + + vec3 sampleColor = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 sumSamples = sampleColor; + vec3 sumSamples2 = sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV - dv).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV - dv + du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV - du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sampleColor isn't it ? + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + dv - du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + dv).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + dv + du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + + vec3 mu = sumSamples / vec3(9.0); + vec3 sigma = sqrt(max(sumSamples2 / vec3(9.0) - mu * mu, vec3(0.0))); + + float gamma = params.covarianceGamma; + vec3 cmin = mu - gamma * sigma; + vec3 cmax = mu + gamma * sigma; + + return mat3(cmin, cmax, mu); +} + +mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVelocity, float fragZe) { + vec2 imageSize = taa_getImageSize(); + vec2 texelSize = taa_getTexelSize(); + vec3 cmin, cmax, cavg; + + #if MINMAX_3X3_ROUNDED + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); + + vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; + vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; + vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; + vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? + vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; + vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; + vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; + vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; + + cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); + cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); + + #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING + cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; + #elif + cavg = (cmin + cmax ) * 0.5; + #endif + + #if MINMAX_3X3_ROUNDED + vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); + vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); + vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; + cmin = 0.5 * (cmin + cmin5); + cmax = 0.5 * (cmax + cmax5); + cavg = 0.5 * (cavg + cavg5); + #endif + #else + const float _SubpixelThreshold = 0.5; + const float _GatherBase = 0.5; + const float _GatherSubpixelMotion = 0.1666; + + vec2 texel_vel = fragVelocity * imageSize; + float texel_vel_mag = length(texel_vel) * -fragZe; + float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); + float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; + + vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); + vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); + vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; + vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; + vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; + vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; + + cmin = min(c00, min(c10, min(c01, c11))); + cmax = max(c00, max(c10, max(c01, c11))); + cavg = (cmin + cmax ) * 0.5; + + #if USE_YCOCG || USE_CLIPPING + cavg = (c00 + c10 + c01 + c11) / 4.0; + #elif + cavg = (cmin + cmax ) * 0.5; + #endif + #endif + + // shrink chroma min-max + #if USE_YCOCG + vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); + vec2 chroma_center = sourceColor.gb; + cmin.yz = chroma_center - chroma_extent; + cmax.yz = chroma_center + chroma_extent; + cavg.yz = chroma_center; + #endif + + return mat3(cmin, cmax, cavg); +} + +//#define USE_OPTIMIZATIONS 0 + +vec3 taa_clampColor(vec3 colorMin, vec3 colorMax, vec3 colorSource, vec3 color) { + const float eps = 0.00001; + vec3 p = colorSource; + vec3 q = color; + // note: only clips towards aabb center (but fast!) + vec3 p_clip = 0.5 * (colorMax + colorMin); + vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); + + vec3 v_clip = q - p_clip; + vec3 v_unit = v_clip.xyz / e_clip; + vec3 a_unit = abs(v_unit); + float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); + + if (ma_unit > 1.0) + return p_clip + v_clip / ma_unit; + else + return q;// point inside aabb +} + +vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec3 candidateColor) { + mat3 colorMinMaxAvg; + + colorMinMaxAvg = taa_evalNeighbourColorVariance(sourceColor, sourceUV, sourceVel); + + // clamp history to neighbourhood of current sample + return taa_clampColor(colorMinMaxAvg[0], colorMinMaxAvg[1], sourceColor, candidateColor); +} + +vec3 taa_evalFeedbackColor(vec3 sourceColor, vec3 historyColor, float blendFactor) { + const float _FeedbackMin = 0.1; + const float _FeedbackMax = 0.9; + // feedback weight from unbiased luminance diff (t.lottes) + #if USE_YCOCG + float lum0 = sourceColor.r; + float lum1 = historyColor.r; + #else + float lum0 = Luminance(sourceColor.rgb); + float lum1 = Luminance(historyColor.rgb); + #endif + float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); + float unbiased_weight = 1.0 - unbiased_diff; + float unbiased_weight_sqr = unbiased_weight * unbiased_weight; + float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); + + + vec3 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor).xyz; + return nextColor; +} + + +<$declareColorWheel()$> + +vec3 taa_getVelocityColorRelative(float velocityPixLength) { + return colorRamp(velocityPixLength/params.debugShowVelocityThreshold); +} + +vec3 taa_getVelocityColorAboveThreshold(float velocityPixLength) { + return colorRamp((velocityPixLength - params.debugShowVelocityThreshold)/params.debugShowVelocityThreshold); +} + + +vec3 taa_evalFXAA(vec2 fragUV) { + + // vec2 texelSize = getInvWidthHeight(); + vec2 texelSize = taa_getTexelSize(); + + // filter width limit for dependent "two-tap" texture samples + float FXAA_SPAN_MAX = 8.0; + + // local contrast multiplier for performing AA + // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail + // see "fxaaQualityEdgeThreshold" + float FXAA_REDUCE_MUL = 1.0 / 8.0; + + // luminance threshold for processing dark colors + // see "fxaaQualityEdgeThresholdMin" + float FXAA_REDUCE_MIN = 1.0 / 128.0; + + // fetch raw RGB values for nearby locations + // sampling pattern is "five on a die" (each diagonal direction and the center) + // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed + vec3 rgbNW = texture(sourceMap, fragUV + (vec2(-1.0, -1.0) * texelSize)).xyz; + vec3 rgbNE = texture(sourceMap, fragUV + (vec2(+1.0, -1.0) * texelSize)).xyz; + vec3 rgbSW = texture(sourceMap, fragUV + (vec2(-1.0, +1.0) * texelSize)).xyz; + vec3 rgbSE = texture(sourceMap, fragUV + (vec2(+1.0, +1.0) * texelSize)).xyz; + vec3 rgbM = texture(sourceMap, fragUV).xyz; + + // convert RGB values to luminance + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot( rgbM, luma); + + // luma range of local neighborhood + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + // direction perpendicular to local luma gradient + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + // compute clamped direction offset for additional "two-tap" samples + // longer vector = blurry, shorter vector = sharp + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texelSize; + + // perform additional texture sampling perpendicular to gradient + vec3 rgbA = (1.0 / 2.0) * ( + texture(sourceMap, fragUV + dir * (1.0 / 3.0 - 0.5)).xyz + + texture(sourceMap, fragUV + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( + texture(sourceMap, fragUV + dir * (0.0 / 3.0 - 0.5)).xyz + + texture(sourceMap, fragUV + dir * (3.0 / 3.0 - 0.5)).xyz); + float lumaB = dot(rgbB, luma); + + // compare luma of new samples to the luma range of the original neighborhood + // if the new samples exceed this range, just use the first two samples instead of all four + if (lumaB < lumaMin || lumaB > lumaMax) { + return rgbA; + } else { + return rgbB; + } +} \ No newline at end of file diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf new file mode 100644 index 0000000000..aca934ca71 --- /dev/null +++ b/libraries/render-utils/src/taa_blend.slf @@ -0,0 +1,156 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// taa_blend.frag +// fragment shader +// +// Created by Sam Gateau on 8/17/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include taa.slh@> + +in vec2 varTexCoord0; +layout(location = 0) out vec4 outFragColor; + +void main(void) { + vec3 nextColor = texture(nextMap, varTexCoord0).xyz; + outFragColor = vec4(nextColor, 1.0); + + + // Pixel being shaded + vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; + + vec2 imageSize = getWidthHeight(0); + vec2 texelSize = getInvWidthHeight(); + + vec2 pixPos = varTexCoord0 * imageSize; + vec2 pixVelocity = imageSize * texture(velocityMap, varTexCoord0).xy; + float pixVelocityLength = length(pixVelocity); + vec2 velocity = pixVelocity * texelSize; + int stereoSide = 0; + vec2 prevTexCoord = taa_fromFragUVToEyeUVAndSide(varTexCoord0, stereoSide) - velocity; + prevTexCoord = taa_fromEyeUVToFragUV(prevTexCoord, stereoSide); + vec2 prevPix = prevTexCoord * imageSize; + + // Pixel Debugged + if (taa_showDebugCursor()) { + vec2 cursorUVRaw = taa_getDebugCursorTexcoord(); + vec2 cursorPosRaw = floor(cursorUVRaw * imageSize) + vec2(0.5); + vec3 cursorFrag = taa_findClosestFragment3x3(cursorUVRaw); + vec2 cursorUV = cursorUVRaw; + vec2 cursorPos = cursorUV * imageSize; + vec2 cursorVelocity = texture(velocityMap, cursorUV).xy; + vec2 cursorPrevUV = taa_fromFragUVToEyeUVAndSide(cursorUV, stereoSide) - cursorVelocity; + cursorVelocity *= imageSize; + float cursorVelocityLength = length(cursorVelocity); + vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; + + vec2 cursorToFragVec = pixPos - cursorPos; + float cursorToFragLength = length(cursorToFragVec); + + if ((cursorToFragLength <= cursorVelocityLength)) { + vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; + vec2 cursorVelocityNor = vec2(cursorVelocityDir.y, -cursorVelocityDir.x); + + if ((dot(cursorVelocityDir, cursorToFragVec) < 0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { + + vec3 speedColor = taa_getVelocityColorRelative(cursorToFragLength); + + outFragColor = vec4(speedColor, 1.0); + return; + } + } + + float tenPercentHeight = 0.1 * imageSize.y; + float centerWidth = imageSize.x * 0.5; + + //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3 * tenPercentHeight); + vec2 nextOrbPos = cursorPos; + vec2 nextOrbPosToPix = pixPos - nextOrbPos; + float nextOrbPosToPixLength = length(nextOrbPosToPix); + + vec2 prevOrbPos = nextOrbPos - cursorVelocityDir * 2.0 * tenPercentHeight; + vec2 prevOrbPosToPix = pixPos - prevOrbPos; + float prevOrbPosToPixLength = length(prevOrbPosToPix); + + float orbPixThreshold = 2.0 / taa_getDebugOrbZoom(); + + if ((prevOrbPosToPixLength < tenPercentHeight) && (cursorVelocityLength > 0.5)) { + vec2 prevOrbPosToPix_uv = cursorPrevUV + prevOrbPosToPix * texelSize / taa_getDebugOrbZoom(); + vec3 preOrbColor = vec3(0.0); + if (!(any(lessThan(prevOrbPosToPix_uv, vec2(0.0))) || any(greaterThan(prevOrbPosToPix_uv, vec2(1.0))))) { + preOrbColor = texture(historyMap, prevOrbPosToPix_uv).xyz; + } + if (prevOrbPosToPixLength < orbPixThreshold) { + preOrbColor = vec3(1.0, 0.0, 1.0); + } + float distanceToNext = length(imageSize * (cursorUV - prevOrbPosToPix_uv)); + if (distanceToNext < orbPixThreshold) { + preOrbColor = vec3(1.0, 0.5, 0.0); + } + outFragColor = vec4(preOrbColor, 1.0); + return; + } + if (nextOrbPosToPixLength < tenPercentHeight) { + vec2 nextOrbPosToPix_uv = cursorUV + nextOrbPosToPix * texelSize / taa_getDebugOrbZoom(); + vec3 nextOrbColor = vec3(0.0); + if (!(any(lessThan(nextOrbPosToPix_uv, vec2(0.0))) || any(greaterThan(nextOrbPosToPix_uv, vec2(1.0))))) { + nextOrbColor = texture(nextMap, nextOrbPosToPix_uv).xyz; + } + float distanceToPrev = length(imageSize * (cursorPrevUV - nextOrbPosToPix_uv)); + if (distanceToPrev < orbPixThreshold) { + nextOrbColor = vec3(1.0, 0.0, 1.0); + } + if (nextOrbPosToPixLength < orbPixThreshold) { + nextOrbColor = vec3(1.0, 0.5, 0.0); + } + + outFragColor = vec4(nextOrbColor, 1.0); + return; + } + } + + // Debug region before debug or fxaa region X + float distToRegionDebug = varTexCoord0.x - taa_getRegionDebug().x; + float distToRegionFXAA = varTexCoord0.x - taa_getRegionFXAA().x; + if ((distToRegionFXAA < 0.0) && (distToRegionDebug > 0.0)) { + return; + } + + // draw region splitter + if ((abs(distToRegionDebug) < getInvWidthHeight().x) || (abs(distToRegionFXAA) < getInvWidthHeight().x)) { + outFragColor.rgb = vec3(1.0, 1.0, 0.0); + return; + } + + if (distToRegionFXAA > 0.0) { + return; + } + + if (taa_showClosestFragment()) { + vec3 fragUV = taa_findClosestFragment3x3(varTexCoord0); + outFragColor = vec4((fragUV.xy - varTexCoord0) * imageSize * 0.5 + vec2(0.5), 0.0, 1.0); + return; + } + + outFragColor = vec4(nextColor, 1.0); + + vec3 prevColor = nextColor; + + if (!(any(lessThan(prevTexCoord, vec2(0.0))) || any(greaterThan(prevTexCoord, vec2(1.0))))) { + prevColor = texture(historyMap, prevTexCoord).xyz; + } + + outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0, 1)); + + if (pixVelocityLength > params.debugShowVelocityThreshold) { + vec3 speedColor = taa_getVelocityColorAboveThreshold(pixVelocityLength); + + outFragColor = vec4(0.0, 1.0, 1.0, 1.0); + } +} diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index d3d25431d0..cd171db855 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -243,7 +243,7 @@ void Font::setupGPU() { state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShapeNoAA(*state); + PrepareStencil::testMaskDrawShape(*state); _pipeline = gpu::Pipeline::create(program, state); auto transparentState = std::make_shared(); @@ -252,7 +252,7 @@ void Font::setupGPU() { transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShapeNoAA(*transparentState); + PrepareStencil::testMaskDrawShape(*transparentState); _transparentPipeline = gpu::Pipeline::create(programTransparent, transparentState); } diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf new file mode 100644 index 0000000000..22a95b55d1 --- /dev/null +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -0,0 +1,41 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/3/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +in vec2 varTexCoord0; +out vec4 outFragColor; + +uniform sampler2D depthMap; + + +void main(void) { + // Pixel being shaded + ivec2 pixelPos; + vec2 texcoordPos; + ivec4 stereoSide; + ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); + + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + + // The position of the pixel fragment in Eye space then in world space + vec3 eyePos = evalEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); + vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; + + vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; + vec4 prevClipPos = (frameTransform._projection[stereoSide.x] * vec4(prevEyePos, 1.0)); + vec2 prevUV = 0.5 * (prevClipPos.xy / prevClipPos.w) + vec2(0.5); + + //vec2 imageSize = getWidthHeight(0); + vec2 imageSize = vec2(1.0, 1.0); + outFragColor = vec4( ((texcoordPos - prevUV) * imageSize), 0.0, 0.0); +} diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 0625179a6d..4c3c52e07b 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -286,7 +286,7 @@ void BlurGaussian::run(const RenderContextPointer& renderContext, const gpu::Fra _parameters->setWidthHeight(blurredFramebuffer->getWidth(), blurredFramebuffer->getHeight(), args->isStereo()); _parameters->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, viewport)); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("BlurGaussian::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(viewport); @@ -401,7 +401,7 @@ void BlurGaussianDepthAware::run(const RenderContextPointer& renderContext, cons _parameters->setDepthPerspective(args->getViewFrustum().getProjection()[1][1]); _parameters->setLinearDepthPosFar(args->getViewFrustum().getFarClip()); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("BlurGaussianDepthAware::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(sourceViewport); diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 6a35623fa0..433851eec2 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -94,7 +94,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS std::static_pointer_cast(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawSceneOctree::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); @@ -201,7 +201,7 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawItemSelection::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index a11e9b1a88..56802a3239 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -163,7 +163,7 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const ItemBounds } // Allright, something to render let's do it - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawStatus::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 88d38d1c66..bdff97c1c1 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -143,7 +143,7 @@ void DrawLight::run(const RenderContextPointer& renderContext, const ItemBounds& RenderArgs* args = renderContext->args; // render lights - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawLight::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; renderItems(renderContext, inLights, _maxDrawn); args->_batch = nullptr; @@ -191,7 +191,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawBounds::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; // Setup projection @@ -235,7 +235,7 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons } RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawQuadVolume::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index ed052adf6e..9c5efb9fa7 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -118,9 +118,15 @@ uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) c auto numSubs = fetchMetaSubItems(subItems); for (auto id : subItems) { - auto& item = scene.getItem(id); - if (item.exist()) { - subItemBounds.emplace_back(id, item.getBound()); + // TODO: Adding an extra check here even thought we shouldn't have too. + // We have cases when the id returned by fetchMetaSubItems is not allocated + if (scene.isAllocatedID(id)) { + auto& item = scene.getItem(id); + if (item.exist()) { + subItemBounds.emplace_back(id, item.getBound()); + } else { + numSubs--; + } } else { numSubs--; } diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp index 65c0ff45b9..07f7367582 100644 --- a/libraries/render/src/render/ResampleTask.cpp +++ b/libraries/render/src/render/ResampleTask.cpp @@ -66,7 +66,7 @@ void HalfDownsample::run(const RenderContextPointer& renderContext, const gpu::F const auto bufferSize = resampledFrameBuffer->getSize(); glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("HalfDownsample::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(resampledFrameBuffer); diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index aaf1d2f1c8..0b4ee2f11a 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -435,44 +435,47 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti void Scene::resetHighlights(const Transaction::HighlightResets& transactions) { auto outlineStage = getStage(HighlightStage::getName()); + if (outlineStage) { + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& newStyle = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - for (auto& transaction : transactions) { - const auto& selectionName = std::get<0>(transaction); - const auto& newStyle = std::get<1>(transaction); - auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - - if (HighlightStage::isIndexInvalid(outlineId)) { - outlineStage->addHighlight(selectionName, newStyle); - } else { - outlineStage->editHighlight(outlineId)._style = newStyle; + if (HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->addHighlight(selectionName, newStyle); + } else { + outlineStage->editHighlight(outlineId)._style = newStyle; + } } } } void Scene::removeHighlights(const Transaction::HighlightRemoves& transactions) { auto outlineStage = getStage(HighlightStage::getName()); + if (outlineStage) { + for (auto& selectionName : transactions) { + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - for (auto& selectionName : transactions) { - auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - - if (!HighlightStage::isIndexInvalid(outlineId)) { - outlineStage->removeHighlight(outlineId); + if (!HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->removeHighlight(outlineId); + } } } } void Scene::queryHighlights(const Transaction::HighlightQueries& transactions) { auto outlineStage = getStage(HighlightStage::getName()); + if (outlineStage) { + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& func = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - for (auto& transaction : transactions) { - const auto& selectionName = std::get<0>(transaction); - const auto& func = std::get<1>(transaction); - auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - - if (!HighlightStage::isIndexInvalid(outlineId)) { - func(&outlineStage->editHighlight(outlineId)._style); - } else { - func(nullptr); + if (!HighlightStage::isIndexInvalid(outlineId)) { + func(&outlineStage->editHighlight(outlineId)._style); + } else { + func(nullptr); + } } } } diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 68c24ecc33..4096de9434 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -50,7 +50,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { jsCallback(handler, url, hash); }); - connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) { + connect(upload, &AssetUpload::finished, upload, [deferred](AssetUpload* upload, const QString& hash) { // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly) Q_ASSERT(QThread::currentThread() == upload->thread()); deferred->resolve({ @@ -70,7 +70,7 @@ void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValu jsCallback(handler, error, result); }); - connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [this, deferred](SetMappingRequest* request) { + connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [deferred](SetMappingRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); // we are now on the "Resource Manager" thread QString error = request->getErrorString(); @@ -102,7 +102,7 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb jsCallback(handler, result.value("data").toString(), { { "errorMessage", error } }); }); - connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { + connect(assetRequest, &AssetRequest::finished, assetRequest, [deferred](AssetRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); // we are now on the "Resource Manager" thread Q_ASSERT(request->getState() == AssetRequest::Finished); @@ -128,7 +128,7 @@ void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScri Promise deferred = jsPromiseReady(makePromise(__FUNCTION__), thisObject(), callback); - connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) { + connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [deferred](SetBakingEnabledRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); // we are now on the "Resource Manager" thread diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 28bf5ed163..f248c20d41 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -60,8 +60,18 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound } } -void AudioScriptingInterface::setStereoInput(bool stereo) { +bool AudioScriptingInterface::setStereoInput(bool stereo) { + bool stereoInputChanged = false; if (_localAudioInterface) { - _localAudioInterface->setIsStereoInput(stereo); + stereoInputChanged = _localAudioInterface->setIsStereoInput(stereo); } + return stereoInputChanged; +} + +bool AudioScriptingInterface::isStereoInput() { + bool stereoEnabled = false; + if (_localAudioInterface) { + stereoEnabled = _localAudioInterface->isStereoInput(); + } + return stereoEnabled; } diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 23a0861acd..be2b4ebc8c 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -35,7 +35,8 @@ protected: // FIXME: there is no way to play a positionless sound Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position); - Q_INVOKABLE void setStereoInput(bool stereo); + Q_INVOKABLE bool setStereoInput(bool stereo); + Q_INVOKABLE bool isStereoInput(); signals: void mutedByMixer(); /// the client has been muted by the mixer diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 1472e53045..3bf044fd8b 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -68,6 +68,10 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool if (path.contains("vr.google.com/downloads")) { isZip = true; } + if (!hasModel(fileList)) { + isZip = false; + } + emit unzipResult(path, fileList, autoAdd, isZip, isBlocks); } @@ -107,6 +111,15 @@ bool FileScriptingInterface::isTempDir(QString tempDir) { return (testContainer == tempContainer); } +bool FileScriptingInterface::hasModel(QStringList fileList) { + for (int i = 0; i < fileList.size(); i++) { + if (fileList.at(i).toLower().contains(".fbx") || fileList.at(i).toLower().contains(".obj")) { + return true; + } + } + return false; +} + QString FileScriptingInterface::getTempDir() { QTemporaryDir dir; dir.setAutoRemove(false); diff --git a/libraries/script-engine/src/FileScriptingInterface.h b/libraries/script-engine/src/FileScriptingInterface.h index e4c27dbf7f..5cbe417130 100644 --- a/libraries/script-engine/src/FileScriptingInterface.h +++ b/libraries/script-engine/src/FileScriptingInterface.h @@ -32,6 +32,7 @@ signals: private: bool isTempDir(QString tempDir); + bool hasModel(QStringList fileList); QStringList unzipFile(QString path, QString tempDir); void recursiveFileScan(QFileInfo file, QString* dirName); void downloadZip(QString path, const QString link); diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 23e8e3b896..68b4dcd408 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -35,7 +35,7 @@ * of gimbal lock. * @namespace Quat * @variation 0 - * @property IDENTITY {Quat} The identity rotation, i.e., no rotation. + * @property IDENTITY {Quat} The identity rotation, i.e., no rotation. Its value is { x: 0, y: 0, z: 0, w: 1 }. * @example Print the IDENTITY value. * print(JSON.stringify(Quat.IDENTITY)); // { x: 0, y: 0, z: 0, w: 1 } * print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY))); // { x: 0, y: 0, z: 0 } diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 1e1cd917a3..7ed0fd9e8b 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -25,27 +25,36 @@ * A 2-dimensional vector. * * @typedef {object} Vec2 - * @property {float} x X-coordinate of the vector. - * @property {float} y Y-coordinate of the vector. + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. */ /**jsdoc * A 3-dimensional vector. * * @typedef {object} Vec3 - * @property {float} x X-coordinate of the vector. - * @property {float} y Y-coordinate of the vector. - * @property {float} z Z-coordinate of the vector. + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. + * @property {number} z - Z-coordinate of the vector. */ /**jsdoc * A 4-dimensional vector. * * @typedef {object} Vec4 - * @property {float} x X-coordinate of the vector. - * @property {float} y Y-coordinate of the vector. - * @property {float} z Z-coordinate of the vector. - * @property {float} w W-coordinate of the vector. + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. + * @property {number} z - Z-coordinate of the vector. + * @property {number} w - W-coordinate of the vector. + */ + +/**jsdoc + * A color vector. + * + * @typedef {object} Vec3Color + * @property {number} x - Red component value. Integer in the range 0 - 255. + * @property {number} y - Green component value. Integer in the range 0 - 255. + * @property {number} z - Blue component value. Integer in the range 0 - 255. */ /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index cea0a83d52..cbf3c1b785 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -287,6 +287,15 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct return false; } +bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const { + glm::vec3 localCenter = calcCenter() - origin; + float distance = glm::dot(localCenter, direction); + const float ONE_OVER_TWO_SQUARED = 0.25f; + float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); + return (glm::length2(localCenter) < radiusSquared + || (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared)); +} + bool AABox::touchesSphere(const glm::vec3& center, float radius) const { // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO); diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index 24485eaad6..cf79cf9d04 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -71,6 +71,7 @@ public: bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; + bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const; bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 4942c63e27..930da6a494 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -59,5 +59,7 @@ static const float MIN_AVATAR_SCALE = 0.005f; static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters +static const float AVATAR_WALK_SPEED_SCALAR = 1.0f; +static const float AVATAR_SPRINT_SPEED_SCALAR = 3.0f; #endif // hifi_AvatarConstants_h diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index cb3c0d07b2..49927a325b 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -92,12 +91,13 @@ void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { void LogHandler::flushRepeatedMessages() { QMutexLocker lock(&_mutex); - QHash::iterator message = _repeatMessageCountHash.begin(); - while (message != _repeatMessageCountHash.end()) { + for(auto& message: _repeatedMessages) { - if (message.value() > 0) { + if (message.messageCount > 1) { QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") - .arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key())); + .arg(message.messageCount - 1) + .arg(message.regexp.pattern()) + .arg(message.lastMessage); QMessageLogContext emptyContext; lock.unlock(); @@ -105,8 +105,7 @@ void LogHandler::flushRepeatedMessages() { lock.relock(); } - _lastRepeatedMessage.remove(message.key()); - message = _repeatMessageCountHash.erase(message); + message.messageCount = 0; } } @@ -118,36 +117,24 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont if (type == LogDebug) { // for debug messages, check if this matches any of our regexes for repeated log messages - foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) { - QRegExp repeatRegex(regexString); - if (repeatRegex.indexIn(message) != -1) { - - if (!_repeatMessageCountHash.contains(regexString)) { - // we have a match but didn't have this yet - output the first one - _repeatMessageCountHash[regexString] = 0; - - // break the foreach so we output the first match + for (auto& repeatRegex : _repeatedMessages) { + if (repeatRegex.regexp.indexIn(message) != -1) { + // If we've printed the first one then return out. + if (repeatRegex.messageCount++ == 0) { break; - } else { - // we have a match - add 1 to the count of repeats for this message and set this as the last repeated message - _repeatMessageCountHash[regexString] += 1; - _lastRepeatedMessage[regexString] = message; - - // return out, we're not printing this one - return QString(); } + repeatRegex.lastMessage = message; + return QString(); } } } + if (type == LogDebug) { // see if this message is one we should only print once - foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) { - QRegExp onlyOnceRegex(regexString); - if (onlyOnceRegex.indexIn(message) != -1) { - if (!_onlyOnceMessageCountHash.contains(message)) { + for (auto& onceOnly : _onetimeMessages) { + if (onceOnly.regexp.indexIn(message) != -1) { + if (onceOnly.messageCount++ == 0) { // we have a match and haven't yet printed this message. - _onlyOnceMessageCountHash[message] = 1; - // break the foreach so we output the first match break; } else { // We've already printed this message, don't print it again. @@ -217,10 +204,16 @@ const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) { QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher"); QMutexLocker lock(&_mutex); - return *_repeatedMessageRegexes.insert(regexString); + RepeatedMessage repeatRecord; + repeatRecord.regexp = QRegExp(regexString); + _repeatedMessages.push_back(repeatRecord); + return regexString; } const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) { QMutexLocker lock(&_mutex); - return *_onlyOnceMessageRegexes.insert(regexString); + OnceOnlyMessage onetimeMessage; + onetimeMessage.regexp = QRegExp(regexString); + _onetimeMessages.push_back(onetimeMessage); + return regexString; } diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index ea961a8d4c..2e64f16c1e 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -13,11 +13,12 @@ #ifndef hifi_LogHandler_h #define hifi_LogHandler_h -#include #include -#include #include +#include #include +#include +#include const int VERBOSE_LOG_INTERVAL_SECONDS = 5; @@ -66,12 +67,19 @@ private: bool _shouldOutputProcessID { false }; bool _shouldOutputThreadID { false }; bool _shouldDisplayMilliseconds { false }; - QSet _repeatedMessageRegexes; - QHash _repeatMessageCountHash; - QHash _lastRepeatedMessage; - QSet _onlyOnceMessageRegexes; - QHash _onlyOnceMessageCountHash; + struct RepeatedMessage { + QRegExp regexp; + int messageCount { 0 }; + QString lastMessage; + }; + std::vector _repeatedMessages; + + struct OnceOnlyMessage { + QRegExp regexp; + int messageCount { 0 }; + }; + std::vector _onetimeMessages; static QMutex _mutex; }; diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 1a0a19bf44..ae4338be6f 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -46,7 +46,7 @@ void printOctalCode(const unsigned char* octalCode) { } char sectionValue(const unsigned char* startByte, char startIndexInByte) { - char rightShift = 8 - startIndexInByte - 3; + int8_t rightShift = 8 - startIndexInByte - 3; if (rightShift < 0) { return ((startByte[0] << -rightShift) & 7) + (startByte[1] >> (8 + rightShift)); @@ -73,7 +73,7 @@ int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsi return sectionValue(descendantOctalCode + 1 + (branchStartBit / 8), branchStartBit % 8); } -unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber) { +unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber) { // find the length (in number of three bit code sequences) // in the parent @@ -111,7 +111,7 @@ unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNu // calculate the amount of left shift required // this will be -1 or -2 if there's wrap - char leftShift = 8 - (startBit % 8) - 3; + int8_t leftShift = 8 - (startBit % 8) - 3; if (leftShift < 0) { // we have a wrap-around to accomodate diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index a0d86f32d2..89c5e6d74e 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -30,7 +30,7 @@ using OctalCodePtrList = std::vector; void printOctalCode(const unsigned char* octalCode); size_t bytesRequiredForCodeLength(unsigned char threeBitCodes); int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsigned char* descendantOctalCode); -unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber); +unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber); const int OVERFLOWED_OCTCODE_BUFFER = -1; const int UNKNOWN_OCTCODE_LENGTH = -2; diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index ac402a549f..b5c76257ef 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -81,7 +81,7 @@ const QString& PathUtils::resourcesPath() { #else staticResourcePath = ":/"; #endif - + #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) if (USE_SOURCE_TREE_RESOURCES()) { // For dev builds, optionally load content from the Git source tree @@ -120,6 +120,31 @@ QUrl PathUtils::resourcesUrl(const QString& relativeUrl) { return QUrl(resourcesUrl() + relativeUrl); } +QUrl PathUtils::expandToLocalDataAbsolutePath(const QUrl& fileUrl) { + QString path = fileUrl.path(); + + if (path.startsWith("/~/")) { + // this results in a qrc:// url... + // return resourcesUrl(path.mid(3)); + +#ifdef Q_OS_MAC + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; +#elif defined (ANDROID) + static const QString staticResourcePath = + QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources/"; +#else + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; +#endif + path.replace(0, 3, staticResourcePath); + QUrl expandedURL = QUrl::fromLocalFile(path); + return expandedURL; + } + + QUrl::fromLocalFile(resourcesPath()).toString(); + + return fileUrl; +} + const QString& PathUtils::qmlBaseUrl() { static const QString staticResourcePath = resourcesUrl() + "qml/"; return staticResourcePath; diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index ec71ccee6a..d879ac968d 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -37,6 +37,7 @@ public: static QUrl resourcesUrl(const QString& relative); static const QString& resourcesPath(); static const QString& qmlBaseUrl(); + static QUrl expandToLocalDataAbsolutePath(const QUrl& fileUrl); static QUrl qmlUrl(const QString& relative); #ifdef DEV_BUILD static const QString& projectRootPath(); diff --git a/libraries/shared/src/PhysicsCollisionGroups.h b/libraries/shared/src/PhysicsCollisionGroups.h index 794f338dc5..edec61dc67 100644 --- a/libraries/shared/src/PhysicsCollisionGroups.h +++ b/libraries/shared/src/PhysicsCollisionGroups.h @@ -64,6 +64,25 @@ const int16_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT // COLLISIONLESS gets an empty mask. const int16_t BULLET_COLLISION_MASK_COLLISIONLESS = 0; +/**jsdoc + *

An entity may collide with the following types of items:

+ * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
1Static entities — non-dynamic entities with no velocity.
2Dynamic entities — entities that have their dynamic property set to + * true.
4Kinematic entities — non-dynamic entities with velocity.
8My avatar.
16Other avatars.
+ *

The values for the collision types that are enabled are added together to give the CollisionMask value. For example, a + * value of 31 means that an entity will collide with all item types.

+ * @typedef {number} Entities.CollisionMask + */ // The USER collision groups are exposed to script and can be used to generate per-object collision masks. // They are not necessarily the same as the BULLET_COLLISION_GROUPS, but we start them off with matching numbers. diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 7b455beae5..a3d312b9c1 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -689,6 +689,15 @@ QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { return object; } +/**jsdoc + * An axis-aligned cube, defined as the bottom right near (minimum axes values) corner of the cube plus the dimension of its + * sides. + * @typedef {object} AACube + * @property {number} x - X coordinate of the brn corner of the cube. + * @property {number} y - Y coordinate of the brn corner of the cube. + * @property {number} z - Z coordinate of the brn corner of the cube. + * @property {number} scale - The dimensions of each side of the cube. + */ QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube) { QScriptValue obj = engine->newObject(); const glm::vec3& corner = aaCube.getCorner(); @@ -765,6 +774,15 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { } } +/**jsdoc + * @typedef {object} Collision + * @property {ContactEventType} type - The contact type of the collision event. + * @property {Uuid} idA - The ID of one of the entities in the collision. + * @property {Uuid} idB - The ID of the other of the entities in the collision. + * @property {Vec3} penetration - The amount of penetration between the two entities. + * @property {Vec3} contactPoint - The point of contact. + * @property {Vec3} velocityChange - The change in relative velocity of the two entities, in m/s. + */ QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) { QScriptValue obj = engine->newObject(); obj.setProperty("type", collision.type); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 4dbbd190ff..78e368748b 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -140,7 +140,7 @@ public: * * @typedef {object} PickRay * @property {Vec3} origin - The starting position of the PickRay. - * @property {Quat} direction - The direction that the PickRay travels. + * @property {Vec3} direction - The direction that the PickRay travels. */ class PickRay : public MathPick { public: @@ -265,6 +265,20 @@ namespace std { }; } +/**jsdoc + *

The type of a collision contact event. + * + * + * + * + * + * + * + * + * + *
ValueDescription
0Start of the collision.
1Continuation of the collision.
2End of the collision.
+ * @typedef {number} ContactEventType + */ enum ContactEventType { CONTACT_EVENT_TYPE_START, CONTACT_EVENT_TYPE_CONTINUE, @@ -328,14 +342,34 @@ namespace graphics { using MeshPointer = std::shared_ptr; - +/**jsdoc + * A handle for a mesh in an entity, such as returned by {@link Entities.getMeshes}. + * @class MeshProxy + * @deprecated Use the {@link Graphics} API instead. + */ class MeshProxy : public QObject { Q_OBJECT public: virtual MeshPointer getMeshPointer() const = 0; + + /**jsdoc + * Get the number of vertices in the mesh. + * @function MeshProxy#getNumVertices + * @returns {number} Integer number of vertices in the mesh. + * @deprecated Use the {@link Graphics} API instead. + */ Q_INVOKABLE virtual int getNumVertices() const = 0; - Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; + + /**jsdoc + * Get the position of a vertex in the mesh. + * @function MeshProxy#getPos + * @param {number} index - Integer index of the mesh vertex. + * @returns {Vec3} Local position of the vertex relative to the mesh. + * @deprecated Use the {@link Graphics} API instead. + */ + Q_INVOKABLE virtual glm::vec3 getPos(int index) const = 0; + Q_INVOKABLE virtual glm::vec3 getPos3(int index) const { return getPos(index); } // deprecated }; Q_DECLARE_METATYPE(MeshProxy*); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 8cdc4bcf14..968292da87 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -1,6 +1,6 @@ // // ShapeInfo.cpp -// libraries/physics/src +// libraries/shared/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. @@ -15,6 +15,33 @@ #include "NumericalConstants.h" // for MILLIMETERS_PER_METER +/**jsdoc + *

A ShapeType defines the shape used for collisions or zones.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"none"No shape.
"box"A cube.
"sphere"A sphere.
"capsule-x"A capsule (cylinder with spherical ends) oriented on the x-axis.
"capsule-y"A capsule (cylinder with spherical ends) oriented on the y-axis.
"capsule-z"A capsule (cylinder with spherical ends) oriented on the z-axis.
"cylinder-x"A cylinder oriented on the x-axis.
"cylinder-y"A cylinder oriented on the y-axis.
"cylinder-z"A cylinder oriented on the z-axis.
"hull"Not used.
"compound"A compound convex hull specified in an OBJ file.
"simple-hull"A convex hull automatically generated from the model.
"simple-compound"A compound convex hull automatically generated from the model, using + * sub-meshes.
"static-mesh"The exact shape of the model.
"plane"A plane.
+ * @typedef {string} ShapeType + */ // Originally within EntityItemProperties.cpp const char* shapeTypeNames[] = { "none", diff --git a/libraries/shared/src/shared/Shapes.cpp b/libraries/shared/src/shared/Shapes.cpp index dabdf21202..afcf956165 100644 --- a/libraries/shared/src/shared/Shapes.cpp +++ b/libraries/shared/src/shared/Shapes.cpp @@ -8,8 +8,11 @@ #include "Shapes.h" +#include "qmath.h" + namespace geometry { +using glm::vec2; using glm::vec3; // The golden ratio @@ -19,8 +22,8 @@ Solid<3> tesselate(const Solid<3>& solid_, int count) { Solid<3> solid = solid_; float length = glm::length(solid.vertices[0]); for (int i = 0; i < count; ++i) { - Solid<3> result { solid.vertices, {} }; - result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3); + Solid<3> result { solid.vertices, {}, {} }; + result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 6); for (size_t f = 0; f < solid.faces.size(); ++f) { Index baseVertex = (Index)result.vertices.size(); const Face<3>& oldFace = solid.faces[f]; @@ -30,13 +33,26 @@ Solid<3> tesselate(const Solid<3>& solid_, int count) { vec3 ab = glm::normalize(a + b) * length; vec3 bc = glm::normalize(b + c) * length; vec3 ca = glm::normalize(c + a) * length; + + result.vertices.push_back(a); + result.vertices.push_back(ab); + result.vertices.push_back(ca); + result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } }); + + result.vertices.push_back(ab); + result.vertices.push_back(b); + result.vertices.push_back(bc); + result.faces.push_back(Face<3>{ { baseVertex + 3, baseVertex + 4, baseVertex + 5 } }); + + result.vertices.push_back(bc); + result.vertices.push_back(c); + result.vertices.push_back(ca); + result.faces.push_back(Face<3>{ { baseVertex + 6, baseVertex + 7, baseVertex + 8 } }); + result.vertices.push_back(ab); result.vertices.push_back(bc); result.vertices.push_back(ca); - result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } }); - result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } }); - result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } }); - result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex + 9, baseVertex + 10, baseVertex + 11 } }); } solid = result; } @@ -50,6 +66,10 @@ const Solid<3>& tetrahedron() { static const auto D = vec3(-1, -1, 1); static const Solid<3> TETRAHEDRON = Solid<3>{ { A, B, C, D }, + { vec2(0.75f, 0.5f), vec2(0.5f, 0.0f), vec2(0.25f, 0.5f), + vec2(0.5f, 1.0f), vec2(1.0f, 1.0f), vec2(0.75f, 0.5f), + vec2(0.25f, 0.5f), vec2(0.5f, 1.0f), vec2(0.75f, 0.5f), + vec2(0.25f, 0.5f), vec2(0.0f, 1.0f), vec2(0.5f, 1.0f) }, FaceVector<3>{ Face<3> { { 0, 1, 2 } }, Face<3> { { 3, 1, 0 } }, @@ -65,8 +85,15 @@ const Solid<4>& cube() { static const auto B = vec3(-1, 1, 1); static const auto C = vec3(-1, 1, -1); static const auto D = vec3(1, 1, -1); + static const float THIRD = 1.0f / 3.0f; static const Solid<4> CUBE = Solid<4>{ { A, B, C, D, -A, -B, -C, -D }, + { vec2(0.5f, 0.0f), vec2(0.25f, 0.0f), vec2(0.25f, THIRD), vec2(0.5f, THIRD), + vec2(0.5f, THIRD), vec2(0.25f, THIRD), vec2(0.25f, 2.0f * THIRD), vec2(0.5f, 2.0f * THIRD), + vec2(0.25f, THIRD), vec2(0.0f, THIRD), vec2(0.0f, 2.0f * THIRD), vec2(0.25f, 2.0f * THIRD), + vec2(1.0f, THIRD), vec2(0.75f, THIRD), vec2(0.75f, 2.0f * THIRD), vec2(1.0f, 2.0f * THIRD), + vec2(0.75f, THIRD), vec2(0.5f, THIRD), vec2(0.5f, 2.0f * THIRD), vec2(0.75f, 2.0f * THIRD), + vec2(0.25f, 1.0f), vec2(0.5f, 1.0f), vec2(0.5f, 2.0f * THIRD), vec2(0.25f, 2.0f * THIRD) }, FaceVector<4>{ Face<4> { { 3, 2, 1, 0 } }, Face<4> { { 0, 1, 7, 6 } }, @@ -86,8 +113,18 @@ const Solid<3>& octahedron() { static const auto D = vec3(0, 0, -1); static const auto E = vec3(1, 0, 0); static const auto F = vec3(-1, 0, 0); + static const float THIRD = 1.0f / 3.0f; + static const float SEVENTH = 1.0f / 7.0f; static const Solid<3> OCTAHEDRON = Solid<3>{ { A, B, C, D, E, F}, + { vec2(2.0f * SEVENTH, THIRD), vec2(SEVENTH, 2.0f * THIRD), vec2(3.0f * SEVENTH, 2.0f * THIRD), + vec2(2.0f * SEVENTH, THIRD), vec2(3.0f * SEVENTH, 2.0f * THIRD), vec2(4.0f * SEVENTH, THIRD), + vec2(5.0f * SEVENTH, 0.0f), vec2(4.0f * SEVENTH, THIRD), vec2(6.0f * SEVENTH, THIRD), + vec2(2.0f * SEVENTH, THIRD), vec2(0.0f, THIRD), vec2(1.0f * SEVENTH, 2.0f * THIRD), + vec2(2.0f * SEVENTH, 1.0f), vec2(3.0f * SEVENTH, 2.0f * THIRD), vec2(1.0f * SEVENTH, 2.0f * THIRD), + vec2(5.0f * SEVENTH, 2.0f * THIRD), vec2(4.0f * SEVENTH, THIRD), vec2(3.0f * SEVENTH, 2.0f * THIRD), + vec2(5.0f * SEVENTH, 2.0f * THIRD), vec2(6.0f * SEVENTH, THIRD), vec2(4.0f * SEVENTH, THIRD), + vec2(5.0f * SEVENTH, 2.0f * THIRD), vec2(1.0f, 2.0f * THIRD), vec2(6.0f * SEVENTH, THIRD) }, FaceVector<3> { Face<3> { { 0, 2, 4, } }, Face<3> { { 0, 4, 3, } }, @@ -116,11 +153,52 @@ const Solid<5>& dodecahedron() { static const vec3 I = vec3(0, -IP, P); static const vec3 J = vec3(P, 0, IP); + + /* _ + / \ | + / \ y2 + / \ | + / \ _ + \ / | + \ / y1 + \ / | + ___________ _ + |x3|- - x1 - -||x3| + |- - - - x2- - - -| + */ + + // x1, x2, and x3 are the solutions to the following system of equations: + // 1 = 3 * x1 + 3 * x2 + x3 + // x1 + 2 * x3 = (golden ratio) * x1 + // x2 = x1 + 2 * x3 + static const float x1 = 4.0f / (17.0f + 7.0f * sqrtf(5.0f)); + static const float x2 = (1.0f / 11.0f) * (5.0f * sqrtf(5.0f) - 9.0f); + static const float x2_2 = x2 / 2.0f; + static const float x3 = (1.0f / 11.0f) * (6.0f * sqrtf(5.0f) - 13.0f); + // y1 and y2 are the solutions to the following system of equations (x is the sidelength, but is different than x1 because the scale in the y direction is different): + // 1 = 3 * y1 + 2 * y2 + // y1 = sin(108 deg) * x + // y1 + y2 = x * sqrtf(5 + 2 * sqrtf(5)) / 2 + static const float y1 = sqrtf(2.0f * (5.0f + sqrtf(5.0f))) / (sqrtf(2.0f * (5.0f + sqrtf(5.0f))) + 4.0f * sqrtf(5.0f + 2.0f * sqrtf(5.0f))); + static const float y2 = -(sqrtf(2.0f * (5.0f + sqrtf(5.0f))) - 2.0f * sqrtf(5.0f + 2.0f * sqrtf(5.0f))) / (sqrtf(2.0f * (5.0f + sqrtf(5.0f))) + 4.0f * sqrtf(5.0f + 2.0f * sqrtf(5.0f))); + static const Solid<5> DODECAHEDRON = Solid<5>{ { A, B, C, D, E, F, G, H, I, J, -A, -B, -C, -D, -E, -F, -G, -H, -I, -J, }, + { vec2(x1 + x2_2, 0.0f), vec2(x2_2, 0.0f), vec2(x2_2 - x3, y1), vec2(x3 + x1, y1 + y2), vec2(x3 + x1 + x2_2, y1), + vec2(1.0f - (x2 - x3 + x2_2), 0.0f), vec2(1.0f - (x2 + x1 + x3), y2), vec2(1.0f - (x2 + x1), y1 + y2), vec2(1.0f - x2, y1 + y2), vec2(1.0f - (x2 - x3), y2), + vec2(1.0f - x2_2, y1), vec2(1.0f - x2, y1 + y2), vec2(1.0f - (x3 + x1), y1 + y2 + y1), vec2(1.0f - x3, y1 + y2 + y1), vec2(1.0f, y1 + y2), + vec2(x3, y1 + y2), vec2(0.0f, y1 + y2 + y1), vec2(x2_2, y1 + y2 + y1 + y2), vec2(x2, y1 + y2 + y1), vec2(x1 + x3, y1 + y2), + vec2(x3 + x1, y1 + y2), vec2(x2, y1 + y2 + y1), vec2(x2 + x1, y1 + y2 + y1), vec2(x3 + x1 + x2, y1 + y2), vec2(x3 + x1 + x2_2, y1), + vec2(x3 + x1 + x2_2, y1), vec2(x3 + x1 + x2, y1 + y2), vec2(x3 + x1 + x2 + x2_2, y1), vec2(x1 + x2 + x2_2, 0.0f), vec2(x3 + x1 + x2_2 + x3, 0.0f), + vec2(1.0f - (x3 + x1 + x2_2), 1.0f - y1), vec2(1.0f - (x3 + x1 + x2), 1.0f - (y1 + y2)), vec2(1.0f - (x3 + x1 + x2 + x2_2), 1.0f - y1), vec2(1.0f - (x1 + x2 + x2_2), 1.0f), vec2(1.0f - (x3 + x1 + x2_2 + x3), 1.0f), + vec2(x2 + x1 + x3, 1.0f - y2), vec2(x2 + x1, 1.0f - (y1 + y2)), vec2(x2, 1.0f - (y1 + y2)), vec2(x2 - x3, 1.0f - y2), vec2(x2 - x3 + x2_2, 1.0f), + vec2(x2 + x1 + x2, y1 + y2 + y1), vec2(x3 + x1 + x2 + x1, y1 + y2), vec2(x3 + x1 + x2, y1 + y2), vec2(x2 + x1, y1 + y2 + y1), vec2(x2 + x1 + x2_2, y1 + y2 + y1 + y2), + vec2(1.0f - (x3 + x1 + x2), y1 + y2 + y1), vec2(1.0f - (x2 + x1), y1 + y2), vec2(1.0f - (x2 + x1 + x2_2), y1), vec2(1.0f - (x2 + x1 + x2), y1 + y2), vec2(1.0f - (x3 + x1 + x2 + x1), y1 + y2 + y1), + vec2(1.0f - (x3 + x1 + x2_2), y1 + y2 + y1 + y2), vec2(1.0f - (x3 + x1), y1 + y2 + y1), vec2(1.0f - x2, y1 + y2), vec2(1.0f - (x2 + x1), y2 + y1), vec2(1.0f - (x3 + x1 + x2), y1 + y2 + y1), + vec2(1.0f - (x1 + x2_2), 1.0f), vec2(1.0f - x2_2, 1.0f), vec2(1.0f - (x2_2 - x3), 1.0f - y1), vec2(1.0f - (x1 + x3), 1.0f - (y1 + y2)), vec2(1.0f - (x3 + x1 + x2_2), 1.0f - y1) }, FaceVector<5> { Face<5> { { 0, 1, 2, 3, 4 } }, Face<5> { { 0, 5, 18, 6, 1 } }, @@ -148,12 +226,33 @@ const Solid<3>& icosahedron() { static const auto D = vec3(P, 0, N); static const auto E = vec3(P, 0, -N); static const auto F = vec3(0, N, -P); - + static const float THIRD = 1.0f / 3.0f; + static const float ELEVENTH = 1.0f / 11.0f; static const Solid<3> ICOSAHEDRON = Solid<3> { { A, B, C, D, E, F, -A, -B, -C, -D, -E, -F, }, + { vec2(3.0f * ELEVENTH, 0.0f), vec2(2.0f * ELEVENTH, THIRD), vec2(4.0f * ELEVENTH, THIRD), + vec2(2.0f * ELEVENTH, THIRD), vec2(3.0f * ELEVENTH, 2.0f * THIRD), vec2(4.0f * ELEVENTH, THIRD), + vec2(3.0f * ELEVENTH, 2.0f * THIRD), vec2(5.0f * ELEVENTH, 2.0f * THIRD), vec2(4.0f * ELEVENTH, THIRD), + vec2(5.0f * ELEVENTH, 2.0f * THIRD), vec2(6.0f * ELEVENTH, THIRD), vec2(4.0f * ELEVENTH, THIRD), + vec2(6.0f * ELEVENTH, THIRD), vec2(5.0f * ELEVENTH, 0.0f), vec2(4.0f * ELEVENTH, THIRD), + vec2(1.0f * ELEVENTH, 0.0f), vec2(0.0f, THIRD), vec2(2.0f * ELEVENTH, THIRD), + vec2(1.0f * ELEVENTH, 2.0f * THIRD), vec2(2.0f * ELEVENTH, THIRD), vec2(0.0f, THIRD), + vec2(2.0f * ELEVENTH, THIRD), vec2(1.0f * ELEVENTH, 2.0f * THIRD), vec2(3.0f * ELEVENTH, 2.0f * THIRD), + vec2(2.0f * ELEVENTH, 1.0f), vec2(3.0f * ELEVENTH, 2.0f * THIRD), vec2(1.0f * ELEVENTH, 2.0f * THIRD), + vec2(3.0f * ELEVENTH, 2.0f * THIRD), vec2(4.0f * ELEVENTH, 1.0f), vec2(5.0f * ELEVENTH, 2.0f * THIRD), + vec2(7.0f * ELEVENTH, 2.0f * THIRD), vec2(5.0f * ELEVENTH, 2.0f * THIRD), vec2(6.0f * ELEVENTH, 1.0f), + vec2(5.0f * ELEVENTH, 2.0f * THIRD), vec2(7.0f * ELEVENTH, 2.0f * THIRD), vec2(6.0f * ELEVENTH, THIRD), + vec2(8.0f * ELEVENTH, THIRD), vec2(6.0f * ELEVENTH, THIRD), vec2(7.0f * ELEVENTH, 2.0f * THIRD), + vec2(6.0f * ELEVENTH, THIRD), vec2(8.0f * ELEVENTH, THIRD), vec2(7.0f * ELEVENTH, 0.0f), + vec2(10.0f * ELEVENTH, THIRD), vec2(9.0f * ELEVENTH, 0.0f), vec2(8.0f * ELEVENTH, THIRD), + vec2(7.0f * ELEVENTH, 2.0f * THIRD), vec2(8.0f * ELEVENTH, 1.0f), vec2(9.0f * ELEVENTH, 2.0f * THIRD), + vec2(8.0f * ELEVENTH, THIRD), vec2(7.0f * ELEVENTH, 2.0f * THIRD), vec2(9.0f * ELEVENTH, 2.0f * THIRD), + vec2(10.0f * ELEVENTH, THIRD), vec2(8.0f * ELEVENTH, THIRD), vec2(9.0f * ELEVENTH, 2.0f * THIRD), + vec2(1.0f, 2.0f * THIRD), vec2(10.0f * ELEVENTH, THIRD), vec2(9.0f * ELEVENTH, 2.0f * THIRD), + vec2(10.0f * ELEVENTH, 1.0f), vec2(1.0f, 2.0f * THIRD), vec2(9.0f * ELEVENTH, 2.0f * THIRD) }, FaceVector<3> { Face<3> { { 1, 2, 0 } }, Face<3> { { 2, 3, 0 } }, diff --git a/libraries/shared/src/shared/Shapes.h b/libraries/shared/src/shared/Shapes.h index 3486a0a663..6bd2eab199 100644 --- a/libraries/shared/src/shared/Shapes.h +++ b/libraries/shared/src/shared/Shapes.h @@ -22,6 +22,7 @@ namespace geometry { using Index = uint32_t; using Vec = glm::vec3; using VertexVector = std::vector; + using TexCoordVector = std::vector; using IndexVector = std::vector; template @@ -33,6 +34,7 @@ namespace geometry { template struct Solid { VertexVector vertices; + TexCoordVector texCoords; FaceVector faces; Solid& fitDimension(float newMaxDimension) { diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index 683903a4e8..8fe1454242 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -85,7 +85,7 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) { qCDebug(storagelogging) << "Failed to map file, falling back to memory storage " << filename; _fallback = _file.readAll(); _mapped = (uint8_t*)_fallback.data(); - } + } _valid = true; } else { qCWarning(storagelogging) << "Failed to open file " << filename; diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index 20f994e63d..f9fc71e417 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -79,12 +79,12 @@ void MainWindow::closeEvent(QCloseEvent* event) { } void MainWindow::moveEvent(QMoveEvent* event) { - emit windowGeometryChanged(QRect(event->pos(), size())); + emit windowGeometryChanged(QRect(QPoint(geometry().x(), geometry().y()), size())); // Geometry excluding the window frame. QMainWindow::moveEvent(event); } void MainWindow::resizeEvent(QResizeEvent* event) { - emit windowGeometryChanged(QRect(QPoint(x(), y()), event->size())); + emit windowGeometryChanged(QRect(QPoint(geometry().x(), geometry().y()), size())); // Geometry excluding the window frame. QMainWindow::resizeEvent(event); } @@ -107,12 +107,12 @@ void MainWindow::changeEvent(QEvent* event) { QWindowStateChangeEvent* stateChangeEvent = static_cast(event); if ((stateChangeEvent->oldState() == Qt::WindowNoState || stateChangeEvent->oldState() == Qt::WindowMaximized) && - windowState() == Qt::WindowMinimized) { + (windowState() & Qt::WindowMinimized) == Qt::WindowMinimized) { emit windowShown(false); emit windowMinimizedChanged(true); } else { emit windowShown(true); - if (stateChangeEvent->oldState() == Qt::WindowMinimized) { + if ((stateChangeEvent->oldState() & Qt::WindowMinimized) == Qt::WindowMinimized) { emit windowMinimizedChanged(false); } } diff --git a/libraries/ui/src/OffscreenQmlElement.h b/libraries/ui/src/OffscreenQmlElement.h index aa4b8cf1a9..69009533c6 100644 --- a/libraries/ui/src/OffscreenQmlElement.h +++ b/libraries/ui/src/OffscreenQmlElement.h @@ -22,6 +22,7 @@ class QQmlContext; private: \ static const QString NAME; \ static const QUrl QML; \ + static bool registered; \ public: \ static void registerType(); \ static void show(std::function f = [](QQmlContext*, QObject*) {}); \ @@ -45,6 +46,7 @@ private: #define HIFI_QML_DEF(x) \ const QUrl x::QML = PathUtils::qmlUrl(#x ".qml"); \ const QString x::NAME = #x; \ + bool x::registered = false; \ \ void x::registerType() { \ qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ @@ -52,6 +54,9 @@ private: \ void x::show(std::function f) { \ auto offscreenUi = DependencyManager::get(); \ + if (!registered) { \ + x::registerType(); \ + } \ offscreenUi->show(QML, NAME, f); \ } \ \ diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 7a938f39c8..a1d09139e3 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -154,6 +154,13 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); if (!item) { diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index cb8ee29068..26f9f58dd5 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -60,6 +60,7 @@ public: void createDesktop(const QUrl& url); void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void hide(const QString& name); + void hideDesktopWindows(); bool isVisible(const QString& name); void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); bool shouldSwallowShortcut(QEvent* event); diff --git a/libraries/ui/src/VirtualPadManager.cpp b/libraries/ui/src/VirtualPadManager.cpp index 4c3e6ff728..c786110bdf 100644 --- a/libraries/ui/src/VirtualPadManager.cpp +++ b/libraries/ui/src/VirtualPadManager.cpp @@ -34,6 +34,15 @@ namespace VirtualPad { return _currentTouch; } + const float Manager::DPI = 534.0f; + const float Manager::BASE_DIAMETER_PIXELS = 512.0f; + const float Manager::BASE_MARGIN_PIXELS = 59.0f; + const float Manager::STICK_RADIUS_PIXELS = 105.0f; + const float Manager::JUMP_BTN_TRIMMED_RADIUS_PIXELS = 67.0f; + const float Manager::JUMP_BTN_FULL_PIXELS = 134.0f; + const float Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS = 67.0f; + const float Manager::JUMP_BTN_LEFT_MARGIN_PIXELS = 547.0f; + Manager::Manager() { } @@ -51,8 +60,40 @@ namespace VirtualPad { return _enabled; } + void Manager::hide(bool hidden) { + _hidden = hidden; + } + + bool Manager::isHidden() { + return _hidden; + } + + int Manager::extraBottomMargin() { + return _extraBottomMargin; + } + + void Manager::setExtraBottomMargin(int margin) { + _extraBottomMargin = margin; + } + + glm::vec2 Manager::getJumpButtonPosition() { + return _jumpButtonPosition; + } + + void Manager::setJumpButtonPosition(glm::vec2 point) { + _jumpButtonPosition = point; + } + Instance* Manager::getLeftVirtualPad() { return &_leftVPadInstance; } + bool Instance::isShown() { + return _shown; + } + + void Instance::setShown(bool show) { + _shown = show; + } + } diff --git a/libraries/ui/src/VirtualPadManager.h b/libraries/ui/src/VirtualPadManager.h index 599954989f..68b3d4f10f 100644 --- a/libraries/ui/src/VirtualPadManager.h +++ b/libraries/ui/src/VirtualPadManager.h @@ -21,10 +21,13 @@ namespace VirtualPad { virtual glm::vec2 getFirstTouch(); virtual void setCurrentTouch(glm::vec2 point); virtual glm::vec2 getCurrentTouch(); + virtual bool isShown(); + virtual void setShown(bool show); private: bool _isBeingTouched; glm::vec2 _firstTouch; glm::vec2 _currentTouch; + bool _shown; }; class Manager : public QObject, public Dependency { @@ -37,9 +40,28 @@ namespace VirtualPad { Instance* getLeftVirtualPad(); bool isEnabled(); void enable(bool enable); + bool isHidden(); + void hide(bool hide); + int extraBottomMargin(); + void setExtraBottomMargin(int margin); + glm::vec2 getJumpButtonPosition(); + void setJumpButtonPosition(glm::vec2 point); + + static const float DPI; + static const float BASE_DIAMETER_PIXELS; + static const float BASE_MARGIN_PIXELS; + static const float STICK_RADIUS_PIXELS; + static const float JUMP_BTN_TRIMMED_RADIUS_PIXELS; + static const float JUMP_BTN_FULL_PIXELS; + static const float JUMP_BTN_BOTTOM_MARGIN_PIXELS; + static const float JUMP_BTN_LEFT_MARGIN_PIXELS; + private: Instance _leftVPadInstance; bool _enabled; + bool _hidden; + glm::vec2 _jumpButtonPosition; + int _extraBottomMargin {0}; }; } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 731a40fa7e..12e9b8b87c 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -225,7 +225,7 @@ void AudioHandler::run() { void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); - + new QQmlFileSelector(engine); static std::once_flag once; std::call_once(once, [] { qRegisterMetaType(); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 0191baca5e..a079609bb7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -387,6 +387,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { offscreenUi->hide("RunningScripts"); _showRunningScripts = true; } + + offscreenUi->hideDesktopWindows(); // destroy desktop window if (_desktopWindow) { _desktopWindow->deleteLater(); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 34827117f0..a8c8ddd9c8 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -78,7 +78,7 @@ public: QObject* getFlags(); signals: - /** jsdoc + /**jsdoc * Signaled when a tablet message or dialog is created * @function TabletProxy#tabletNotification * @returns {Signal} @@ -205,7 +205,7 @@ public: - /** jsdoc + /**jsdoc * Check if the tablet has a message dialog open * @function TabletProxy#isMessageDialogOpen */ @@ -291,7 +291,7 @@ signals: */ void screenChanged(QVariant type, QVariant url); - /** jsdoc + /**jsdoc * Signaled when the tablet becomes visible or becomes invisible * @function TabletProxy#isTabletShownChanged * @returns {Signal} diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 06cf929368..4a0f52e272 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -20,8 +20,12 @@ if (NOT SERVER_ONLY AND NOT ANDROID) add_subdirectory(${DIR}) set(DIR "oculusLegacy") add_subdirectory(${DIR}) - set(DIR "hifiSixense") - add_subdirectory(${DIR}) + + if (USE_SIXENSE) + set(DIR "hifiSixense") + add_subdirectory(${DIR}) + endif() + set(DIR "hifiSpacemouse") add_subdirectory(${DIR}) set(DIR "hifiNeuron") diff --git a/plugins/hifiCodec/CMakeLists.txt b/plugins/hifiCodec/CMakeLists.txt index 28c1dc3807..9ecaf7b511 100644 --- a/plugins/hifiCodec/CMakeLists.txt +++ b/plugins/hifiCodec/CMakeLists.txt @@ -12,5 +12,7 @@ link_hifi_libraries(audio plugins) add_dependency_external_projects(hifiAudioCodec) target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${HIFIAUDIOCODEC_LIBRARIES}) -install_beside_console() +if (BUILD_SERVER) + install_beside_console() +endif () diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index 638cadf574..893b7f48b1 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -19,7 +19,7 @@ if (WIN32 AND (NOT USE_GLES)) set(TARGET_NAME oculus) setup_hifi_plugin(Multimedia) link_hifi_libraries( - shared task gl gpu gpu-gl controllers ui qml + shared task gl gpu ${PLATFORM_GL_BACKEND} controllers ui qml plugins ui-plugins display-plugins input-plugins audio-client networking render-utils ${PLATFORM_GL_BACKEND} diff --git a/plugins/pcmCodec/CMakeLists.txt b/plugins/pcmCodec/CMakeLists.txt index 900a642a88..cce33ecd1a 100644 --- a/plugins/pcmCodec/CMakeLists.txt +++ b/plugins/pcmCodec/CMakeLists.txt @@ -9,5 +9,7 @@ set(TARGET_NAME pcmCodec) setup_hifi_client_server_plugin() link_hifi_libraries(shared plugins) -install_beside_console() +if (BUILD_SERVER) + install_beside_console() +endif () diff --git a/script-archive/inspect.js b/script-archive/inspect.js index 555b4105b7..18b26ab709 100644 --- a/script-archive/inspect.js +++ b/script-archive/inspect.js @@ -26,14 +26,14 @@ var RADIUS_RATE = 1.0 / 100.0; var PAN_RATE = 250.0; var Y_AXIS = { - x: 0, - y: 1, - z: 0 + x: 0, + y: 1, + z: 0 }; var X_AXIS = { - x: 1, - y: 0, - z: 0 + x: 1, + y: 0, + z: 0 }; var LOOK_AT_TIME = 500; @@ -56,21 +56,20 @@ var mode = noMode; var mouseLastX = 0; var mouseLastY = 0; - var center = { - x: 0, - y: 0, - z: 0 + x: 0, + y: 0, + z: 0 }; var position = { - x: 0, - y: 0, - z: 0 + x: 0, + y: 0, + z: 0 }; var vector = { - x: 0, - y: 0, - z: 0 + x: 0, + y: 0, + z: 0 }; var radius = 0.0; var azimuth = 0.0; @@ -83,258 +82,248 @@ var rotatingTowardsTarget = false; var targetCamOrientation; var oldPosition, oldOrientation; - function orientationOf(vector) { - var direction, - yaw, - pitch; + var direction, + yaw, + pitch; - direction = Vec3.normalize(vector); - yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); - pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); - return Quat.multiply(yaw, pitch); + direction = Vec3.normalize(vector); + yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); + pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); + return Quat.multiply(yaw, pitch); } - function handleRadialMode(dx, dy) { - azimuth += dx / AZIMUTH_RATE; - radius += radius * dy * RADIUS_RATE; - if (radius < 1) { - radius = 1; - } + azimuth += dx / AZIMUTH_RATE; + radius += radius * dy * RADIUS_RATE; + if (radius < 1) { + radius = 1; + } - vector = { - x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, - y: Math.sin(altitude) * radius, - z: (Math.cos(altitude) * Math.sin(azimuth)) * radius - }; - position = Vec3.sum(center, vector); - Camera.setPosition(position); - Camera.setOrientation(orientationOf(vector)); + vector = { + x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, + y: Math.sin(altitude) * radius, + z: (Math.cos(altitude) * Math.sin(azimuth)) * radius + }; + position = Vec3.sum(center, vector); + Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } function handleOrbitMode(dx, dy) { - azimuth += dx / AZIMUTH_RATE; - altitude += dy / ALTITUDE_RATE; - if (altitude > PI / 2.0) { - altitude = PI / 2.0; - } - if (altitude < -PI / 2.0) { - altitude = -PI / 2.0; - } + azimuth += dx / AZIMUTH_RATE; + altitude += dy / ALTITUDE_RATE; + if (altitude > PI / 2.0) { + altitude = PI / 2.0; + } + if (altitude < -PI / 2.0) { + altitude = -PI / 2.0; + } - vector = { - x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, - y: Math.sin(altitude) * radius, - z: (Math.cos(altitude) * Math.sin(azimuth)) * radius - }; - position = Vec3.sum(center, vector); - Camera.setPosition(position); - Camera.setOrientation(orientationOf(vector)); + vector = { + x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, + y: Math.sin(altitude) * radius, + z: (Math.cos(altitude) * Math.sin(azimuth)) * radius + }; + position = Vec3.sum(center, vector); + Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } - function handlePanMode(dx, dy) { - var up = Quat.getUp(Camera.getOrientation()); - var right = Quat.getRight(Camera.getOrientation()); - var distance = Vec3.length(vector); + var up = Quat.getUp(Camera.getOrientation()); + var right = Quat.getRight(Camera.getOrientation()); + var distance = Vec3.length(vector); - var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE)); + var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE)); - center = Vec3.sum(center, dv); - position = Vec3.sum(position, dv); + center = Vec3.sum(center, dv); + position = Vec3.sum(position, dv); - Camera.setPosition(position); - Camera.setOrientation(orientationOf(vector)); + Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } function saveCameraState() { - oldMode = Camera.mode; - oldPosition = Camera.getPosition(); - oldOrientation = Camera.getOrientation(); + oldMode = Camera.mode; + oldPosition = Camera.getPosition(); + oldOrientation = Camera.getOrientation(); - Camera.mode = "independent"; - Camera.setPosition(oldPosition); + Camera.mode = "independent"; + Camera.setPosition(oldPosition); } function restoreCameraState() { - Camera.mode = oldMode; - Camera.setPosition(oldPosition); - Camera.setOrientation(oldOrientation); + Camera.mode = oldMode; + Camera.setPosition(oldPosition); + Camera.setOrientation(oldOrientation); } function handleModes() { - var newMode = (mode == noMode) ? noMode : detachedMode; - if (alt) { - if (control) { - if (shift) { - newMode = panningMode; - } else { - newMode = orbitMode; - } - } else { - newMode = radialMode; + var newMode = (mode == noMode) ? noMode : detachedMode; + if (alt) { + if (control) { + if (shift) { + newMode = panningMode; + } else { + newMode = orbitMode; + } + } else { + newMode = radialMode; + } } - } - // if entering detachMode - if (newMode == detachedMode && mode != detachedMode) { - avatarPosition = MyAvatar.position; - avatarOrientation = MyAvatar.orientation; - } - // if leaving detachMode - if (mode == detachedMode && newMode == detachedMode && - (avatarPosition.x != MyAvatar.position.x || - avatarPosition.y != MyAvatar.position.y || - avatarPosition.z != MyAvatar.position.z || - avatarOrientation.x != MyAvatar.orientation.x || - avatarOrientation.y != MyAvatar.orientation.y || - avatarOrientation.z != MyAvatar.orientation.z || - avatarOrientation.w != MyAvatar.orientation.w)) { - newMode = noMode; - } + // if entering detachMode + if (newMode == detachedMode && mode != detachedMode) { + avatarPosition = MyAvatar.position; + avatarOrientation = MyAvatar.orientation; + } + // if leaving detachMode + if (mode == detachedMode && newMode == detachedMode && + (avatarPosition.x != MyAvatar.position.x || + avatarPosition.y != MyAvatar.position.y || + avatarPosition.z != MyAvatar.position.z || + avatarOrientation.x != MyAvatar.orientation.x || + avatarOrientation.y != MyAvatar.orientation.y || + avatarOrientation.z != MyAvatar.orientation.z || + avatarOrientation.w != MyAvatar.orientation.w)) { + newMode = noMode; + } - if (mode == noMode && newMode != noMode && Camera.mode == "independent") { - newMode = noMode; - } + if (mode == noMode && newMode != noMode && Camera.mode == "independent") { + newMode = noMode; + } - // if leaving noMode - if (mode == noMode && newMode != noMode) { - saveCameraState(); - } - // if entering noMode - if (newMode == noMode && mode != noMode) { - restoreCameraState(); - } + // if leaving noMode + if (mode == noMode && newMode != noMode) { + saveCameraState(); + } + // if entering noMode + if (newMode == noMode && mode != noMode) { + restoreCameraState(); + } - mode = newMode; + mode = newMode; } function keyPressEvent(event) { - var changed = false; + var changed = false; - if (event.text == "ALT") { - alt = true; - changed = true; - } - if (event.text == "CONTROL") { - control = true; - changed = true; - } - if (event.text == "SHIFT") { - shift = true; - changed = true; - } + if (event.text == "ALT") { + alt = true; + changed = true; + } + if (event.text == "CONTROL") { + control = true; + changed = true; + } + if (event.text == "SHIFT") { + shift = true; + changed = true; + } - if (changed) { - handleModes(); - } + if (changed) { + handleModes(); + } } function keyReleaseEvent(event) { - var changed = false; + var changed = false; - if (event.text == "ALT") { - alt = false; - changed = true; - mode = noMode; - restoreCameraState(); - } - if (event.text == "CONTROL") { - control = false; - changed = true; - } - if (event.text == "SHIFT") { - shift = false; - changed = true; - } - - if (changed) { - handleModes(); - } -} - - - -function mousePressEvent(event) { - if (alt && !isActive) { - mouseLastX = event.x; - mouseLastY = event.y; - - // Compute trajectories related values - var pickRay = Camera.computePickRay(mouseLastX, mouseLastY); - var modelIntersection = Entities.findRayIntersection(pickRay, true); - - position = Camera.getPosition(); - - var avatarTarget = MyAvatar.getTargetAvatarPosition(); - - - var distance = -1; - var string; - - if (modelIntersection.intersects && modelIntersection.accurate) { - distance = modelIntersection.distance; - center = modelIntersection.intersection; - string = "Inspecting model"; - //We've selected our target, now orbit towards it automatically - rotatingTowardsTarget = true; - //calculate our target cam rotation - Script.setTimeout(function() { - rotatingTowardsTarget = false; - }, LOOK_AT_TIME); - - vector = Vec3.subtract(position, center); - targetCamOrientation = orientationOf(vector); - radius = Vec3.length(vector); - azimuth = Math.atan2(vector.z, vector.x); - altitude = Math.asin(vector.y / Vec3.length(vector)); - - isActive = true; + if (event.text == "ALT") { + alt = false; + changed = true; + mode = noMode; + restoreCameraState(); + } + if (event.text == "CONTROL") { + control = false; + changed = true; + } + if (event.text == "SHIFT") { + shift = false; + changed = true; } - } + if (changed) { + handleModes(); + } +} + +function mousePressEvent(event) { + if (alt && !isActive) { + mouseLastX = event.x; + mouseLastY = event.y; + + // Compute trajectories related values + var pickRay = Camera.computePickRay(mouseLastX, mouseLastY); + var modelIntersection = Entities.findRayIntersection(pickRay, true); + var avatarIntersection = AvatarList.findRayIntersection(pickRay); + + position = Camera.getPosition(); + + if (avatarIntersection.intersects || (modelIntersection.intersects && modelIntersection.accurate)) { + if (avatarIntersection.intersects) { + center = avatarIntersection.intersection; + } else { + center = modelIntersection.intersection; + } + // We've selected our target, now orbit towards it automatically + rotatingTowardsTarget = true; + // calculate our target cam rotation + Script.setTimeout(function () { + rotatingTowardsTarget = false; + }, LOOK_AT_TIME); + + vector = Vec3.subtract(position, center); + targetCamOrientation = orientationOf(vector); + radius = Vec3.length(vector); + azimuth = Math.atan2(vector.z, vector.x); + altitude = Math.asin(vector.y / Vec3.length(vector)); + + isActive = true; + } + } } function mouseReleaseEvent(event) { - if (isActive) { - isActive = false; - } + if (isActive) { + isActive = false; + } } function mouseMoveEvent(event) { - if (isActive && mode != noMode && !rotatingTowardsTarget) { - if (mode == radialMode) { - handleRadialMode(event.x - mouseLastX, event.y - mouseLastY); + if (isActive && mode != noMode && !rotatingTowardsTarget) { + if (mode == radialMode) { + handleRadialMode(event.x - mouseLastX, event.y - mouseLastY); + } + if (mode == orbitMode) { + handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY); + } + if (mode == panningMode) { + handlePanMode(event.x - mouseLastX, event.y - mouseLastY); + } } - if (mode == orbitMode) { - handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY); - } - if (mode == panningMode) { - handlePanMode(event.x - mouseLastX, event.y - mouseLastY); - } - - } - mouseLastX = event.x; - mouseLastY = event.y; + mouseLastX = event.x; + mouseLastY = event.y; } function update() { - handleModes(); - if (rotatingTowardsTarget) { - rotateTowardsTarget(); - } + handleModes(); + if (rotatingTowardsTarget) { + rotateTowardsTarget(); + } } function rotateTowardsTarget() { - var newOrientation = Quat.mix(Camera.getOrientation(), targetCamOrientation, .1); - Camera.setOrientation(newOrientation); + var newOrientation = Quat.mix(Camera.getOrientation(), targetCamOrientation, 0.1); + Camera.setOrientation(newOrientation); } function scriptEnding() { - if (mode != noMode) { - restoreCameraState(); - } + if (mode != noMode) { + restoreCameraState(); + } } Controller.keyPressEvent.connect(keyPressEvent); @@ -345,4 +334,4 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file +Script.scriptEnding.connect(scriptEnding); diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index a8f6bf42a1..11aee6a9d2 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -16,7 +16,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/+android/touchscreenvirtualpad.js", "system/+android/bottombar.js", "system/+android/audio.js" , - "system/+android/modes.js"/*, + "system/+android/modes.js", + "system/+android/stats.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index e9262c9308..db089f09ee 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -186,7 +186,7 @@ var prevEntityID = null; for (var i = 0; i < 7; i++) { var newID = Entities.addEntity({ - name: "hinge test " + i, + name: "slider test " + i, type: "Box", color: { blue: 128, green: 40 * i, red: 20 }, dimensions: { x: 0.2, y: 0.1, z: 0.2 }, diff --git a/scripts/developer/tests/messagesTests.js b/scripts/developer/tests/messagesTests.js index 18beafa4cc..094ad6f1c0 100644 --- a/scripts/developer/tests/messagesTests.js +++ b/scripts/developer/tests/messagesTests.js @@ -5,7 +5,7 @@ Messages.subscribe(channelName); //messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly); Messages.messageReceived.connect(function(channel, message, sender, local) { - print("message recieved on ", channel, " message:", message, " from:", sender, " local:", local); + print("message received on ", channel, " message:", message, " from:", sender, " local:", local); }); Messages.dataReceived.connect(function(channel, data, sender, local) { @@ -17,7 +17,7 @@ Messages.dataReceived.connect(function(channel, data, sender, local) { } dataAsString += int8data[i]; } - print("data recieved on ", channel, " from:", sender, " local:", local, "length of data:", int8data.length, " data:", dataAsString); + print("data received on ", channel, " from:", sender, " local:", local, "length of data:", int8data.length, " data:", dataAsString); }); var counter = 0; diff --git a/scripts/developer/tests/webOverlayTool.js b/scripts/developer/tests/webOverlayTool.js new file mode 100644 index 0000000000..1a3aa35205 --- /dev/null +++ b/scripts/developer/tests/webOverlayTool.js @@ -0,0 +1,102 @@ +// webSpawnTool.js +// +// Stress tests the rendering of web surfaces over time +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +SPAWNER = function (properties) { + properties = properties || {}; + var RADIUS = properties.radius || 5.0; // Spawn within this radius (square) + var SPAWN_COUNT = properties.count || 10000; // number of entities to spawn + var SPAWN_LIMIT = properties.spawnLimit || 1; + var SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 2; + var SPAWN_LIFETIME = properties.lifetime || 10; // Entity timeout (when/if we crash, we need the entities to delete themselves) + + function randomPositionXZ(center, radius) { + return { + x: center.x + (Math.random() * radius * 2.0) - radius, + y: center.y, + z: center.z + (Math.random() * radius * 2.0) - radius + }; + } + + function makeObject(properties) { + + var overlay = Overlays.addOverlay("web3d", { + name: "Web", + url: "https://www.reddit.com/r/random", + localPosition: randomPositionXZ( { x: 0, y: 0, z: -1 }, 1), + localRotation: Quat.angleAxis(180, Vec3.Y_AXIS), + dimensions: {x: .8, y: .45, z: 0.1}, + color: { red: 255, green: 255, blue: 255 }, + alpha: 1.0, + showKeyboardFocusHighlight: false, + visible: true + }); + + var now = Date.now(); + + return { + destroy: function () { + Overlays.deleteOverlay(overlay) + }, + getAge: function () { + return (Date.now() - now) / 1000.0; + } + }; + } + + + var items = []; + var toCreate = 0; + var spawned = 0; + var spawnTimer = 0.0; + var keepAliveTimer = 0.0; + + function clear () { + } + + function create() { + toCreate = SPAWN_COUNT; + Script.update.connect(spawn); + } + + function spawn(dt) { + if (toCreate <= 0) { + Script.update.disconnect(spawn); + print("Finished spawning"); + } + else if ((spawnTimer -= dt) < 0.0){ + spawnTimer = SPAWN_INTERVAL; + + var n = Math.min(toCreate, SPAWN_LIMIT); + print("Spawning " + n + " items (" + (spawned += n) + ")"); + + toCreate -= n; + for (; n > 0; --n) { + items.push(makeObject()); + } + } + } + + function despawn() { + print("despawning"); + items.forEach(function (item) { + item.destroy(); + }); + item = []; + } + + function init () { + Script.update.disconnect(init); + Script.scriptEnding.connect(despawn); + clear(); + create(); + } + + Script.update.connect(init); +}; + +SPAWNER(); diff --git a/scripts/developer/utilities/audio/audioScope.js b/scripts/developer/utilities/audio/audioScope.js index 00c9e4b725..63cbf0a6e2 100644 --- a/scripts/developer/utilities/audio/audioScope.js +++ b/scripts/developer/utilities/audio/audioScope.js @@ -1,4 +1,4 @@ -var qml = Script.resourcesPath() + '/qml/AudioScope.qml'; +var qml = Script.resourcesPath() + '/qml/AudioScopeUI.qml'; var window = new OverlayWindow({ title: 'Audio Scope', source: qml, @@ -14,4 +14,4 @@ window.closed.connect(function () { AudioScope.setServerEcho(false); AudioScope.selectAudioScopeFiveFrames(); Script.stop(); -}); \ No newline at end of file +}); diff --git a/scripts/developer/utilities/render/antialiasing.js b/scripts/developer/utilities/render/antialiasing.js new file mode 100644 index 0000000000..e915d75e93 --- /dev/null +++ b/scripts/developer/utilities/render/antialiasing.js @@ -0,0 +1,99 @@ +"use strict"; + +// +// gemstoneMagicMaker.js +// tablet-sample-app +// +// Created by Faye Li on Feb 6 2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var TABLET_BUTTON_NAME = "TAA"; + var QMLAPP_URL = Script.resolvePath("./antialiasing.qml"); + + + var onLuciScreen = false; + + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + var moveDebugCursor = false; + Controller.mousePressEvent.connect(function (e) { + if (e.isMiddleButton) { + moveDebugCursor = true; + setDebugCursor(e.x, e.y); + } + }); + Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); + Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); + + + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + function setDebugCursor(x, y) { + nx = ((x + 0.5) / Window.innerWidth); + ny = 1.0 - ((y + 0.5) / (Window.innerHeight)); + + Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny }; + } + +}()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml new file mode 100644 index 0000000000..e8034c48bd --- /dev/null +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -0,0 +1,177 @@ +// +// Antialiasing.qml +// +// Created by Sam Gateau on 8/14/2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + +import "configSlider" +import "../lib/plotperf" + +Item { +Rectangle { + id: root; + + HifiConstants { id: hifi; } + color: hifi.colors.baseGray; + + Column { + id: antialiasing + spacing: 20 + padding: 10 + + Column{ + spacing: 10 + + Row { + spacing: 10 + id: fxaaOnOff + property bool debugFXAA: false + HifiControls.Button { + text: { + if (fxaaOnOff.debugFXAA) { + return "FXAA" + } else { + return "TAA" + } + } + onClicked: { + fxaaOnOff.debugFXAA = !fxaaOnOff.debugFXAA + if (fxaaOnOff.debugFXAA) { + Render.getConfig("RenderMainView.JitterCam").none(); + Render.getConfig("RenderMainView.Antialiasing").debugFXAAX = 0; + } else { + Render.getConfig("RenderMainView.JitterCam").play(); + Render.getConfig("RenderMainView.Antialiasing").debugFXAAX = 1.0; + } + + } + } + } + Separator {} + Row { + spacing: 10 + + HifiControls.Button { + text: { + var state = 2 - (Render.getConfig("RenderMainView.JitterCam").freeze * 1 - Render.getConfig("RenderMainView.JitterCam").stop * 2); + if (state === 2) { + return "Jitter" + } else if (state === 1) { + return "Paused at " + Render.getConfig("RenderMainView.JitterCam").index + "" + } else { + return "No Jitter" + } + } + onClicked: { Render.getConfig("RenderMainView.JitterCam").cycleStopPauseRun(); } + } + HifiControls.Button { + text: "<" + onClicked: { Render.getConfig("RenderMainView.JitterCam").prev(); } + } + HifiControls.Button { + text: ">" + onClicked: { Render.getConfig("RenderMainView.JitterCam").next(); } + } + } + Separator {} + HifiControls.CheckBox { + boxSize: 20 + text: "Constrain color" + checked: Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] = checked } + } + ConfigSlider { + label: qsTr("Covariance gamma") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "covarianceGamma" + max: 1.5 + min: 0.5 + } + Separator {} + HifiControls.CheckBox { + boxSize: 20 + text: "Feedback history color" + checked: Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] = checked } + } + + ConfigSlider { + label: qsTr("Source blend") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "blend" + max: 1.0 + min: 0.0 + } + + ConfigSlider { + label: qsTr("Post sharpen") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "sharpen" + max: 1.0 + min: 0.0 + } + Separator {} + Row { + + spacing: 10 + HifiControls.CheckBox { + boxSize: 20 + text: "Debug" + checked: Render.getConfig("RenderMainView.Antialiasing")["debug"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["debug"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Show Debug Cursor" + checked: Render.getConfig("RenderMainView.Antialiasing")["showCursorPixel"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showCursorPixel"] = checked } + } + } + ConfigSlider { + label: qsTr("Debug Region <") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "debugX" + max: 1.0 + min: 0.0 + } + HifiControls.CheckBox { + boxSize: 20 + text: "Closest Fragment" + checked: Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] = checked } + } + ConfigSlider { + label: qsTr("Debug Velocity Threshold [pix]") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "debugShowVelocityThreshold" + max: 50 + min: 0.0 + } + ConfigSlider { + label: qsTr("Debug Orb Zoom") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "debugOrbZoom" + max: 32.0 + min: 1.0 + } + } + } +} +} diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index c2173f6e2a..664af836a9 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -157,7 +157,7 @@ }) function cleanup() { - Pointers.removePointer(ray); + Pointers.removePointer(laser); Selection.disableListHighlight(HoveringList) Selection.removeListFromMap(HoveringList) diff --git a/scripts/developer/utilities/render/debugTransition.js b/scripts/developer/utilities/render/debugTransition.js index 161ff3f5d4..450b2e3ac9 100644 --- a/scripts/developer/utilities/render/debugTransition.js +++ b/scripts/developer/utilities/render/debugTransition.js @@ -1,5 +1,3 @@ -"use strict"; - // // debugTransition.js // developer/utilities/render @@ -12,12 +10,17 @@ // (function() { + "use strict"; + var TABLET_BUTTON_NAME = "Transition"; var QMLAPP_URL = Script.resolvePath("./transition.qml"); var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg"); - + Script.include([ + Script.resolvePath("../../../system/libraries/stringHelpers.js"), + ]); + var onScreen = false; function onClicked() { @@ -37,6 +40,71 @@ var hasEventBridge = false; + function enableSphereVisualization() { + if (gradientSphere==undefined) { + gradientSphere = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0), + dimensions: { x: 1.0, y: 1.0, z: 1.0 }, + color: { red: 100, green: 150, blue: 255}, + alpha: 0.2, + solid: false + }); + } + if (noiseSphere==undefined) { + noiseSphere = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0), + dimensions: { x: 1.0, y: 1.0, z: 1.0 }, + color: { red: 255, green: 150, blue: 100}, + alpha: 0.2, + solid: false + }); + } + } + + function disableSphereVisualization() { + Overlays.deleteOverlay(noiseSphere); + Overlays.deleteOverlay(gradientSphere); + noiseSphere = undefined + gradientSphere = undefined + } + + // Create a Laser pointer used to pick and add objects to selections + var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 }; + var COLOR1 = {red: 255, green: 0, blue: 0}; + var COLOR2 = {red: 0, green: 255, blue: 0}; + var end1 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR1, + ignoreRayIntersection: true + } + var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignoreRayIntersection: true + } + var laser + + function enablePointer() { + laser = Pointers.createPointer(PickType.Ray, { + joint: "Mouse", + filter: Picks.PICK_ENTITIES, + renderStates: [{name: "one", end: end1}], + defaultRenderStates: [{name: "one", end: end2, distance: 2.0}], + enabled: true + }); + Pointers.setRenderState(laser, "one"); + Pointers.enablePointer(laser) + } + + function disablePointer() { + Pointers.disablePointer(laser) + Pointers.removePointer(laser); + } + function wireEventBridge(on) { if (!tablet) { print("Warning in wireEventBridge(): 'tablet' undefined!"); @@ -46,11 +114,15 @@ if (!hasEventBridge) { tablet.fromQml.connect(fromQml); hasEventBridge = true; + enablePointer(); + Render.getConfig("RenderMainView.FadeEdit")["editFade"] = true } } else { if (hasEventBridge) { tablet.fromQml.disconnect(fromQml); hasEventBridge = false; + disablePointer(); + Render.getConfig("RenderMainView.FadeEdit")["editFade"] = false } } } @@ -66,12 +138,87 @@ wireEventBridge(onScreen); } + var isEditEnabled = false + var noiseSphere + var gradientSphere + var selectedEntity + var editedCategory + + var FADE_MIN_SCALE = 0.001 + var FADE_MAX_SCALE = 10000.0 + + function parameterToValuePow(parameter, minValue, maxOverMinValue) { + return minValue * Math.pow(maxOverMinValue, parameter); + //return parameter + } + + function update(dt) { + var gradientProperties = Entities.getEntityProperties(selectedEntity, ["position", "dimensions"]); + if (gradientProperties!=undefined) { + var pos = gradientProperties.position + if (pos!=undefined) { + var config = Render.getConfig("RenderMainView.Fade") + //print("Center at "+pos.x+" "+pos.y+" "+pos.z) + var dim = {x:config.baseSizeX, y:config.baseSizeY, z:config.baseSizeZ} + dim.x = parameterToValuePow(dim.x, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.y = parameterToValuePow(dim.y, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.z = parameterToValuePow(dim.z, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + if (editedCategory==4 || editedCategory==5) { + dim.y = gradientProperties.dimensions.y + pos.y = pos.y - gradientProperties.dimensions.y/2 + } + Overlays.editOverlay(gradientSphere, { position: pos, dimensions: dim, alpha: config.baseLevel }) + dim.x = parameterToValuePow(config.noiseSizeX, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.y = parameterToValuePow(config.noiseSizeY, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.z = parameterToValuePow(config.noiseSizeZ, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + Overlays.editOverlay(noiseSphere, { position: pos, dimensions: dim, alpha: config.noiseLevel }) + } + } + } + + Script.update.connect(update); + + function loadConfiguration(fileUrl) { + var config = Render.getConfig("RenderMainView.Fade") + config.load(fileUrl) + } + + function saveConfiguration(fileUrl) { + var config = Render.getConfig("RenderMainView.Fade") + config.save(fileUrl) + } + function fromQml(message) { + tokens = message.split('*') + //print("Received '"+message+"' from transition.qml") + command = tokens[0].toLowerCase() + if (command=="category") { + editedCategory = parseInt(tokens[1]) + } else if (command=="save") { + var filePath = tokens[1] + print("Raw token = "+filePath) + if (filePath.startsWith("file:///")) { + filePath = filePath.substr(8) + print("Saving configuration to "+filePath) + saveConfiguration(filePath) + } else { + print("Configurations can only be saved to local files") + } + } else if (command=="load") { + var filePath = tokens[1] + if (filePath.startsWith("file:///")) { + filePath = filePath.substr(8) + print("Loading configuration from "+filePath) + loadConfiguration(filePath) + } else { + print("Configurations can only be loaded from local files") + } + } } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); - + Script.scriptEnding.connect(function () { if (onScreen) { tablet.gotoHomeScreen(); @@ -81,4 +228,28 @@ tablet.removeButton(button); }); + + var currentSelectionName = "" + var SelectionList = "TransitionEdit" + + Selection.enableListToScene(SelectionList) + Selection.clearSelectedItemsList(SelectionList) + + Entities.clickDownOnEntity.connect(function (id, event) { + if (selectedEntity) { + Selection.removeFromSelectedItemsList(SelectionList, "entity", selectedEntity) + } + selectedEntity = id + Selection.addToSelectedItemsList(SelectionList, "entity", selectedEntity) + update() + }) + + function cleanup() { + disablePointer(); + Selection.removeListFromMap(SelectionList) + Selection.disableListToScene(SelectionList); + Overlays.deleteOverlay(noiseSphere); + Overlays.deleteOverlay(gradientSphere); + } + Script.scriptEnding.connect(cleanup); }()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 1da7871172..189d23c44f 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -200,6 +200,7 @@ Rectangle { ListElement { text: "Debug Scattering"; color: "White" } ListElement { text: "Ambient Occlusion"; color: "White" } ListElement { text: "Ambient Occlusion Blurred"; color: "White" } + ListElement { text: "Velocity"; color: "White" } ListElement { text: "Custom"; color: "White" } } width: 200 diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index e83a85f8ed..f74468a273 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -11,6 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 import "qrc:///qml/styles-uit" import "qrc:///qml/controls-uit" as HifiControls @@ -22,11 +23,31 @@ Rectangle { id: root anchors.margins: hifi.dimensions.contentMargin.x + signal sendToScript(var message); + color: hifi.colors.baseGray; property var config: Render.getConfig("RenderMainView.Fade"); property var configEdit: Render.getConfig("RenderMainView.FadeEdit"); + FileDialog { + id: fileDialog + title: "Please choose a file" + folder: shortcuts.documents + nameFilters: [ "JSON files (*.json)", "All files (*)" ] + onAccepted: { + root.sendToScript(title.split(" ")[0]+"*"+fileUrl.toString()) + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 500ms later + paramWidgetLoader.sourceComponent = undefined; + postpone.interval = 500 + postpone.start() + } + onRejected: { + } + Component.onCompleted: visible = false + } + ColumnLayout { spacing: 3 anchors.left: parent.left @@ -37,23 +58,14 @@ Rectangle { } RowLayout { - spacing: 20 + spacing: 8 Layout.fillWidth: true id: root_col - HifiControls.CheckBox { - anchors.verticalCenter: parent.verticalCenter - boxSize: 20 - text: "Edit" - checked: root.configEdit["editFade"] - onCheckedChanged: { - root.configEdit["editFade"] = checked; - Render.getConfig("RenderMainView.DrawFadedOpaqueBounds").enabled = checked; - } - } HifiControls.ComboBox { anchors.verticalCenter: parent.verticalCenter - Layout.fillWidth: true + anchors.left : parent.left + width: 300 id: categoryBox model: ["Elements enter/leave domain", "Bubble isect. - Owner POV", "Bubble isect. - Trespasser POV", "Another user leaves/arrives", "Changing an avatar"] Timer { @@ -61,17 +73,48 @@ Rectangle { interval: 100; running: false; repeat: false onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets + var isTimeBased = categoryBox.currentIndex==0 || categoryBox.currentIndex==3 + paramWidgetLoader.item.isTimeBased = isTimeBased } } onCurrentIndexChanged: { + var descriptions = [ + "Time based threshold, gradients centered on object", + "Fixed threshold, gradients centered on owner avatar", + "Position based threshold (increases when trespasser moves closer to avatar), gradients centered on trespasser avatar", + "Time based threshold, gradients centered on bottom of object", + "UNSUPPORTED" + ] + + description.text = descriptions[currentIndex] root.config["editedCategory"] = currentIndex; // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component // by setting the loader source to Null and then recreate it 100ms later paramWidgetLoader.sourceComponent = undefined; postpone.interval = 100 postpone.start() + root.sendToScript("category*"+currentIndex) } } + HifiControls.Button { + action: saveAction + Layout.fillWidth: true + anchors.top: parent.top + anchors.bottom: parent.bottom + } + HifiControls.Button { + action: loadAction + Layout.fillWidth: true + anchors.top: parent.top + anchors.bottom: parent.bottom + } + } + + HifiControls.Label { + id: description + text: "..." + Layout.fillWidth: true + wrapMode: Text.WordWrap } RowLayout { @@ -104,19 +147,18 @@ Rectangle { id: saveAction text: "Save" onTriggered: { - root.config.save() + fileDialog.title = "Save configuration..." + fileDialog.selectExisting = false + fileDialog.open() } } Action { id: loadAction text: "Load" onTriggered: { - root.config.load() - // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component - // by setting the loader source to Null and then recreate it 500ms later - paramWidgetLoader.sourceComponent = undefined; - postpone.interval = 500 - postpone.start() + fileDialog.title = "Load configuration..." + fileDialog.selectExisting = true + fileDialog.open() } } @@ -128,13 +170,8 @@ Rectangle { ColumnLayout { spacing: 3 width: root_col.width + property bool isTimeBased - HifiControls.CheckBox { - text: "Invert" - boxSize: 20 - checked: root.config["isInverted"] - onCheckedChanged: { root.config["isInverted"] = checked } - } RowLayout { Layout.fillWidth: true @@ -196,16 +233,32 @@ Rectangle { } } - - ConfigSlider { + RowLayout { + spacing: 20 height: 36 - label: "Edge Width" - integral: false - config: root.config - property: "edgeWidth" - max: 1.0 - min: 0.0 + + HifiControls.CheckBox { + text: "Invert gradient" + anchors.verticalCenter: parent.verticalCenter + boxSize: 20 + checked: root.config["isInverted"] + onCheckedChanged: { root.config["isInverted"] = checked } + } + ConfigSlider { + anchors.left: undefined + anchors.verticalCenter: parent.verticalCenter + height: 36 + width: 300 + label: "Edge Width" + integral: false + config: root.config + property: "edgeWidth" + max: 1.0 + min: 0.0 + } } + + RowLayout { Layout.fillWidth: true @@ -278,6 +331,8 @@ Rectangle { Layout.fillWidth: true ConfigSlider { + enabled: isTimeBased + opacity: isTimeBased ? 1.0 : 0.0 anchors.left: undefined anchors.right: undefined Layout.fillWidth: true @@ -290,6 +345,8 @@ Rectangle { min: 0.1 } HifiControls.ComboBox { + enabled: isTimeBased + opacity: isTimeBased ? 1.0 : 0.0 Layout.fillWidth: true model: ["Linear", "Ease In", "Ease Out", "Ease In / Out"] currentIndex: root.config["timing"] @@ -364,20 +421,6 @@ Rectangle { id: paramWidgetLoader sourceComponent: paramWidgets } - - Row { - anchors.left: parent.left - anchors.right: parent.right - - Button { - action: saveAction - } - Button { - action: loadAction - } - } - - } } \ No newline at end of file diff --git a/scripts/system/+android/audio.js b/scripts/system/+android/audio.js index b4f156d4bf..955f74d63a 100644 --- a/scripts/system/+android/audio.js +++ b/scripts/system/+android/audio.js @@ -46,7 +46,6 @@ function onMuteClicked() { printd("On Mute Clicked"); //Menu.setIsOptionChecked("Mute Microphone", !Menu.isOptionChecked("Mute Microphone")); Audio.muted = !Audio.muted; - onMuteToggled(); } function onMuteToggled() { diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js new file mode 100644 index 0000000000..2946e541b5 --- /dev/null +++ b/scripts/system/+android/avatarSelection.js @@ -0,0 +1,164 @@ +"use strict"; +// +// avatarSelection.js +// scripts/system/ +// +// Created by Gabriel Calero & Cristian Duarte on 21 Sep 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var window; + +var logEnabled = true; +var isVisible = false; + +function printd(str) { + if (logEnabled) + print("[avatarSelection.js] " + str); +} + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var data; + printd("fromQml " + JSON.stringify(message)); + switch (message.method) { + case 'selectAvatar': + // use this message.params.avatarUrl + printd("Selected Avatar: [" + message.params.avatarUrl + "]"); + App.askBeforeSetAvatarUrl(message.params.avatarUrl); + break; + case 'openAvatarMarket': + // good + App.openUrl("https://metaverse.highfidelity.com/marketplace?category=avatars"); + break; + case 'hide': + Controller.setVPadHidden(false); + module.exports.hide(); + module.exports.onHidden(); + break; + default: + print('[avatarSelection.js] Unrecognized message from avatarSelection.qml:', JSON.stringify(message)); + } +} + +function sendToQml(message) { + if (!window) { + print("[avatarSelection.js] There is no window object"); + return; + } + window.sendToQml(message); +} + +function refreshSelected(currentAvatarURL) { + sendToQml({ + type: "refreshSelected", + selectedAvatarUrl: currentAvatarURL + }); + + sendToQml({ + type: "showAvatars" + }); +} + +function init() { + if (!window) { + print("[avatarSelection.js] There is no window object for init()"); + return; + } + var DEFAULT_AVATAR_URL = "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v3/mannequin.fst"; + sendToQml({ + type: "addAvatar", + name: "Wooden Mannequin", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/thumbnail/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg", + avatarUrl: DEFAULT_AVATAR_URL + }); + sendToQml({ + type: "addAvatar", + name: "Cody", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53/thumbnail/hifi-mp-8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53.jpg", + avatarUrl: "http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst" + }); + sendToQml({ + type: "addAvatar", + name: "Mixamo Will", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73/thumbnail/hifi-mp-d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73.jpg", + avatarUrl: "http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst" + }); + sendToQml({ + type: "addAvatar", + name: "Albert", + thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e57c395-612e-4acd-9561-e79dbda0bc49/thumbnail/hifi-mp-1e57c395-612e-4acd-9561-e79dbda0bc49.jpg", + avatarUrl: "http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" + }); + /* We need to implement the wallet, so let's skip this for the moment + sendToQml({ + type: "addExtraOption", + showName: "More choices", + thumbnailUrl: "../../../images/moreAvatars.png", + methodNameWhenClicked: "openAvatarMarket", + actionText: "MARKETPLACE" + }); + */ + var currentAvatarURL = Settings.getValue('Avatar/fullAvatarURL', DEFAULT_AVATAR_URL); + printd("Default Avatar: [" + DEFAULT_AVATAR_URL + "]"); + printd("Current Avatar: [" + currentAvatarURL + "]"); + if (!currentAvatarURL || 0 === currentAvatarURL.length) { + currentAvatarURL = DEFAULT_AVATAR_URL; + } + refreshSelected(currentAvatarURL); +} + +module.exports = { + init: function() { + window = new QmlFragment({ + qml: "hifi/avatarSelection.qml", + visible: false + }); + if (window) { + window.fromQml.connect(fromQml); + } + init(); + }, + show: function() { + Controller.setVPadHidden(true); + if (window) { + window.setVisible(true); + isVisible = true; + } + }, + hide: function() { + Controller.setVPadHidden(false); + if (window) { + window.setVisible(false); + } + isVisible = false; + }, + destroy: function() { + Controller.setVPadHidden(false); + if (window) { + window.fromQml.disconnect(fromQml); + window.close(); + window = null; + } + }, + isVisible: function() { + return isVisible; + }, + width: function() { + return window ? window.size.x : 0; + }, + height: function() { + return window ? window.size.y : 0; + }, + position: function() { + return window && isVisible ? window.position : null; + }, + refreshSelectedAvatar: function(currentAvatarURL) { + refreshSelected(currentAvatarURL); + }, + onHidden: function() { + Controller.setVPadHidden(false); + } +}; diff --git a/scripts/system/+android/bottombar.js b/scripts/system/+android/bottombar.js index e58840ad6f..3435edb548 100644 --- a/scripts/system/+android/bottombar.js +++ b/scripts/system/+android/bottombar.js @@ -14,8 +14,12 @@ var bottombar; var bottomHudOptionsBar; var gotoBtn; +var avatarBtn; +var bubbleBtn; +var loginBtn; var gotoScript = Script.require('./goto.js'); +var avatarSelection = Script.require('./avatarSelection.js'); var logEnabled = false; @@ -34,15 +38,20 @@ function init() { hideAddressBar(); } }); + avatarSelection.init(); + App.fullAvatarURLChanged.connect(processedNewAvatar); setupBottomBar(); setupBottomHudOptionsBar(); raiseBottomBar(); + GlobalServices.connected.connect(handleLogin); + GlobalServices.disconnected.connect(handleLogout); } function shutdown() { + App.fullAvatarURLChanged.disconnect(processedNewAvatar); } function setupBottomBar() { @@ -60,6 +69,32 @@ function setupBottomBar() { } }); + avatarBtn = bottombar.addButton({ + icon: "icons/avatar-i.svg", + activeIcon: "icons/avatar-a.svg", + bgOpacity: 0, + height: 240, + width: 294, + hoverBgOpacity: 0, + activeBgOpacity: 0, + activeHoverBgOpacity: 0, + iconSize: 108, + textSize: 45, + text: "AVATAR" + }); + avatarBtn.clicked.connect(function() { + printd("Avatar button clicked"); + if (!avatarSelection.isVisible()) { + showAvatarSelection(); + } else { + hideAvatarSelection(); + } + }); + avatarSelection.onHidden = function() { + if (avatarBtn) { + avatarBtn.isActive = false; + } + }; gotoBtn = bottombar.addButton({ icon: "icons/goto-i.svg", @@ -69,7 +104,7 @@ function setupBottomBar() { activeBgOpacity: 0, activeHoverBgOpacity: 0, height: 240, - width: 300, + width: 294, iconSize: 108, textSize: 45, text: "GO TO" @@ -83,6 +118,44 @@ function setupBottomBar() { } }); + bubbleBtn = bottombar.addButton({ + icon: "icons/bubble-i.svg", + activeIcon: "icons/bubble-a.svg", + bgOpacity: 0, + hoverBgOpacity: 0, + activeBgOpacity: 0, + activeHoverBgOpacity: 0, + height: 240, + width: 294, + iconSize: 108, + textSize: 45, + text: "BUBBLE" + }); + + bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); + + bubbleBtn.clicked.connect(function() { + Users.toggleIgnoreRadius(); + bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); + }); + + loginBtn = bottombar.addButton({ + icon: "icons/login-i.svg", + activeIcon: "icons/login-a.svg", + height: 240, + width: 294, + iconSize: 108, + textSize: 45, + text: Account.isLoggedIn() ? "LOG OUT" : "LOG IN" + }); + loginBtn.clicked.connect(function() { + if (!Account.isLoggedIn()) { + Account.checkAndSignalForAccessToken(); + } else { + Menu.triggerOption("Login / Sign Up"); + } + }); + // TODO: setup all the buttons or provide a dynamic interface raiseBottomBar(); @@ -125,6 +198,7 @@ function lowerBottomBar() { if (bottomHudOptionsBar) { bottomHudOptionsBar.show(); } + Controller.setVPadExtraBottomMargin(0); } function raiseBottomBar() { @@ -135,6 +209,7 @@ function raiseBottomBar() { if (bottomHudOptionsBar) { bottomHudOptionsBar.hide(); } + Controller.setVPadExtraBottomMargin(255); // Height in bottombar.qml print('[bottombar.js] raiseBottomBar end'); } @@ -148,10 +223,43 @@ function hideAddressBar() { gotoBtn.isActive = false; } +function showAvatarSelection() { + avatarSelection.show(); + avatarBtn.isActive = true; +} +function hideAvatarSelection() { + avatarSelection.hide(); + avatarBtn.isActive = false; +} + +// TODO: Move to avatarSelection.js and make it possible to hide the window from there AND switch the button state here too +function processedNewAvatar(url, modelName) { + avatarSelection.refreshSelectedAvatar(url); + hideAvatarSelection(); +} + +function handleLogin() { + Script.setTimeout(function() { + if (Account.isLoggedIn()) { + MyAvatar.displayName=Account.getUsername(); + } + }, 2000); + if (loginBtn) { + loginBtn.editProperties({text: "LOG OUT"}); + } +} +function handleLogout() { + MyAvatar.displayName=""; + if (loginBtn) { + loginBtn.editProperties({text: "LOG IN"}); + } +} Script.scriptEnding.connect(function () { shutdown(); + GlobalServices.connected.disconnect(handleLogin); + GlobalServices.disconnected.disconnect(handleLogout); }); init(); diff --git a/scripts/system/+android/goto.js b/scripts/system/+android/goto.js index e917455128..2019af9077 100644 --- a/scripts/system/+android/goto.js +++ b/scripts/system/+android/goto.js @@ -52,7 +52,7 @@ module.exports = { }); }, show: function() { - Controller.setVPadEnabled(false); + Controller.setVPadHidden(true); if (window) { window.fromQml.connect(fromQml); window.setVisible(true); @@ -60,7 +60,7 @@ module.exports = { } }, hide: function() { - Controller.setVPadEnabled(true); + Controller.setVPadHidden(false); if (window) { window.fromQml.disconnect(fromQml); window.setVisible(false); diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index b29548094f..c41ae1f327 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -17,7 +17,7 @@ var currentSelectedBtn; var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; -var DEFAULT_MODE = MODE_RADAR; +var DEFAULT_MODE = MODE_MY_VIEW; var logEnabled = true; var radar = Script.require('./radar.js'); @@ -33,7 +33,6 @@ function init() { radar.setUniqueColor(uniqueColor); radar.init(); setupModesBar(); - radar.isTouchValid = isRadarModeValidTouch; } function shutdown() { @@ -183,34 +182,6 @@ function onButtonClicked(clickedButton, whatToDo, hideAllAfter) { } } -function isRadarModeValidTouch(coords) { - var qmlFragments = [modesbar.qmlFragment]; - var windows = []; - for (var i=0; i < qmlFragments.length; i++) { - var aQmlFrag = qmlFragments[i]; - if (aQmlFrag != null && aQmlFrag.isVisible() && - coords.x >= aQmlFrag.position.x * 3 && coords.x <= aQmlFrag.position.x * 3 + aQmlFrag.size.x * 3 && - coords.y >= aQmlFrag.position.y * 3 && coords.y <= aQmlFrag.position.y * 3 + aQmlFrag.size.y * 3 - ) { - printd("godViewModeTouchValid- false because of qmlFragments!? idx " + i); - return false; - } - } - - for (var i=0; i < windows.length; i++) { - var aWin = windows[i]; - if (aWin != null && aWin.position() != null && - coords.x >= aWin.position().x * 3 && coords.x <= aWin.position().x * 3 + aWin.width() * 3 && - coords.y >= aWin.position().y * 3 && coords.y <= aWin.position().y * 3 + aWin.height() * 3 - ) { - printd("godViewModeTouchValid- false because of windows!?"); - return false; - } - } - printd("godViewModeTouchValid- true by default "); - return true; -} - Script.scriptEnding.connect(function () { shutdown(); }); diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 9b9bd9c473..455299dd5f 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -13,7 +13,7 @@ var radarModeInterface = {}; -var logEnabled = true; +var logEnabled = false; function printd(str) { if (logEnabled) { print("[radar.js] " + str); @@ -31,16 +31,13 @@ var MOVE_BY = 1; // Swipe/Drag vars var PINCH_INCREMENT_FIRST = 0.4; // 0.1 meters zoom in - out var PINCH_INCREMENT = 0.4; // 0.1 meters zoom in - out -var RADAR_HEIGHT_MAX_PLUS_AVATAR = 40; +var RADAR_HEIGHT_MAX_PLUS_AVATAR = 80; var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2; -var RADAR_CAMERA_DISTANCE_TO_ICONS = 0.5; // Icons are near the camera to - // prevent the LOD manager - // dismissing them -var RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE = 1; // How much above the - // avatar base should - // the icon appear -var AVATAR_DISPLAY_NAME_HEIGHT = 38; -var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 18; +var RADAR_CAMERA_DISTANCE_TO_ICONS = 1.5; // Icons are near the camera to prevent the LOD manager dismissing them +var RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE = 1; // How much above the avatar base should the icon appear +var AVATAR_DISPLAY_NAME_HEIGHT = 106; +var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 48; +var AVATAR_DISPLAY_NAME_FONT_SIZE = 50; var lastDragAt; var lastDeltaDrag; @@ -121,19 +118,10 @@ function actionOnObjectFromEvent(event) { } function mousePress(event) { - if (!isTouchValid(coords)) { - currentTouchIsValid = false; - return; - } else { - currentTouchIsValid = true; - } mousePressOrTouchEnd(event); } function mousePressOrTouchEnd(event) { - if (!currentTouchIsValid) { - return; - } if (radar) { if (actionOnObjectFromEvent(event)) { return; @@ -158,9 +146,6 @@ function fakeDoubleTap(event) { teleporter.dragTeleportRelease(event); } -var currentTouchIsValid = false; // Currently used to know if touch hasn't - // started on a UI overlay - var DOUBLE_TAP_TIME = 300; var fakeDoubleTapStart = Date.now(); var touchEndCount = 0; @@ -241,12 +226,6 @@ function touchEnd(event) { return; } - // if touch is invalid, cancel - if (!currentTouchIsValid) { - printd("touchEnd fail because !currentTouchIsValid"); - return; - } - if (analyzeDoubleTap(event)) return; // double tap detected, finish @@ -348,20 +327,6 @@ function computePointAtPlaneY(x, y, py) { p2.z, py); } -/******************************************************************************* - * - ******************************************************************************/ - -function isTouchValid(coords) { - // TODO: Extend to the detection of touches on new menu bars - var radarModeTouchValid = radarModeInterface.isTouchValid(coords); - - // getItemAtPoint does not exist anymore, look for another way to know if we - // are touching buttons - // is it still needed? - return /* !tablet.getItemAtPoint(coords) && */radarModeTouchValid; -} - /******************************************************************************* * ******************************************************************************/ @@ -376,16 +341,8 @@ function touchBegin(event) { x : event.x, y : event.y }; - if (!isTouchValid(coords)) { - printd("analyze touch - RADAR_TOUCH - INVALID"); - currentTouchIsValid = false; - touchStartingCoordinates = null; - } else { - printd("analyze touch - RADAR_TOUCH - ok"); - currentTouchIsValid = true; - touchStartingCoordinates = coords; - touchBeginTime = Date.now(); - } + touchStartingCoordinates = coords; + touchBeginTime = Date.now(); } var startedDraggingCamera = false; // first time @@ -851,9 +808,6 @@ function oneFingerTouchUpdate(event) { } function touchUpdate(event) { - if (!currentTouchIsValid) { - return; // avoid moving and zooming when tap is over UI entities - } if (event.isPinching || event.isPinchOpening) { pinchUpdate(event); } else { @@ -924,18 +878,14 @@ function saveAvatarData(QUuid) { if (avatarsData[QUuid] != undefined) { avatarsData[QUuid].position = avat.position; } else { - var avatarIcon = Overlays.addOverlay("image3d", { - subImage : { - x : 0, - y : 0, - width : 150, - height : 142 - }, - url : getAvatarIconForUser(QUuid), - dimensions : ICON_ENTITY_DEFAULT_DIMENSIONS, - visible : false, - ignoreRayIntersection : false, - orientation : Quat.fromPitchYawRollDegrees(-90, 0, 0) + var avatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.8, + outerRadius: 2, + isSolid: true, + visible: false }); var needRefresh = !avat || !avat.displayName; @@ -943,26 +893,15 @@ function saveAvatarData(QUuid) { : "Unknown"; var textWidth = displayName.length * AVATAR_DISPLAY_NAME_CHAR_WIDTH; var avatarName = Overlays.addOverlay("text", { - width : textWidth, - height : AVATAR_DISPLAY_NAME_HEIGHT, - color : { - red : 255, - green : 255, - blue : 255 - }, - backgroundAlpha : 0.0, - textRaiseColor : { - red : 0, - green : 0, - blue : 0 - }, - font : { - size : 68, - bold : true - }, - visible : false, - text : displayName, - textAlignCenter : true + width: textWidth, + height: AVATAR_DISPLAY_NAME_HEIGHT, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + textRaiseColor: { red: 0, green: 0, blue: 0}, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, + visible: false, + text: displayName, + textAlignCenter: true }); avatarsIcons.push(avatarIcon); avatarsNames.push(avatarName); @@ -1023,22 +962,24 @@ function avatarRemoved(QUuid) { ******************************************************************************/ var myAvatarIcon; var myAvatarName; - +function distanceForCameraHeight(h) { + if (h < 30) return 1; + if (h < 40) return 2; + if (h < 50) return 2.5; + return 5; +} function renderMyAvatarIcon() { - var iconPos = findLineToHeightIntersectionCoords(MyAvatar.position.x, - MyAvatar.position.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, - MyAvatar.position.z, Camera.position.x, Camera.position.y, - Camera.position.z, Camera.position.y - - RADAR_CAMERA_DISTANCE_TO_ICONS); - if (!iconPos) { - printd("avatarmy icon pos null"); - return; - } + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); + var iconPos = findLineToHeightIntersectionCoords( MyAvatar.position.x, + MyAvatar.position.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, + MyAvatar.position.z, + Camera.position.x, Camera.position.y, Camera.position.z, + commonY); + if (!iconPos) { printd("avatarmy icon pos null"); return;} var iconDimensions = avatarIconPlaneDimensions(); var avatarPos = MyAvatar.position; var cameraPos = Camera.position; - var commonY = Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS; var borderPoints = [ computePointAtPlaneY(0, 0, commonY), computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ]; @@ -1050,45 +991,30 @@ function renderMyAvatarIcon() { var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) / (borderPoints[1].z - borderPoints[0].z); - if (!myAvatarIcon && MyAvatar.sessionUUID) { - myAvatarIcon = Overlays.addOverlay("image3d", { - subImage : { - x : 0, - y : 0, - width : 150, - height : 142 - }, - url : getAvatarIconForUser(MyAvatar.sessionUUID), - dimensions : ICON_ENTITY_DEFAULT_DIMENSIONS, - visible : false, - ignoreRayIntersection : false, - orientation : Quat.fromPitchYawRollDegrees(-90, 0, 0) + if (!myAvatarIcon && MyAvatar.SELF_ID) { + myAvatarIcon = Overlays.addOverlay("circle3d", { + color: uniqueColor.convertHexToRGB(uniqueColor.getColor(MyAvatar.SELF_ID)), + dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS, + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + innerRadius: 1.8, + outerRadius: 2, + isSolid: true, + visible: false }); } if (!myAvatarName) { myAvatarName = Overlays.addOverlay("text", { - width : 40, - height : AVATAR_DISPLAY_NAME_HEIGHT, - textAlignCenter : true, - color : { - red : 255, - green : 255, - blue : 255 - }, - backgroundAlpha : 0.0, - font : { - size : 68, - bold : true - }, - textRaiseColor : { - red : 0, - green : 0, - blue : 0 - }, - visible : false, - text : "Me" - }); + width: 100, + height: AVATAR_DISPLAY_NAME_HEIGHT, + textAlignCenter: true, + color: { red: 255, green: 255, blue: 255}, + backgroundAlpha: 0.0, + font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true}, + textRaiseColor: { red: 0, green: 0, blue: 0}, + visible: false, + text: "Me" + }); } if (myAvatarIcon) { @@ -1139,7 +1065,7 @@ function hideAllAvatarIcons() { function renderAllOthersAvatarIcons() { var avatarPos; var iconDimensions = avatarIconPlaneDimensions(); - var commonY = Camera.position.y - RADAR_CAMERA_DISTANCE_TO_ICONS; + var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y); var borderPoints = [ computePointAtPlaneY(0, 0, commonY), computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ]; @@ -1150,27 +1076,18 @@ function renderAllOthersAvatarIcons() { avatarPos = AvatarList.getAvatar(QUuid).position; var cameraPos = Camera.position; - var p1 = findLineToHeightIntersectionCoords(avatarPos.x, - avatarPos.y, avatarPos.z, cameraPos.x, cameraPos.y, - cameraPos.z, commonY); + var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, avatarPos.z, + cameraPos.x, cameraPos.y, cameraPos.z, + commonY); - var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) - / (borderPoints[1].x - borderPoints[0].x); - var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) - / (borderPoints[1].z - borderPoints[0].z); + var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) / (borderPoints[1].x - borderPoints[0].x); + var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) / (borderPoints[1].z - borderPoints[0].z); if (avatarsData[QUuid].icon != undefined) { - var iconPos = findLineToHeightIntersectionCoords( - avatarPos.x, - avatarPos.y - + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, - avatarPos.z, Camera.position.x, Camera.position.y, - Camera.position.z, Camera.position.y - - RADAR_CAMERA_DISTANCE_TO_ICONS); - if (!iconPos) { - print("avatar icon pos bad for " + QUuid); - continue; - } + var iconPos = findLineToHeightIntersectionCoords( avatarPos.x, avatarPos.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, avatarPos.z, + Camera.position.x, Camera.position.y, Camera.position.z, + commonY); + if (!iconPos) { print ("avatar icon pos bad for " + QUuid); continue; } if (avatarsData[QUuid].needRefresh) { var avat = AvatarList.getAvatar(QUuid); if (avat && avat.displayName) { @@ -1389,6 +1306,8 @@ function startRadar() { Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0); radar = true; + Controller.setVPadEnabled(false); // this was done before in CompositeExtra in the DisplayPlugin (Checking for camera not independent, not radar mode) + connectRadarModeEvents(); } @@ -1397,6 +1316,8 @@ function endRadar() { Camera.mode = "first person"; radar = false; + Controller.setVPadEnabled(true); + disconnectRadarModeEvents(); hideAllEntitiesIcons(); hideAllAvatarIcons(); diff --git a/scripts/system/+android/stats.js b/scripts/system/+android/stats.js new file mode 100644 index 0000000000..a93bcb5794 --- /dev/null +++ b/scripts/system/+android/stats.js @@ -0,0 +1,39 @@ +"use strict"; +// +// stats.js +// scripts/system/ +// +// Created by Sam Gondelman on 3/14/18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +var statsbar; +var statsButton; + +function init() { + statsbar = new QmlFragment({ + qml: "hifi/StatsBar.qml" + }); + + statsButton = statsbar.addButton({ + icon: "icons/stats.svg", + activeIcon: "icons/stats.svg", + textSize: 45, + bgOpacity: 0.0, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF", + text: "STATS" + }); + statsButton.clicked.connect(function() { + Menu.triggerOption("Stats"); + }); +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android/touchscreenvirtualpad.js b/scripts/system/+android/touchscreenvirtualpad.js index fa41a2b5e0..d48b623e03 100644 --- a/scripts/system/+android/touchscreenvirtualpad.js +++ b/scripts/system/+android/touchscreenvirtualpad.js @@ -14,6 +14,7 @@ function init() { Controller.setVPadEnabled(true); + Controller.setVPadHidden(false); } init(); diff --git a/scripts/system/+android/uniqueColor.js b/scripts/system/+android/uniqueColor.js index c296b6c87d..a2741642d2 100644 --- a/scripts/system/+android/uniqueColor.js +++ b/scripts/system/+android/uniqueColor.js @@ -41,6 +41,14 @@ function getColorForId(uuid) { module.exports = { getColor: function(id) { - return getColorForId(id); + return getColorForId(id); + }, + convertHexToRGB: function(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + red: parseInt(result[1], 16), + green: parseInt(result[2], 16), + blue: parseInt(result[3], 16) + } : null; } }; diff --git a/scripts/system/assets/images/Overlay-Viz-blank.png b/scripts/system/assets/images/Overlay-Viz-blank.png index 76f535b6e6..bbbf44f7a9 100644 Binary files a/scripts/system/assets/images/Overlay-Viz-blank.png and b/scripts/system/assets/images/Overlay-Viz-blank.png differ diff --git a/scripts/system/away.js b/scripts/system/away.js index 2a45786d0d..dc9b33e952 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -24,8 +24,9 @@ var OVERLAY_HEIGHT = 1080; var OVERLAY_DATA = { width: OVERLAY_WIDTH, height: OVERLAY_HEIGHT, - imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", - color: {red: 255, green: 255, blue: 255}, + imageURL: Script.resolvePath("assets/images/Overlay-Viz-blank.png"), + emissive: true, + drawInFront: true, alpha: 1 }; var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away @@ -37,7 +38,7 @@ var OVERLAY_DATA_HMD = { localRotation: {x: 0, y: 0, z: 0, w: 1}, width: OVERLAY_WIDTH, height: OVERLAY_HEIGHT, - url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", + url: Script.resolvePath("assets/images/Overlay-Viz-blank.png"), color: {red: 255, green: 255, blue: 255}, alpha: 1, scale: 2 * MyAvatar.sensorToWorldScale, diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 994bde49eb..f1a403a49c 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -89,14 +89,8 @@ bubbleOverlayTimestamp = nowTimestamp; Script.update.connect(update); updateConnected = true; - - // Flash button - if (!bubbleFlashTimer) { - bubbleFlashTimer = Script.setInterval(function () { - writeButtonProperties(bubbleButtonFlashState); - bubbleButtonFlashState = !bubbleButtonFlashState; - }, 500); - } + writeButtonProperties(bubbleButtonFlashState); + bubbleButtonFlashState = !bubbleButtonFlashState; } // Called from the C++ scripting interface to show the bubble overlay diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 26ffb08796..d2e7d3ffc8 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -698,6 +698,9 @@ Window.location = "hifi://BankOfHighFidelity"; } break; + case 'wallet_availableUpdatesReceived': + // NOP + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 18b194dd3a..4f041d3067 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -43,6 +43,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.totalVariance = 0; this.highVarianceCount = 0; this.veryhighVarianceCount = 0; + this.orderedPluginNames = []; this.tabletID = null; this.blacklist = []; this.pointerManager = new PointerManager(); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 39841607cd..1cf057338e 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -14,7 +14,7 @@ PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI - Picks, makeLaserLockInfo Xform, makeLaserParams + Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -295,6 +295,7 @@ Script.include("/~/system/libraries/Xform.js"); this.actionID = null; this.grabbedThingID = null; this.targetObject = null; + this.potentialEntityWithContextOverlay = false; }; this.updateRecommendedArea = function() { @@ -375,7 +376,7 @@ Script.include("/~/system/libraries/Xform.js"); return true; } return false; - } + }; this.isReady = function (controllerData) { if (HMD.active) { @@ -449,11 +450,16 @@ Script.include("/~/system/libraries/Xform.js"); if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { if (controllerData.triggerClicks[this.hand]) { var entityID = rayPickInfo.objectID; + var targetProps = Entities.getEntityProperties(entityID, [ "dynamic", "shapeType", "position", "rotation", "dimensions", "density", - "userData", "locked", "type" + "userData", "locked", "type", "href" ]); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } this.targetObject = new TargetObject(entityID, targetProps); this.targetObject.parentProps = getEntityParents(targetProps); @@ -462,15 +468,17 @@ Script.include("/~/system/libraries/Xform.js"); Script.clearTimeout(this.contextOverlayTimer); } this.contextOverlayTimer = false; - if (entityID !== this.entityWithContextOverlay) { + if (entityID === this.entityWithContextOverlay) { this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } var targetEntity = this.targetObject.getTargetEntity(); entityID = targetEntity.id; targetProps = targetEntity.props; - if (entityIsGrabbable(targetProps)) { + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { if (!entityIsDistanceGrabbable(targetProps)) { this.targetObject.makeDynamic(); } @@ -480,7 +488,8 @@ Script.include("/~/system/libraries/Xform.js"); this.grabbedDistance = rayPickInfo.distance; } - if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && otherFarGrabModule.distanceHolding) { + if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && + otherFarGrabModule.distanceHolding) { this.prepareDistanceRotatingData(controllerData); this.distanceRotate(otherFarGrabModule); } else { diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js new file mode 100644 index 0000000000..59ce79cfd1 --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js @@ -0,0 +1,95 @@ +"use strict"; + +// nearGrabHyperLinkEntity.js +// +// Created by Dante Ruiz on 03/02/2018 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule, + propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, + Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, + TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, AddressManager +*/ + +(function() { + Script.include("/~/system/libraries/controllerDispatcherUtils.js"); + Script.include("/~/system/libraries/controllers.js"); + + function NearGrabHyperLinkEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.hyperlink = ""; + + this.parameters = makeDispatcherModuleParameters( + 485, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + + this.getTargetProps = function(controllerData) { + var nearbyEntitiesProperties = controllerData.nearbyEntityProperties[this.hand]; + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + for (var i = 0; i < nearbyEntitiesProperties.length; i++) { + var props = nearbyEntitiesProperties[i]; + if (props.distance > NEAR_GRAB_RADIUS * sensorScaleFactor) { + continue; + } + if (props.href !== "" && props.href !== undefined) { + return props; + } + } + return null; + }; + + this.isReady = function(controllerData) { + this.targetEntityID = null; + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && + controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { + return makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.hyperlink = targetProps.href; + this.targetEntityID = targetProps.id; + return makeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function(controllerData) { + if ((controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && + controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) || this.hyperlink === "") { + return makeRunningValues(false, [], []); + } + + if (controllerData.triggerClicks[this.hand] || + controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { + AddressManager.handleLookupString(this.hyperlink); + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + } + + var leftNearGrabHyperLinkEntity = new NearGrabHyperLinkEntity(LEFT_HAND); + var rightNearGrabHyperLinkEntity = new NearGrabHyperLinkEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearGrabHyperLink", leftNearGrabHyperLinkEntity); + enableDispatcherModule("RightNearGrabHyperLink", rightNearGrabHyperLinkEntity); + + function cleanup() { + disableDispatcherModule("LeftNearGrabHyperLink"); + disableDispatcherModule("RightNearGrabHyperLink"); + + } + + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 1eb30bbefd..8db8e29f37 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -31,7 +31,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/scaleAvatar.js", "controllerModules/hudOverlayPointer.js", "controllerModules/mouseHMD.js", - "controllerModules/scaleEntity.js" + "controllerModules/scaleEntity.js", + "controllerModules/nearGrabHyperLinkEntity.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 1171703847..776fd11e5c 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -8,7 +8,7 @@ // // Grab's physically moveable entities with the mouse, by applying a spring force. // -// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect +// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -444,6 +444,7 @@ Grabber.prototype.releaseEvent = function(event) { this.actionID = null; Pointers.setRenderState(this.mouseRayEntities, ""); + Pointers.setLockEndUUID(this.mouseRayEntities, null, false); var args = "mouse"; Entities.callEntityMethod(this.entityID, "releaseGrab", args); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 5e9aefcb07..fd7a488eb7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -116,11 +116,19 @@ var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; +var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; +var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; +var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; +var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; +var SETTING_EDIT_PREFIX = "Edit/"; + + var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; @@ -226,7 +234,7 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; -var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; + var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts @@ -280,7 +288,7 @@ var toolBar = (function () { position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); properties.position = position; - if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM) && + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } }); } else { @@ -338,7 +346,6 @@ var toolBar = (function () { if (systemToolbar) { systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); } - Menu.removeMenuItem(GRABBABLE_ENTITIES_MENU_CATEGORY, GRABBABLE_ENTITIES_MENU_ITEM); } var buttonHandlers = {}; // only used to tablet mode @@ -409,6 +416,12 @@ var toolBar = (function () { // default: // shapeType = "uv"; //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }) + } var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; if (materialURL) { @@ -416,7 +429,8 @@ var toolBar = (function () { type: "Material", materialURL: materialURL, //materialMappingMode: materialMappingMode, - priority: DEFAULT_LAYERED_MATERIAL_PRIORITY + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData }); } } @@ -777,9 +791,12 @@ function findClickedEntity(event) { } var pickRay = Camera.computePickRay(event.x, event.y); - var overlayResult = Overlays.findRayIntersection(pickRay, true, getMainTabletIDs()); - if (overlayResult.intersects) { - return null; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; + } } var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking @@ -968,8 +985,13 @@ function mouseReleaseEvent(event) { function wasTabletClicked(event) { var rayPick = Camera.computePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs()); - return result.intersects; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length === 0) { + return false; + } else { + var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs()); + return result.intersects; + } } function mouseClickEvent(event) { @@ -1135,34 +1157,35 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: GRABBABLE_ENTITIES_MENU_CATEGORY, - menuItemName: GRABBABLE_ENTITIES_MENU_ITEM, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, afterItem: "Unparent Entity", isCheckable: true, - isChecked: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, true), grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", - menuItemName: "Allow Selecting of Large Models", - afterItem: GRABBABLE_ENTITIES_MENU_ITEM, + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, isCheckable: true, - isChecked: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true), grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", - menuItemName: "Allow Selecting of Small Models", - afterItem: "Allow Selecting of Large Models", + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, isCheckable: true, - isChecked: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true), grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", - menuItemName: "Allow Selecting of Lights", - afterItem: "Allow Selecting of Small Models", + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false), grouping: "Advanced" }); Menu.addMenuItem({ @@ -1267,6 +1290,12 @@ Script.scriptEnding.connect(function () { Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)); + + progressDialog.cleanup(); cleanupModelMenus(); tooltip.cleanup(); @@ -2039,7 +2068,7 @@ var PropertiesTool = function (opts) { parentSelectedEntities(); } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if (data.type === 'saveUserData') { + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; Entities.editEntity(actualID, data.properties); diff --git a/scripts/system/help.js b/scripts/system/help.js index e29fc59e59..aaeb82721c 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -40,7 +40,7 @@ } function onScreenChanged(type, url) { - onHelpScreen = type === "Web" && url.startsWith(HELP_URL); + onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0); button.editProperties({ isActive: onHelpScreen }); } diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 7664ae8714..6c1931932a 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1387,12 +1387,14 @@ input#reset-to-natural-dimensions { margin-top: 48px; } -#userdata-clear{ +#userdata-clear, +#materialdata-clear { margin-bottom: 10px; } -#static-userdata { +#static-userdata, +#static-materialData { display: none; z-index: 99; position: absolute; @@ -1403,7 +1405,8 @@ input#reset-to-natural-dimensions { background-color: #2e2e2e; } -#userdata-saved { +#userdata-saved, +#materialData-saved { margin-top:5px; font-size:16px; display:none; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index c53a2fa5bd..8647dca035 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -42,25 +42,24 @@ -
@@ -293,7 +292,6 @@
-
BehaviorM @@ -315,6 +313,10 @@ +
+ + +
@@ -365,8 +367,6 @@
- -
LightM @@ -400,7 +400,6 @@
-
ModelM @@ -484,7 +483,6 @@
-
ZoneM @@ -532,6 +530,10 @@
+
+ + +
Skybox @@ -676,7 +678,7 @@ min="-1000" max="50000" step="10"> -
+
@@ -779,6 +781,20 @@ + +
+ +

+
+ + + + Saved! +
+
+
+ +
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2b29fbf041..4b6329db44 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -66,6 +66,7 @@ function enableProperties() { if (elLocked.checked === false) { removeStaticUserData(); + removeStaticMaterialData(); } } @@ -78,8 +79,13 @@ function disableProperties() { } var elLocked = document.getElementById("property-locked"); - if ($('#userdata-editor').css('display') === "block" && elLocked.checked === true) { - showStaticUserData(); + if (elLocked.checked === true) { + if ($('#userdata-editor').css('display') === "block") { + showStaticUserData(); + } + if ($('#materialdata-editor').css('display') === "block") { + showStaticMaterialData(); + } } } @@ -321,22 +327,19 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { } var keys = Object.keys(updateKeyPair); keys.forEach(function (key) { - delete parsedData[groupName][key]; if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") { if (updateKeyPair[key] instanceof Element) { if (updateKeyPair[key].type === "checkbox") { - if (updateKeyPair[key].checked !== defaults[key]) { - parsedData[groupName][key] = updateKeyPair[key].checked; - } + parsedData[groupName][key] = updateKeyPair[key].checked; } else { var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); - if (val !== defaults[key]) { - parsedData[groupName][key] = val; - } + parsedData[groupName][key] = val; } } else { parsedData[groupName][key] = updateKeyPair[key]; } + } else if (defaults[key] !== null && defaults[key] !== "null") { + parsedData[groupName][key] = defaults[key]; } }); if (Object.keys(parsedData[groupName]).length === 0) { @@ -359,15 +362,139 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal multiDataUpdater(groupName, val, userDataElement, def); } +function setMaterialDataFromEditor(noUpdate) { + var json = null; + try { + json = materialEditor.get(); + } catch (e) { + alert('Invalid JSON code - look for red X in your code ', +e); + } + if (json === null) { + return; + } else { + var text = materialEditor.getText(); + if (noUpdate === true) { + EventBridge.emitWebEvent( + JSON.stringify({ + id: lastEntityID, + type: "saveMaterialData", + properties: { + materialData: text + } + }) + ); + return; + } else { + updateProperty('materialData', text); + } + } +} + function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); } +var materialEditor = null; + +function createJSONMaterialEditor() { + var container = document.getElementById("materialdata-editor"); + var options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'materialData', + onModeChange: function() { + $('.jsoneditor-poweredBy').remove(); + }, + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + var currentJSONString = materialEditor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#materialdata-save').attr('disabled', false); + + + } + }; + materialEditor = new JSONEditor(container, options); +} + +function hideNewJSONMaterialEditorButton() { + $('#materialdata-new-editor').hide(); +} + +function showSaveMaterialDataButton() { + $('#materialdata-save').show(); +} + +function hideSaveMaterialDataButton() { + $('#materialdata-save').hide(); +} + +function showNewJSONMaterialEditorButton() { + $('#materialdata-new-editor').show(); +} + +function showMaterialDataTextArea() { + $('#property-material-data').show(); +} + +function hideMaterialDataTextArea() { + $('#property-material-data').hide(); +} + +function showStaticMaterialData() { + if (materialEditor !== null) { + $('#static-materialdata').show(); + $('#static-materialdata').css('height', $('#materialdata-editor').height()); + $('#static-materialdata').text(materialEditor.getText()); + } +} + +function removeStaticMaterialData() { + $('#static-materialdata').hide(); +} + +function setMaterialEditorJSON(json) { + materialEditor.set(json); + if (materialEditor.hasOwnProperty('expandAll')) { + materialEditor.expandAll(); + } +} + +function getMaterialEditorJSON() { + return materialEditor.get(); +} + +function deleteJSONMaterialEditor() { + if (materialEditor !== null) { + materialEditor.destroy(); + materialEditor = null; + } +} + +var savedMaterialJSONTimer = null; + +function saveJSONMaterialData(noUpdate) { + setMaterialDataFromEditor(noUpdate); + $('#materialdata-saved').show(); + $('#materialdata-save').attr('disabled', true); + if (savedMaterialJSONTimer !== null) { + clearTimeout(savedMaterialJSONTimer); + } + savedMaterialJSONTimer = setTimeout(function() { + $('#materialdata-saved').hide(); + + }, EDITOR_TIMEOUT_DURATION); +} + var editor = null; -var editorTimeout = null; -var lastJSONString = null; function createJSONEditor() { var container = document.getElementById("userdata-editor"); @@ -398,11 +525,6 @@ function createJSONEditor() { function hideNewJSONEditorButton() { $('#userdata-new-editor').hide(); - -} - -function hideClearUserDataButton() { - $('#userdata-clear').hide(); } function showSaveUserDataButton() { @@ -411,17 +533,10 @@ function showSaveUserDataButton() { function hideSaveUserDataButton() { $('#userdata-save').hide(); - } function showNewJSONEditorButton() { $('#userdata-new-editor').show(); - -} - -function showClearUserDataButton() { - $('#userdata-clear').show(); - } function showUserDataTextArea() { @@ -449,7 +564,6 @@ function setEditorJSON(json) { if (editor.hasOwnProperty('expandAll')) { editor.expandAll(); } - } function getEditorJSON() { @@ -487,12 +601,15 @@ function bindAllNonJSONEditorElements() { // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear") { + if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear" || e.target.id === "materialdata-new-editor" || e.target.id === "materialdata-clear") { return; } else { if ($('#userdata-editor').css('height') !== "0px") { saveJSONUserData(true); } + if ($('#materialdata-editor').css('height') !== "0px") { + saveJSONMaterialData(true); + } } }); } @@ -616,6 +733,8 @@ function loaded() { var elShape = document.getElementById("property-shape"); + var elCanCastShadow = document.getElementById("property-can-cast-shadow"); + var elLightSpotLight = document.getElementById("property-light-spot-light"); var elLightColor = document.getElementById("property-light-color"); var elLightColorRed = document.getElementById("property-light-color-red"); @@ -653,6 +772,10 @@ function loaded() { var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x"); var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y"); var elMaterialMappingRot = document.getElementById("property-material-mapping-rot"); + var elMaterialData = document.getElementById("property-material-data"); + var elClearMaterialData = document.getElementById("materialdata-clear"); + var elSaveMaterialData = document.getElementById("materialdata-save"); + var elNewJSONMaterialEditor = document.getElementById('materialdata-new-editor'); var elImageURL = document.getElementById("property-image-url"); @@ -687,6 +810,8 @@ function loaded() { var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); + var elZoneKeyLightCastShadows = document.getElementById("property-zone-key-light-cast-shadows"); + // Skybox var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); @@ -771,9 +896,15 @@ function loaded() { } else if (data.type === "update") { if (!data.selections || data.selections.length === 0) { - if (editor !== null && lastEntityID !== null) { - saveJSONUserData(true); - deleteJSONEditor(); + if (lastEntityID !== null) { + if (editor !== null) { + saveJSONUserData(true); + deleteJSONEditor(); + } + if (materialEditor !== null) { + saveJSONMaterialData(true); + deleteJSONMaterialEditor(); + } } elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; @@ -782,6 +913,7 @@ function loaded() { disableProperties(); } else if (data.selections && data.selections.length > 1) { deleteJSONEditor(); + deleteJSONMaterialEditor(); var selections = data.selections; var ids = []; @@ -814,8 +946,13 @@ function loaded() { } else { properties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { - saveJSONUserData(true); + if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + if (editor !== null) { + saveJSONUserData(true); + } + if (materialEditor !== null) { + saveJSONMaterialData(true); + } } var doSelectElement = lastEntityID === '"' + properties.id + '"'; @@ -844,7 +981,6 @@ function loaded() { elLocked.checked = properties.locked; - elName.value = properties.name; elVisible.checked = properties.visible; @@ -993,6 +1129,28 @@ function loaded() { hideNewJSONEditorButton(); } + var materialJson = null; + try { + materialJson = JSON.parse(properties.materialData); + } catch (e) { + // normal text + deleteJSONMaterialEditor(); + elMaterialData.value = properties.materialData; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + } + if (materialJson !== null) { + if (materialEditor === null) { + createJSONMaterialEditor(); + } + + setMaterialEditorJSON(materialJson); + showSaveMaterialDataButton(); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + } + elHyperlinkHref.value = properties.href; elDescription.value = properties.description; @@ -1011,6 +1169,12 @@ function loaded() { properties.color.green + "," + properties.color.blue + ")"; } + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + + elCanCastShadow.checked = properties.canCastShadow; + } + if (properties.type === "Model") { elModelURL.value = properties.modelURL; elShapeType.value = properties.shapeType; @@ -1060,7 +1224,6 @@ function loaded() { elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type === "Zone") { // Key light elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); @@ -1076,6 +1239,8 @@ function loaded() { elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); + elZoneKeyLightCastShadows.checked = properties.keyLight.castShadows; + // Skybox elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); @@ -1139,13 +1304,15 @@ function loaded() { // Show/hide sections as required showElements(document.getElementsByClassName('skybox-section'), elZoneSkyboxModeEnabled.checked); + showElements(document.getElementsByClassName('keylight-section'), elZoneKeyLightModeEnabled.checked); + showElements(document.getElementsByClassName('ambient-section'), elZoneAmbientLightModeEnabled.checked); + showElements(document.getElementsByClassName('haze-section'), elZoneHazeModeEnabled.checked); - } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); @@ -1176,12 +1343,22 @@ function loaded() { elMaterialMappingRot.value = properties.materialMappingRot.toFixed(2); } + // Only these types can cast a shadow + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + + showElements(document.getElementsByClassName('can-cast-shadow-section'), true); + } else { + showElements(document.getElementsByClassName('can-cast-shadow-section'), false); + } + if (properties.locked) { disableProperties(); elLocked.removeAttribute('disabled'); } else { enableProperties(); elSaveUserData.disabled = true; + elSaveMaterialData.disabled = true; } var activeElement = document.activeElement; @@ -1281,7 +1458,7 @@ function loaded() { if (elCloneable.checked) { elGrabbable.checked = false; } - userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); + userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); elCloneableDynamic.addEventListener('change', function(event) { userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1); @@ -1366,6 +1543,31 @@ function loaded() { showSaveUserDataButton(); }); + elClearMaterialData.addEventListener("click", function() { + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value); + }); + + elSaveMaterialData.addEventListener("click", function() { + saveJSONMaterialData(true); + }); + + elMaterialData.addEventListener('change', createEmitTextPropertyUpdateFunction('materialData')); + + elNewJSONMaterialEditor.addEventListener('click', function() { + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + var data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); + }); + var colorChangeFunction = createEmitColorPropertyUpdateFunction( 'color', elColorRed, elColorGreen, elColorBlue); elColorRed.addEventListener('change', colorChangeFunction); @@ -1432,6 +1634,8 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); + elCanCastShadow.addEventListener('change', createEmitCheckedPropertyUpdateFunction('canCastShadow')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); @@ -1590,6 +1794,9 @@ function loaded() { elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); + elZoneKeyLightCastShadows.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('keyLight', 'castShadows')); + // Skybox var skyboxModeChanged = createZoneComponentModeChangedFunction('skyboxMode', elZoneSkyboxModeInherit, elZoneSkyboxModeDisabled, elZoneSkyboxModeEnabled); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index fb49de1050..864c7d92b4 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -30,6 +30,7 @@ var userIsLoggedIn = false; var walletNeedsSetup = false; var marketplaceBaseURL = "https://highfidelity.com"; + var messagesWaiting = false; function injectCommonCode(isDirectoryPage) { @@ -205,16 +206,22 @@ purchasesElement.id = "purchasesButton"; purchasesElement.setAttribute('href', "#"); - purchasesElement.innerHTML = "My Purchases"; + purchasesElement.innerHTML = ""; + if (messagesWaiting) { + purchasesElement.innerHTML += " "; + } + purchasesElement.innerHTML += "My Purchases"; // FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same // line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px". + $('.navbar-brand').css('margin-right', '10px'); purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) + "px;position:relative;z-index:999;"; navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); $('#purchasesButton').on('click', function () { EventBridge.emitWebEvent(JSON.stringify({ type: "PURCHASES", - referrerURL: window.location.href + referrerURL: window.location.href, + hasUpdates: messagesWaiting })); }); } @@ -243,7 +250,7 @@ }); } - function buyButtonClicked(id, name, author, price, href, referrer) { + function buyButtonClicked(id, name, author, price, href, referrer, edition) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, @@ -251,7 +258,8 @@ itemPrice: price ? parseInt(price, 10) : 0, itemHref: href, referrer: referrer, - itemAuthor: author + itemAuthor: author, + itemEdition: edition })); } @@ -319,7 +327,8 @@ $(this).closest('.grid-item').find('.creator').find('.value').text(), $(this).closest('.grid-item').find('.item-cost').text(), $(this).attr('data-href'), - "mainPage"); + "mainPage", + -1); }); } @@ -410,7 +419,11 @@ } var cost = $('.item-cost').text(); - if (availability !== 'available') { + var isUpdating = window.location.href.indexOf('edition=') > -1; + var urlParams = new URLSearchParams(window.location.search); + if (isUpdating) { + purchaseButton.html('UPDATE FOR FREE'); + } else if (availability !== 'available') { purchaseButton.html('UNAVAILABLE (' + availability + ')'); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE = 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var properties = SelectionManager.savedProperties[toMove[i]]; if (!properties) { continue; } @@ -1622,7 +1638,7 @@ SelectionDisplay = (function() { y: 0, z: vector.z }); - Entities.editEntity(SelectionManager.selections[i], { + Entities.editEntity(toMove[i], { position: newPosition }); @@ -1647,11 +1663,11 @@ SelectionDisplay = (function() { mode: mode, onBegin: function(event, pickRay, pickResult) { if (direction === TRANSLATE_DIRECTION.X) { - pickNormal = { x:0, y:0, z:1 }; + pickNormal = { x:0, y:1, z:1 }; } else if (direction === TRANSLATE_DIRECTION.Y) { - pickNormal = { x:1, y:0, z:0 }; + pickNormal = { x:1, y:0, z:1 }; } else if (direction === TRANSLATE_DIRECTION.Z) { - pickNormal = { x:0, y:1, z:0 }; + pickNormal = { x:1, y:1, z:0 }; } var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; @@ -1695,7 +1711,6 @@ SelectionDisplay = (function() { onMove: function(event) { pickRay = generalComputePickRay(event.x, event.y); - // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); var vector = Vec3.subtract(newIntersection, lastPick); @@ -1713,7 +1728,7 @@ SelectionDisplay = (function() { var dotVector = Vec3.dot(vector, projectionVector); vector = Vec3.multiply(dotVector, projectionVector); vector = grid.snapToGrid(vector); - + var wantDebug = false; if (wantDebug) { print("translateUpDown... "); @@ -1721,9 +1736,19 @@ SelectionDisplay = (function() { Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); } - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var id = SelectionManager.selections[i]; + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var id = toMove[i]; var properties = SelectionManager.savedProperties[id]; var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { position: newPosition }); @@ -1743,6 +1768,13 @@ SelectionDisplay = (function() { }; }; + that.restoreAvatarCollisionsFromStretch = function() { + if (handleStretchCollisionOverride) { + Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, true); + handleStretchCollisionOverride = false; + } + } + // TOOL DEFINITION: HANDLE STRETCH TOOL function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) { var directionFor3DStretch = directionVec; @@ -1945,6 +1977,10 @@ SelectionDisplay = (function() { if (scaleHandle != null) { Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); } + if (Menu.isOptionChecked(AVATAR_COLLISIONS_OPTION)) { + Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, false); + handleStretchCollisionOverride = true; + } }; var onEnd = function(event, reason) { @@ -1954,11 +1990,12 @@ SelectionDisplay = (function() { if (scaleHandle != null) { Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); } + that.restoreAvatarCollisionsFromStretch(); pushCommandForSelections(); }; var onMove = function(event) { - var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || directionEnum === STRETCH_DIRECTION.ALL; + var proportional = (spaceMode === SPACE_WORLD) || directionEnum === STRETCH_DIRECTION.ALL; var position, dimensions, rotation; if (spaceMode === SPACE_LOCAL) { @@ -1999,10 +2036,10 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); - if (directionEnum === STRETCH_DIRECTION.ALL) { - var toCameraDistance = getDistanceToCamera(position); - var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; - changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); + if (directionEnum === STRETCH_DIRECTION.ALL) { + var toCameraDistance = getDistanceToCamera(position); + var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; + changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); } var newDimensions; @@ -2027,9 +2064,11 @@ SelectionDisplay = (function() { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - newDimensions.x = Math.max(newDimensions.x, STRETCH_MINIMUM_DIMENSION); - newDimensions.y = Math.max(newDimensions.y, STRETCH_MINIMUM_DIMENSION); - newDimensions.z = Math.max(newDimensions.z, STRETCH_MINIMUM_DIMENSION); + var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : + STRETCH_MINIMUM_DIMENSION; + newDimensions.x = Math.max(newDimensions.x, minimumDimension); + newDimensions.y = Math.max(newDimensions.y, minimumDimension); + newDimensions.z = Math.max(newDimensions.z, minimumDimension); var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); if (directionEnum === STRETCH_DIRECTION.ALL) { @@ -2089,10 +2128,10 @@ SelectionDisplay = (function() { directionVector = { x:1, y:1, z:1 }; selectedHandle = handleScaleLBNCube; } else if (directionEnum === SCALE_DIRECTION.RBN) { - directionVector = { x:1, y:1, z:-1 }; + directionVector = { x:-1, y:1, z:1 }; selectedHandle = handleScaleRBNCube; } else if (directionEnum === SCALE_DIRECTION.LBF) { - directionVector = { x:-1, y:1, z:1 }; + directionVector = { x:1, y:1, z:-1 }; selectedHandle = handleScaleLBFCube; } else if (directionEnum === SCALE_DIRECTION.RBF) { directionVector = { x:-1, y:1, z:-1 }; @@ -2101,10 +2140,10 @@ SelectionDisplay = (function() { directionVector = { x:1, y:-1, z:1 }; selectedHandle = handleScaleLTNCube; } else if (directionEnum === SCALE_DIRECTION.RTN) { - directionVector = { x:1, y:-1, z:-1 }; + directionVector = { x:-1, y:-1, z:1 }; selectedHandle = handleScaleRTNCube; } else if (directionEnum === SCALE_DIRECTION.LTF) { - directionVector = { x:-1, y:-1, z:1 }; + directionVector = { x:1, y:-1, z:-1 }; selectedHandle = handleScaleLTFCube; } else if (directionEnum === SCALE_DIRECTION.RTF) { directionVector = { x:-1, y:-1, z:-1 }; @@ -2146,8 +2185,19 @@ SelectionDisplay = (function() { // the selections center point. Otherwise, the rotation will be around the entities // registration point which does not need repositioning. var reposition = (SelectionManager.selections.length > 1); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toRotate = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toRotate.length; i++) { + var entityID = toRotate[i]; var initialProperties = SelectionManager.savedProperties[entityID]; var newProperties = { diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 19d4417a12..3be6ac0b00 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -2,15 +2,7 @@ var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html'); Grid = function(opts) { var that = {}; - - var colors = [ - { red: 0, green: 0, blue: 0 }, - { red: 255, green: 255, blue: 255 }, - { red: 255, green: 0, blue: 0 }, - { red: 0, green: 255, blue: 0 }, - { red: 0, green: 0, blue: 255 }, - ]; - var colorIndex = 0; + var gridColor = { red: 0, green: 0, blue: 0 }; var gridAlpha = 0.6; var origin = { x: 0, y: +MyAvatar.getJointPosition('LeftToeBase').y.toFixed(1) + 0.1, z: 0 }; var scale = 500; @@ -28,10 +20,10 @@ Grid = function(opts) { position: origin, visible: false, drawInFront: false, - color: colors[0], + color: gridColor, alpha: gridAlpha, minorGridEvery: minorGridEvery, - majorGridEvery: majorGridEvery, + majorGridEvery: majorGridEvery }); that.visible = false; @@ -39,26 +31,38 @@ Grid = function(opts) { that.getOrigin = function() { return origin; - } + }; + + that.getMinorIncrement = function() { + return minorGridEvery; + }; - that.getMinorIncrement = function() { return minorGridEvery; }; that.setMinorIncrement = function(value) { minorGridEvery = value; updateGrid(); - } - that.getMajorIncrement = function() { return majorGridEvery; }; + }; + + that.getMajorIncrement = function() { + return majorGridEvery; + }; + that.setMajorIncrement = function(value) { majorGridEvery = value; updateGrid(); }; - that.getColorIndex = function() { return colorIndex; }; - that.setColorIndex = function(value) { - colorIndex = value; + that.getColor = function() { + return gridColor; + }; + + that.setColor = function(value) { + gridColor = value; updateGrid(); }; - that.getSnapToGrid = function() { return snapToGrid; }; + that.getSnapToGrid = function() { + return snapToGrid; + }; that.setSnapToGrid = function(value) { snapToGrid = value; that.emitUpdate(); @@ -67,9 +71,11 @@ Grid = function(opts) { that.setEnabled = function(enabled) { that.enabled = enabled; updateGrid(); - } + }; - that.getVisible = function() { return that.visible; }; + that.getVisible = function() { + return that.visible; + }; that.setVisible = function(visible, noUpdate) { that.visible = visible; updateGrid(); @@ -77,7 +83,7 @@ Grid = function(opts) { if (!noUpdate) { that.emitUpdate(); } - } + }; that.snapToSurface = function(position, dimensions, registration) { if (!snapToGrid) { @@ -97,7 +103,7 @@ Grid = function(opts) { y: origin.y + (registration.y * dimensions.y), z: position.z }; - } + }; that.snapToGrid = function(position, majorOnly, dimensions, registration) { if (!snapToGrid) { @@ -121,7 +127,7 @@ Grid = function(opts) { position.z = Math.round(position.z / spacing) * spacing; return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin); - } + }; that.snapToSpacing = function(delta, majorOnly) { if (!snapToGrid) { @@ -133,11 +139,11 @@ Grid = function(opts) { var snappedDelta = { x: Math.round(delta.x / spacing) * spacing, y: Math.round(delta.y / spacing) * spacing, - z: Math.round(delta.z / spacing) * spacing, + z: Math.round(delta.z / spacing) * spacing }; return snappedDelta; - } + }; that.setPosition = function(newPosition, noUpdate) { @@ -157,7 +163,7 @@ Grid = function(opts) { majorGridEvery: majorGridEvery, gridSize: halfSize, visible: that.visible, - snapToGrid: snapToGrid, + snapToGrid: snapToGrid }); } }; @@ -183,8 +189,8 @@ Grid = function(opts) { majorGridEvery = data.majorGridEvery; } - if (data.colorIndex !== undefined) { - colorIndex = data.colorIndex; + if (data.gridColor) { + gridColor = data.gridColor; } if (data.gridSize) { @@ -196,7 +202,7 @@ Grid = function(opts) { } updateGrid(true); - } + }; function updateGrid(noUpdate) { Overlays.editOverlay(gridOverlay, { @@ -204,8 +210,8 @@ Grid = function(opts) { visible: that.visible && that.enabled, minorGridEvery: minorGridEvery, majorGridEvery: majorGridEvery, - color: colors[colorIndex], - alpha: gridAlpha, + color: gridColor, + alpha: gridAlpha }); if (!noUpdate) { @@ -219,7 +225,7 @@ Grid = function(opts) { that.addListener = function(callback) { that.onUpdate = callback; - } + }; Script.scriptEnding.connect(cleanup); updateGrid(); @@ -238,7 +244,7 @@ GridTool = function(opts) { var webView = null; webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - webView.setVisible = function(value) {}; + webView.setVisible = function(value) { }; horizontalGrid.addListener(function(data) { webView.emitScriptEvent(JSON.stringify(data)); @@ -250,8 +256,8 @@ GridTool = function(opts) { webView.webEventReceived.connect(function(data) { try { data = JSON.parse(data); - } catch(e) { - print("gridTool.js: Error parsing JSON: " + e.name + " data " + data) + } catch (e) { + print("gridTool.js: Error parsing JSON: " + e.name + " data " + data); return; } @@ -280,11 +286,11 @@ GridTool = function(opts) { that.addListener = function(callback) { listeners.push(callback); - } + }; that.setVisible = function(visible) { webView.setVisible(visible); - } + }; return that; }; diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 442a9f6d24..6afde85c29 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -356,7 +356,7 @@ getTabletWidthFromSettings = function () { var DEFAULT_TABLET_WIDTH = 0.4375; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var toolbarMode = tablet.toolbarMode; - var DEFAULT_TABLET_SCALE = 100; + var DEFAULT_TABLET_SCALE = 70; var tabletScalePercentage = DEFAULT_TABLET_SCALE; if (!toolbarMode) { if (HMD.active) { @@ -441,4 +441,4 @@ getMainTabletIDs = function () { tabletIDs.push(HMD.homeButtonID); } return tabletIDs; -}; \ No newline at end of file +}; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 8f51d88f2d..6e94fb238d 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -9,7 +9,8 @@ // /* global Tablet, Script, HMD, UserActivityLogger, Entities, Account, Wallet, ContextOverlay, Settings, Camera, Vec3, - Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow */ + Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow, Overlays, SoundCache, + DesktopPreviewProvider */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ var selectionDisplay = null; // for gridTool.js to ignore @@ -86,13 +87,24 @@ var selectionDisplay = null; // for gridTool.js to ignore } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; + var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; + var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg"; + var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg"; var marketplaceButton = tablet.addButton({ - icon: "icons/tablet-icons/market-i.svg", - activeIcon: "icons/tablet-icons/market-a.svg", + icon: NORMAL_ICON, + activeIcon: NORMAL_ACTIVE, text: "MARKET", sortOrder: 9 }); + function messagesWaiting(isWaiting) { + marketplaceButton.editProperties({ + icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), + activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) + }); + } + function onCanWriteAssetsChanged() { var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); tablet.emitScriptEvent(message); @@ -115,20 +127,42 @@ var selectionDisplay = null; // for gridTool.js to ignore var filterText; // Used for updating Purchases QML var onWalletScreen = false; + var onCommerceScreen = false; + var tabletShouldBeVisibleInSecondaryCamera = false; + + function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { + if (visibleInSecondaryCam) { + // if we're potentially showing the tablet, only do so if it was visible before + if (!tabletShouldBeVisibleInSecondaryCamera) { + return; + } + } else { + // if we're hiding the tablet, check to see if it was visible in the first place + tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); + } + + Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonHighlightIDtabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + } + function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH + var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); - if (!onWalletScreenNow && onWalletScreen) { // exiting wallet screen + if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen if (isHmdPreviewDisabledBySecurity) { DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", false); + setTabletVisibleInSecondaryCamera(true); isHmdPreviewDisabledBySecurity = false; } } + onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); @@ -175,6 +209,7 @@ var selectionDisplay = null; // for gridTool.js to ignore } } + var userHasUpdates = false; function sendCommerceSettings() { tablet.emitScriptEvent(JSON.stringify({ type: "marketplaces", @@ -183,7 +218,8 @@ var selectionDisplay = null; // for gridTool.js to ignore commerceMode: Settings.getValue("commerce", true), userIsLoggedIn: Account.loggedIn, walletNeedsSetup: Wallet.walletStatus === 1, - metaverseServerURL: Account.metaverseServerURL + metaverseServerURL: Account.metaverseServerURL, + messagesWaiting: userHasUpdates } })); } @@ -242,7 +278,7 @@ var selectionDisplay = null; // for gridTool.js to ignore var wearableDimensions = null; if (itemType === "contentSet") { - console.log("Item is a content set; codepath shouldn't go here.") + console.log("Item is a content set; codepath shouldn't go here."); return; } @@ -560,6 +596,10 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'purchases_goToMarketplaceClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'updateItemClicked': + tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition, + MARKETPLACES_INJECT_SCRIPT_URL); + break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -572,6 +612,7 @@ var selectionDisplay = null; // for gridTool.js to ignore if (!isHmdPreviewDisabled) { DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); + setTabletVisibleInSecondaryCamera(false); isHmdPreviewDisabledBySecurity = true; } break; @@ -579,6 +620,7 @@ var selectionDisplay = null; // for gridTool.js to ignore if (isHmdPreviewDisabledBySecurity) { DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", false); + setTabletVisibleInSecondaryCamera(true); isHmdPreviewDisabledBySecurity = false; } break; @@ -612,6 +654,11 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'sendMoney_sendPublicly': // NOP break; + case 'wallet_availableUpdatesReceived': + case 'purchases_availableUpdatesReceived': + userHasUpdates = message.numUpdates > 0; + messagesWaiting(userHasUpdates); + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 728760c1e7..ba37f6ee4e 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars:true, plusplus:true, forin:true*/ -/*global Script, Settings, Window, Controller, Overlays, SoundArray, LODManager, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ +/*global Script, Settings, Window, Controller, Overlays, SoundArray, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ // // notifications.js // Version 0.801 @@ -84,21 +84,18 @@ var NOTIFICATION_MENU_ITEM_POST = " Notifications"; var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; - var lodTextID = false; - var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications" + var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications"; var NotificationType = { UNKNOWN: 0, SNAPSHOT: 1, - LOD_WARNING: 2, - CONNECTION_REFUSED: 3, - EDIT_ERROR: 4, - TABLET: 5, - CONNECTION: 6, - WALLET: 7, + CONNECTION_REFUSED: 2, + EDIT_ERROR: 3, + TABLET: 4, + CONNECTION: 5, + WALLET: 6, properties: [ { text: "Snapshot" }, - { text: "Level of Detail" }, { text: "Connection Refused" }, { text: "Edit error" }, { text: "Tablet" }, @@ -153,10 +150,6 @@ // This handles the final dismissal of a notification after fading function dismiss(firstNoteOut, firstButOut, firstOut) { - if (firstNoteOut === lodTextID) { - lodTextID = false; - } - Overlays.deleteOverlay(firstNoteOut); Overlays.deleteOverlay(firstButOut); notifications.splice(firstOut, 1); @@ -418,9 +411,6 @@ function deleteNotification(index) { var notificationTextID = notifications[index]; - if (notificationTextID === lodTextID) { - lodTextID = false; - } Overlays.deleteOverlay(notificationTextID); Overlays.deleteOverlay(buttons[index]); notifications.splice(index, 1); @@ -674,20 +664,6 @@ } } - LODManager.LODDecreased.connect(function () { - var warningText = "\n" + - "Due to the complexity of the content, the \n" + - "level of detail has been decreased. " + - "You can now see: \n" + - LODManager.getLODFeedbackText(); - - if (lodTextID === false) { - lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); - } else { - Overlays.editOverlay(lodTextID, { text: warningText }); - } - }); - Controller.keyPressEvent.connect(keyPressEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/scripts/system/particle_explorer/hifi-entity-ui.js b/scripts/system/particle_explorer/hifi-entity-ui.js index 720aaee4d9..05b6ba6f75 100644 --- a/scripts/system/particle_explorer/hifi-entity-ui.js +++ b/scripts/system/particle_explorer/hifi-entity-ui.js @@ -439,8 +439,9 @@ HifiEntityUI.prototype = { $colPickContainer.colpick({ - colorScheme: 'dark', - layout: 'hex', + colorScheme: (group.layoutColorScheme === undefined ? 'dark' : group.layoutColorScheme), + layout: (group.layoutType === undefined ? 'hex' : group.layoutType), + submit: (group.useSubmitButton === undefined ? true : group.useSubmitButton), color: { r: domArray[0].value, g: domArray[1].value, diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html index 48cd4afa06..ab4c249cc3 100644 --- a/scripts/system/particle_explorer/particleExplorer.html +++ b/scripts/system/particle_explorer/particleExplorer.html @@ -27,6 +27,7 @@ + diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 971798828f..3598f30ee0 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -236,7 +236,10 @@ red: 255, green: 255, blue: 255 - } + }, + layoutType: "hex", + layoutColorScheme: "dark", + useSubmitButton: false }, { type: "Row" @@ -249,7 +252,10 @@ red: 0, green: 0, blue: 0 - } + }, + layoutType: "hex", + layoutColorScheme: "dark", + useSubmitButton: false }, { type: "Row" @@ -262,7 +268,10 @@ red: 255, green: 255, blue: 255 - } + }, + layoutType: "hex", + layoutColorScheme: "dark", + useSubmitButton: false }, { type: "Row" @@ -275,7 +284,10 @@ red: 255, green: 255, blue: 255 - } + }, + layoutType: "hex", + layoutColorScheme: "dark", + useSubmitButton: false }, { type: "Row" diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index ae8ef52a15..658d1c3ced 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -411,8 +411,6 @@ function snapshotUploaded(isError, reply) { } else { print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID); } - } else { - print(reply); } isUploadingPrintableStill = false; } diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 100d0e82ee..ee3dab7308 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -19,11 +19,11 @@ var tabletRezzed = false; var activeHand = null; var DEFAULT_WIDTH = 0.4375; - var DEFAULT_TABLET_SCALE = 100; + var DEFAULT_TABLET_SCALE = 70; var preMakeTime = Date.now(); var validCheckTime = Date.now(); var debugTablet = false; - var tabletScalePercentage = 100.0; + var tabletScalePercentage = 70.0; UIWebTablet = null; var MSECS_PER_SEC = 1000.0; var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone"; diff --git a/scripts/tutorials/entity_scripts/sit.js b/scripts/tutorials/entity_scripts/sit.js index 70456ea493..7afc9b30f4 100644 --- a/scripts/tutorials/entity_scripts/sit.js +++ b/scripts/tutorials/entity_scripts/sit.js @@ -155,11 +155,11 @@ MyAvatar.characterControllerEnabled = false; MyAvatar.hmdLeanRecenterEnabled = false; var roles = this.rolesToOverride(); - for (i in roles) { + for (var i = 0; i < roles.length; i++) { MyAvatar.overrideRoleAnimation(roles[i], ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); } - for (var i in OVERRIDEN_DRIVE_KEYS) { + for (i = 0; i < OVERRIDEN_DRIVE_KEYS.length; i++) { MyAvatar.disableDriveKey(OVERRIDEN_DRIVE_KEYS[i]); } @@ -190,12 +190,12 @@ if (Settings.getValue(SETTING_KEY) === this.entityID) { Settings.setValue(SETTING_KEY, ""); - for (var i in OVERRIDEN_DRIVE_KEYS) { + for (var i = 0; i < OVERRIDEN_DRIVE_KEYS.length; i++) { MyAvatar.enableDriveKey(OVERRIDEN_DRIVE_KEYS[i]); } var roles = this.rolesToOverride(); - for (i in roles) { + for (i = 0; i < roles.length; i++) { MyAvatar.restoreRoleAnimation(roles[i]); } MyAvatar.characterControllerEnabled = true; @@ -272,7 +272,7 @@ // Check if a drive key is pressed var hasActiveDriveKey = false; for (var i in OVERRIDEN_DRIVE_KEYS) { - if (MyAvatar.getRawDriveKey(OVERRIDEN_DRIVE_KEYS[i]) != 0.0) { + if (MyAvatar.getRawDriveKey(OVERRIDEN_DRIVE_KEYS[i]) !== 0.0) { hasActiveDriveKey = true; break; } diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp index 62aef37af6..1fa4862e53 100644 --- a/tests/gpu-test/src/TestWindow.cpp +++ b/tests/gpu-test/src/TestWindow.cpp @@ -114,14 +114,14 @@ void TestWindow::beginFrame() { // the rest of the renderDeferred inputs can be omitted #else - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::beginFrame", _renderArgs->_context, [&](gpu::Batch& batch) { batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); batch.clearDepthFramebuffer(1e4); batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); }); #endif - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::beginFrame", _renderArgs->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(_renderArgs->_viewport); batch.setStateScissorRect(_renderArgs->_viewport); batch.setProjectionTransform(_projectionMatrix); @@ -131,7 +131,7 @@ void TestWindow::beginFrame() { void TestWindow::endFrame() { #ifdef DEFERRED_LIGHTING RenderArgs* args = _renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::endFrame::begin", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; auto deferredFboColorDepthStencil = _prepareDeferredOutputs.get0()->getDeferredFramebufferDepthColor(); batch.setViewportTransform(args->_viewport); @@ -144,7 +144,7 @@ void TestWindow::endFrame() { _renderDeferred.run(_renderContext, _renderDeferredInputs); - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::endFrame::blit", _renderArgs->_context, [&](gpu::Batch& batch) { PROFILE_RANGE_BATCH(batch, "blit"); // Blit to screen auto framebufferCache = DependencyManager::get(); @@ -154,7 +154,7 @@ void TestWindow::endFrame() { }); #endif - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::endFrame::finish", _renderArgs->_context, [&](gpu::Batch& batch) { batch.resetStages(); }); _glContext.swapBuffers(this); diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 77ce015e3f..41d84ca026 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -147,7 +147,7 @@ class MyTestWindow : public TestWindow { return; } - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("main::renderFrame", _renderArgs->_context, [&](gpu::Batch& batch) { batch.setViewTransform(_camera); _renderArgs->_batch = &batch; _currentTest->renderTest(_currentTestId, _renderArgs); diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index 66ca28f7db..fd4d8d88dd 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -14,7 +14,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries( shared task networking animation - ktx image octree gl gpu gpu-gl + ktx image octree gl gpu ${PLATFORM_GL_BACKEND} render render-utils graphics fbx model-networking graphics-scripting entities entities-renderer audio avatars script-engine diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index ec56555f68..93672cc5a2 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -919,7 +919,7 @@ private: void render(RenderArgs* renderArgs) { auto& gpuContext = renderArgs->_context; gpuContext->beginFrame(); - gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("QTestWindow::render", gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); PROFILE_RANGE(render, __FUNCTION__); diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 62c970cab5..ce666065e3 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -563,12 +563,12 @@ private: void render() { auto& gpuContext = _renderThread._gpuContext; gpuContext->beginFrame(); - gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("RenderThread::render::begin", gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); auto framebuffer = DependencyManager::get()->getFramebuffer(); - gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("RenderThread::render", gpuContext, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(framebuffer); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(1, 0, 0, 1)); diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index 9eadc1dec2..4711dc4102 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -119,7 +119,7 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : nodeList->startThread(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); - connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainChanged(QUrl))); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ACClientApp::domainConnectionRefused); connect(nodeList.data(), &NodeList::nodeAdded, this, &ACClientApp::nodeAdded); @@ -169,7 +169,7 @@ void ACClientApp::domainConnectionRefused(const QString& reasonMessage, int reas qDebug() << "domainConnectionRefused"; } -void ACClientApp::domainChanged(const QString& domainHostname) { +void ACClientApp::domainChanged(QUrl domainURL) { if (_verbose) { qDebug() << "domainChanged"; } diff --git a/tools/ac-client/src/ACClientApp.h b/tools/ac-client/src/ACClientApp.h index d43e78eaeb..7a31b5ef5e 100644 --- a/tools/ac-client/src/ACClientApp.h +++ b/tools/ac-client/src/ACClientApp.h @@ -29,7 +29,7 @@ public: private slots: void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo); - void domainChanged(const QString& domainHostname); + void domainChanged(QUrl domainURL); void nodeAdded(SharedNodePointer node); void nodeActivated(SharedNodePointer node); void nodeKilled(SharedNodePointer node); diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp index dbc2ad53f6..526065b2f7 100644 --- a/tools/atp-client/src/ATPClientApp.cpp +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -158,7 +158,7 @@ ATPClientApp::ATPClientApp(int argc, char* argv[]) : nodeList->startThread(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); - connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainChanged(QUrl))); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ATPClientApp::domainConnectionRefused); connect(nodeList.data(), &NodeList::nodeAdded, this, &ATPClientApp::nodeAdded); @@ -227,7 +227,7 @@ void ATPClientApp::domainConnectionRefused(const QString& reasonMessage, int rea } } -void ATPClientApp::domainChanged(const QString& domainHostname) { +void ATPClientApp::domainChanged(QUrl domainURL) { if (_verbose) { qDebug() << "domainChanged"; } diff --git a/tools/atp-client/src/ATPClientApp.h b/tools/atp-client/src/ATPClientApp.h index 7ab4ec4a7a..63ee218e4b 100644 --- a/tools/atp-client/src/ATPClientApp.h +++ b/tools/atp-client/src/ATPClientApp.h @@ -31,7 +31,7 @@ public: private slots: void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo); - void domainChanged(const QString& domainHostname); + void domainChanged(QUrl domainURL); void nodeAdded(SharedNodePointer node); void nodeActivated(SharedNodePointer node); void nodeKilled(SharedNodePointer node); diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index a875f5676a..a2589bb760 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -5,7 +5,7 @@ project(${TARGET_NAME}) SET (CMAKE_AUTOUIC ON) SET (CMAKE_AUTOMOC ON) -setup_hifi_project (Core Widgets) +setup_hifi_project (Core Widgets Network) link_hifi_libraries () # FIX: Qt was built with -reduce-relocations diff --git a/tools/auto-tester/src/Downloader.cpp b/tools/auto-tester/src/Downloader.cpp new file mode 100644 index 0000000000..030aa95a19 --- /dev/null +++ b/tools/auto-tester/src/Downloader.cpp @@ -0,0 +1,32 @@ +// +// Downloader.cpp +// +// Created by Nissim Hadar on 1 Mar 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Downloader.h" + +Downloader::Downloader(QUrl imageUrl, QObject *parent) : QObject(parent) { + connect( + &_networkAccessManager, SIGNAL (finished(QNetworkReply*)), + this, SLOT (fileDownloaded(QNetworkReply*)) + ); + + QNetworkRequest request(imageUrl); + _networkAccessManager.get(request); +} + +void Downloader::fileDownloaded(QNetworkReply* reply) { + _downloadedData = reply->readAll(); + + //emit a signal + reply->deleteLater(); + emit downloaded(); +} + +QByteArray Downloader::downloadedData() const { + return _downloadedData; +} \ No newline at end of file diff --git a/tools/auto-tester/src/Downloader.h b/tools/auto-tester/src/Downloader.h new file mode 100644 index 0000000000..b0ad58fac5 --- /dev/null +++ b/tools/auto-tester/src/Downloader.h @@ -0,0 +1,48 @@ +// +// Downloader.h +// +// Created by Nissim Hadar on 1 Mar 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_downloader_h +#define hifi_downloader_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class Downloader : public QObject { +Q_OBJECT +public: + explicit Downloader(QUrl imageUrl, QObject *parent = 0); + + QByteArray downloadedData() const; + +signals: + void downloaded(); + + private slots: + void fileDownloaded(QNetworkReply* pReply); + +private: + QNetworkAccessManager _networkAccessManager; + QByteArray _downloadedData; +}; + +#endif // hifi_downloader_h \ No newline at end of file diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 94b95a5ab6..99f7d22c5f 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -17,13 +17,13 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { // Make sure the image is 8 bits per colour QImage::Format format = expectedImage.format(); - if (format != QImage::Format::Format_RGB32) { + if (format != QImage::Format::Format_ARGB32) { throw -1; } const int L = 255; // (2^number of bits per pixel) - 1 - const double K1{ 0.01 }; - const double K2{ 0.03 }; + const double K1 { 0.01 }; + const double K2 { 0.03 }; const double c1 = pow((K1 * L), 2); const double c2 = pow((K2 * L), 2); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 816eac7fbd..99f9025fdd 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -12,33 +12,28 @@ #include #include #include +#include +#include #include #include +#include "ui/AutoTester.h" +extern AutoTester* autoTester; + +#include + Test::Test() { - snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.*-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); - - expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg"); - mismatchWindow.setModal(true); } -bool Test::createTestResultsFolderPathIfNeeded(QString directory) { - // The test results folder is located in the root of the tests (i.e. for recursive test evaluation) - if (testResultsFolderPath == "") { - testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER; - QDir testResultsFolder(testResultsFolderPath); +bool Test::createTestResultsFolderPath(QString directory) { + QDateTime now = QDateTime::currentDateTime(); + testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); + QDir testResultsFolder(testResultsFolderPath); - if (testResultsFolder.exists()) { - testResultsFolder.removeRecursively(); - } - - // Create a new test results folder - return QDir().mkdir(testResultsFolderPath); - } else { - return true; - } + // Create a new test results folder + return QDir().mkdir(testResultsFolderPath); } void Test::zipAndDeleteTestResultsFolder() { @@ -60,9 +55,9 @@ void Test::zipAndDeleteTestResultsFolder() { index = 1; } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) { +bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) { progressBar->setMinimum(0); - progressBar->setMaximum(expectedImages.length() - 1); + progressBar->setMaximum(expectedImagesFullFilenames.length() - 1); progressBar->setValue(0); progressBar->setVisible(true); @@ -71,12 +66,13 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage const double THRESHOLD { 0.999 }; bool success{ true }; bool keepOn{ true }; - for (int i = 0; keepOn && i < expectedImages.length(); ++i) { + for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) { // First check that images are the same size - QImage resultImage(resultImages[i]); - QImage expectedImage(expectedImages[i]); + QImage resultImage(resultImagesFullFilenames[i]); + QImage expectedImage(expectedImagesFullFilenames[i]); + if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) { - messageBox.critical(0, "Internal error", "Images are not the same size"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); exit(-1); } @@ -84,21 +80,21 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage try { similarityIndex = imageComparer.compareImages(resultImage, expectedImage); } catch (...) { - messageBox.critical(0, "Internal error", "Image not in expected format"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Image not in expected format"); exit(-1); } if (similarityIndex < THRESHOLD) { TestFailure testFailure = TestFailure{ (float)similarityIndex, - expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) - QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image - QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image + expectedImagesFullFilenames[i].left(expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image }; mismatchWindow.setTestFailure(testFailure); - if (!interactiveMode) { + if (!isInteractiveMode) { appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); success = false; } else { @@ -131,20 +127,20 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { if (!QDir().exists(testResultsFolderPath)) { - messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found"); exit(-1); } QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; if (!QDir().mkdir(failureFolderPath)) { - messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath); exit(-1); } ++index; QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); if (!descriptionFile.open(QIODevice::ReadWrite)) { - messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME); exit(-1); } @@ -164,60 +160,86 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te sourceFile = testFailure._pathname + testFailure._expectedImageFilename; destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } sourceFile = testFailure._pathname + testFailure._actualImageFilename; destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); } -void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { +void Test::startTestsEvaluation() { // Get list of JPEG images in folder, sorted by name - QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); - if (pathToImageDirectory == "") { + pathToTestResultsDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (pathToTestResultsDirectory == "") { return; } - // Leave if test results folder could not be created - if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) { + // Quit if test results folder could not be created + if (!createTestResultsFolderPath(pathToTestResultsDirectory)) { return; } - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + // Before any processing - all images are converted to PNGs, as this is the format stored on GitHub + QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", pathToTestResultsDirectory); + foreach(QString filename, sortedSnapshotFilenames) { + QStringList stringParts = filename.split("."); + copyJPGtoPNG( + pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg", + pathToTestResultsDirectory + "/" + stringParts[0] + ".png" + ); - // Separate images into two lists. The first is the expected images, the second is the test results + QFile::remove(pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg"); + } + + // Create two lists. The first is the test results, the second is the expected images + // The expected images are represented as a URL to enable download from GitHub // Images that are in the wrong format are ignored. - QStringList expectedImages; - QStringList resultImages; - foreach(QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; - if (isInExpectedImageFilenameFormat(currentFilename)) { - expectedImages << fullCurrentFilename; - } else if (isInSnapshotFilenameFormat(currentFilename)) { - resultImages << fullCurrentFilename; + + QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory); + QStringList expectedImagesURLs; + + resultImagesFullFilenames.clear(); + expectedImagesFilenames.clear(); + expectedImagesFullFilenames.clear(); + + foreach(QString currentFilename, sortedTestResultsFilenames) { + QString fullCurrentFilename = pathToTestResultsDirectory + "/" + currentFilename; + if (isInSnapshotFilenameFormat("png", currentFilename)) { + resultImagesFullFilenames << fullCurrentFilename; + + QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); + + // Images are stored on GitHub as ExpectedImage_ddddd.png + // Extract the digits at the end of the filename (excluding the file extension) + QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); + QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; + + QString imageURLString("https://github.com/" + githubUser + "/hifi_tests/blob/" + gitHubBranch + "/" + + expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename + "?raw=true"); + + expectedImagesURLs << imageURLString; + + // The image retrieved from GitHub needs a unique name + QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI."); + + expectedImagesFilenames << expectedImageFilename; + expectedImagesFullFilenames << pathToTestResultsDirectory + "/" + expectedImageFilename; } } - // The number of images in each list should be identical - if (expectedImages.length() != resultImages.length()) { - messageBox.critical(0, - "Test failed", - "Found " + QString::number(resultImages.length()) + " images in directory" + - "\nExpected to find " + QString::number(expectedImages.length()) + " images" - ); - - exit(-1); - } - - bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar); + autoTester->downloadImages(expectedImagesURLs, pathToTestResultsDirectory, expectedImagesFilenames); +} +void Test::finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar) { + bool success = compareImageLists(interactiveMode, progressBar); + if (success) { messageBox.information(0, "Success", "All images are as expected"); } else { @@ -242,72 +264,31 @@ bool Test::isAValidDirectory(QString pathname) { return true; } -// Two criteria are used to decide if a folder contains valid test results. -// 1) a 'test'js' file exists in the folder -// 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) { - // Select folder to start recursing from - QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); - if (topLevelDirectory == "") { - return; +QString Test::extractPathFromTestsDown(QString fullPath) { + // `fullPath` includes the full path to the test. We need the portion below (and including) `tests` + QStringList pathParts = fullPath.split('/'); + int i{ 0 }; + while (i < pathParts.length() && pathParts[i] != "tests") { + ++i; } - // Leave if test results folder could not be created - if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) { - return; + if (i == pathParts.length()) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad testPathname"); + exit(-1); } - bool success{ true }; - QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname{ directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (!fileInfo.exists()) { - // Folder does not contain 'test.js' - continue; - } - - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); - - // Separate images into two lists. The first is the expected images, the second is the test results - // Images that are in the wrong format are ignored. - QStringList expectedImages; - QStringList resultImages; - foreach(QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = directory + "/" + currentFilename; - if (isInExpectedImageFilenameFormat(currentFilename)) { - expectedImages << fullCurrentFilename; - } else if (isInSnapshotFilenameFormat(currentFilename)) { - resultImages << fullCurrentFilename; - } - } - - if (expectedImages.length() != resultImages.length()) { - // Number of images doesn't match - continue; - } - - // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar); + QString partialPath; + for (int j = i; j < pathParts.length(); ++j) { + partialPath += "/" + pathParts[j]; } - if (success) { - messageBox.information(0, "Success", "All images are as expected"); - } else { - messageBox.information(0, "Failure", "One or more images are not as expected"); - } - - zipAndDeleteTestResultsFolder(); + return partialPath; } void Test::importTest(QTextStream& textStream, const QString& testPathname) { - textStream << "Script.include(\"" << "file:///" << testPathname + "?raw=true\");" << endl; + QString partialPath = extractPathFromTestsDown(testPathname); + textStream << "Script.include(\"" << "https://github.com/" << githubUser + << "/hifi_tests/blob/" << gitHubBranch << partialPath + "?raw=true\");" << endl; } // Creates a single script in a user-selected folder. @@ -319,11 +300,58 @@ void Test::createRecursiveScript() { return; } - QFile allTestsFilename(topLevelDirectory + "/" + "allTests.js"); + createRecursiveScript(topLevelDirectory, true); +} + +// This method creates a `testRecursive.js` script in every sub-folder. +void Test::createAllRecursiveScripts() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + createRecursiveScript(topLevelDirectory, false); + + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + // Only process directories that have sub-directories + bool hasNoSubDirectories{ true }; + QDirIterator it2(directory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it2.hasNext()) { + QString directory2 = it2.next(); + + // Only process directories + QDir dir; + if (isAValidDirectory(directory2)) { + hasNoSubDirectories = false; + break; + } + } + + if (!hasNoSubDirectories) { + createRecursiveScript(directory, false); + } + } + + messageBox.information(0, "Success", "Scripts have been created"); +} + +void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode) { + const QString recursiveTestsFilename("testRecursive.js"); + QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename); if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, - "Internal Error", - "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"" + "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\"" ); exit(-1); @@ -332,12 +360,11 @@ void Test::createRecursiveScript() { QTextStream textStream(&allTestsFilename); textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl; - textStream << "var autoTester = Script.require(\"https://github.com/highfidelity/hifi_tests/blob/master/tests/utils/autoTester.js?raw=true\");" << endl; + textStream << "var autoTester = Script.require(\"https://github.com/" + githubUser + "/hifi_tests/blob/" + + gitHubBranch + "/tests/utils/autoTester.js?raw=true\");" << endl; + textStream << "autoTester.enableRecursive();" << endl << endl; - // The main will call each test after the previous test is completed - // This is implemented with an interval timer that periodically tests if a - // running test has increment a testNumber variable that it received as an input. QVector testPathnames; // First test if top-level folder has a test.js file @@ -360,7 +387,7 @@ void Test::createRecursiveScript() { continue; } - const QString testPathname{ directory + "/" + TEST_FILENAME }; + const QString testPathname { directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -370,8 +397,8 @@ void Test::createRecursiveScript() { } } - if (testPathnames.length() <= 0) { - messageBox.information(0, "Failure", "No \"test.js\" files found"); + if (interactiveMode && testPathnames.length() <= 0) { + messageBox.information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found"); allTestsFilename.close(); return; } @@ -380,50 +407,45 @@ void Test::createRecursiveScript() { textStream << "autoTester.runRecursive();" << endl; allTestsFilename.close(); - messageBox.information(0, "Success", "Script has been created"); + + if (interactiveMode) { + messageBox.information(0, "Success", "Script has been created"); + } } void Test::createTest() { - // Rename files sequentially, as ExpectedResult_1.jpeg, ExpectedResult_2.jpg and so on + // Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on // Any existing expected result images will be deleted - QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); - if (pathToImageDirectory == "") { + QString imageSourceDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (imageSourceDirectory == "") { return; } - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + QString imageDestinationDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images", ".", QFileDialog::ShowDirsOnly); + if (imageDestinationDirectory == "") { + return; + } - int i = 1; + QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", imageSourceDirectory); + + int i = 1; + const int maxImages = pow(10, NUM_DIGITS); foreach (QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; - if (isInExpectedImageFilenameFormat(currentFilename)) { - if (!QFile::remove(fullCurrentFilename)) { + QString fullCurrentFilename = imageSourceDirectory + "/" + currentFilename; + if (isInSnapshotFilenameFormat("jpg", currentFilename)) { + if (i >= maxImages) { + messageBox.critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); + exit(-1); + } + QString newFilename = "ExpectedImage_" + QString::number(i - 1).rightJustified(5, '0') + ".png"; + QString fullNewFileName = imageDestinationDirectory + "/" + newFilename; + + try { + copyJPGtoPNG(fullCurrentFilename, fullNewFileName); + } catch (...) { messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted"); exit(-1); } - } else if (isInSnapshotFilenameFormat(currentFilename)) { - const int MAX_IMAGES = 100000; - if (i >= MAX_IMAGES) { - messageBox.critical(0, "Error", "More than 100,000 images not supported"); - exit(-1); - } - QString newFilename = "ExpectedImage_" + QString::number(i-1).rightJustified(5, '0') + ".jpg"; - QString fullNewFileName = pathToImageDirectory + "/" + newFilename; - - if (!imageDirectory.rename(fullCurrentFilename, newFilename)) { - if (!QFile::exists(fullCurrentFilename)) { - messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" - + fullCurrentFilename + " not found" - + "\nTest creation aborted" - ); - exit(-1); - } else { - messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" - + "unknown error" + "\nTest creation aborted" - ); - exit(-1); - } - } ++i; } } @@ -431,54 +453,393 @@ void Test::createTest() { messageBox.information(0, "Success", "Test images have been created"); } -void Test::deleteOldSnapshots() { +ExtractedText Test::getTestScriptLines(QString testFileName) { + ExtractedText relevantTextFromTest; + + QFile inputFile(testFileName); + inputFile.open(QIODevice::ReadOnly); + if (!inputFile.isOpen()) { + messageBox.critical(0, + "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to open \"" + testFileName + ); + } + + QTextStream stream(&inputFile); + QString line = stream.readLine(); + + // Name of test is the string in the following line: + // autoTester.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {... + const QString ws("\\h*"); //white-space character + const QString functionPerformName(ws + "autoTester" + ws + "\\." + ws + "perform"); + const QString quotedString("\\\".+\\\""); + const QString ownPath("Script" + ws + "\\." + ws + "resolvePath" + ws + "\\(" + ws + "\\\"\\.\\\"" + ws + "\\)"); + const QString functionParameter("function" + ws + "\\(testType" + ws + "\\)"); + QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*"); + QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); + + // Assert platform checks that test is running on the correct OS + const QString functionAssertPlatform(ws + "autoTester" + ws + "\\." + ws + "assertPlatform"); + const QString regexAssertPlatform(ws + functionAssertPlatform + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertPlatform = QRegularExpression(regexAssertPlatform); + + // Assert display checks that test is running on the correct display + const QString functionAssertDisplay(ws + "autoTester" + ws + "\\." + ws + "assertDisplay"); + const QString regexAssertDisplay(ws + functionAssertDisplay + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertDisplay = QRegularExpression(regexAssertDisplay); + + // Assert CPU checks that test is running on the correct type of CPU + const QString functionAssertCPU(ws + "autoTester" + ws + "\\." + ws + "assertCPU"); + const QString regexAssertCPU(ws + functionAssertCPU + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertCPU = QRegularExpression(regexAssertCPU); + + // Assert GPU checks that test is running on the correct type of GPU + const QString functionAssertGPU(ws + "autoTester" + ws + "\\." + ws + "assertGPU"); + const QString regexAssertGPU(ws + functionAssertGPU + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertGPU = QRegularExpression(regexAssertGPU); + + // Each step is either of the following forms: + // autoTester.addStepSnapshot("Take snapshot"... + // autoTester.addStep("Clean up after test"... + const QString functionAddStepSnapshotName(ws + "autoTester" + ws + "\\." + ws + "addStepSnapshot"); + const QString regexStepSnapshot(ws + functionAddStepSnapshotName + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot); + + const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep"); + const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ws + "\\)" + ".*"); + const QRegularExpression lineStep = QRegularExpression(regexStep); + + while (!line.isNull()) { + line = stream.readLine(); + if (lineContainingTitle.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + relevantTextFromTest.title = tokens[1]; + } else if (lineAssertPlatform.match(line).hasMatch()) { + QStringList platforms = line.split('"'); + relevantTextFromTest.platform = platforms[1]; + } else if (lineAssertDisplay.match(line).hasMatch()) { + QStringList displays = line.split('"'); + relevantTextFromTest.display = displays[1]; + } else if (lineAssertCPU.match(line).hasMatch()) { + QStringList cpus = line.split('"'); + relevantTextFromTest.cpu = cpus[1]; + } else if (lineAssertGPU.match(line).hasMatch()) { + QStringList gpus = line.split('"'); + relevantTextFromTest.gpu = gpus[1]; + } else if (lineStepSnapshot.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + QString nameOfStep = tokens[1]; + + Step *step = new Step(); + step->text = nameOfStep; + step->takeSnapshot = true; + relevantTextFromTest.stepList.emplace_back(step); + } else if (lineStep.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + QString nameOfStep = tokens[1]; + + Step *step = new Step(); + step->text = nameOfStep; + step->takeSnapshot = false; + relevantTextFromTest.stepList.emplace_back(step); + } + } + + inputFile.close(); + + return relevantTextFromTest; +} + +// Create an MD file for a user-selected test. +// The folder selected must contain a script named "test.js", the file produced is named "test.md" +void Test::createMDFile() { + // Folder selection + QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly); + if (testDirectory == "") { + return; + } + + createMDFile(testDirectory); +} + +void Test::createAllMDFiles() { // Select folder to start recursing from - QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select root folder for snapshot deletion", ".", QFileDialog::ShowDirsOnly); + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { return; } - // Recurse over folders + // First test if top-level folder has a test.js file + const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createMDFile(topLevelDirectory); + } + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); // Only process directories - QDir dir(directory); + QDir dir; if (!isAValidDirectory(directory)) { continue; } - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); - - // Delete any file that is a snapshot (NOT the Expected Images) - QStringList expectedImages; - QStringList resultImages; - foreach(QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = directory + "/" + currentFilename; - if (isInSnapshotFilenameFormat(currentFilename)) { - if (!QFile::remove(fullCurrentFilename)) { - messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nSnapshot deletion aborted"); - exit(-1); - } - } + const QString testPathname{ directory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createMDFile(directory); } } + + messageBox.information(0, "Success", "MD files have been created"); } -QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) { +void Test::createMDFile(QString testDirectory) { + // Verify folder contains test.js file + QString testFileName(testDirectory + "/" + TEST_FILENAME); + QFileInfo testFileInfo(testFileName); + if (!testFileInfo.exists()) { + messageBox.critical(0, "Error", "Could not find file: " + TEST_FILENAME); + return; + } + + ExtractedText testScriptLines = getTestScriptLines(testFileName); + + QString mdFilename(testDirectory + "/" + "test.md"); + QFile mdFile(mdFilename); + if (!mdFile.open(QIODevice::WriteOnly)) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); + exit(-1); + } + + QTextStream stream(&mdFile); + + //Test title + QString testName = testScriptLines.title; + stream << "# " << testName << "\n"; + + // Find the relevant part of the path to the test (i.e. from "tests" down + QString partialPath = extractPathFromTestsDown(testDirectory); + + stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; + + stream << "## Preconditions" << "\n"; + stream << "- In an empty region of a domain with editing rights." << "\n\n"; + + // Platform + QStringList platforms = testScriptLines.platform.split(" ");; + stream << "## Platforms\n"; + stream << "Run the test on each of the following platforms\n"; + for (int i = 0; i < platforms.size(); ++i) { + // Note that the platforms parameter may include extra spaces, these appear as empty strings in the list + if (platforms[i] != QString()) { + stream << " - " << platforms[i] << "\n"; + } + } + + // Display + QStringList displays = testScriptLines.display.split(" "); + stream << "## Displays\n"; + stream << "Run the test on each of the following displays\n"; + for (int i = 0; i < displays.size(); ++i) { + // Note that the displays parameter may include extra spaces, these appear as empty strings in the list + if (displays[i] != QString()) { + stream << " - " << displays[i] << "\n"; + } + } + + // CPU + QStringList cpus = testScriptLines.cpu.split(" "); + stream << "## Processors\n"; + stream << "Run the test on each of the following processors\n"; + for (int i = 0; i < cpus.size(); ++i) { + // Note that the cpus parameter may include extra spaces, these appear as empty strings in the list + if (cpus[i] != QString()) { + stream << " - " << cpus[i] << "\n"; + } + } + + // GPU + QStringList gpus = testScriptLines.gpu.split(" "); + stream << "## Graphics Cards\n"; + stream << "Run the test on graphics cards from each of the following vendors\n"; + for (int i = 0; i < gpus.size(); ++i) { + // Note that the gpus parameter may include extra spaces, these appear as empty strings in the list + if (gpus[i] != QString()) { + stream << " - " << gpus[i] << "\n"; + } + } + + stream << "## Steps\n"; + stream << "Press space bar to advance step by step\n\n"; + + int snapShotIndex { 0 }; + for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) { + stream << "### Step " << QString::number(i + 1) << "\n"; + stream << "- " << testScriptLines.stepList[i]->text << "\n"; + if (testScriptLines.stepList[i]->takeSnapshot) { + stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n"; + ++snapShotIndex; + } + } + + mdFile.close(); +} + +void Test::createTestsOutline() { + QString testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", ".", QFileDialog::ShowDirsOnly); + if (testsRootDirectory == "") { + return; + } + + const QString testsOutlineFilename { "testsOutline.md" }; + QString mdFilename(testsRootDirectory + "/" + testsOutlineFilename); + QFile mdFile(mdFilename); + if (!mdFile.open(QIODevice::WriteOnly)) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); + exit(-1); + } + + QTextStream stream(&mdFile); + + //Test title + stream << "# Outline of all tests\n"; + stream << "Directories with an appended (*) have an automatic test\n\n"; + + // We need to know our current depth, as this isn't given by QDirIterator + int rootDepth { testsRootDirectory.count('/') }; + + // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file + QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + // Ignore the utils directory + if (directory.right(5) == "utils") { + continue; + } + + // The prefix is the MarkDown prefix needed for correct indentation + // It consists of 2 spaces for each level of indentation, folled by a dash sign + int currentDepth = directory.count('/') - rootDepth; + QString prefix = QString(" ").repeated(2 * currentDepth - 1) + " - "; + + // The directory name appears after the last slash (we are assured there is at least 1). + QString directoryName = directory.right(directory.length() - directory.lastIndexOf("/") - 1); + + // autoTester is run on a clone of the repository. We use relative paths, so we can use both local disk and GitHub + // For a test in "D:/GitHub/hifi_tests/tests/content/entity/zone/ambientLightInheritance" the + // GitHub URL is "./content/entity/zone/ambientLightInheritance?raw=true" + QString partialPath = directory.right(directory.length() - (directory.lastIndexOf("/tests/") + QString("/tests").length() + 1)); + QString url = "./" + partialPath; + + stream << prefix << "[" << directoryName << "](" << url << "?raw=true" << ")"; + QFileInfo fileInfo1(directory + "/test.md"); + if (fileInfo1.exists()) { + stream << " [(test description)](" << url << "/test.md)"; + } + + QFileInfo fileInfo2(directory + "/" + TEST_FILENAME); + if (fileInfo2.exists()) { + stream << " (*)"; + } + stream << "\n"; + } + + mdFile.close(); + + messageBox.information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created"); +} + +void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) { + QFile::remove(destinationPNGFullFilename); + + QImageReader reader; + reader.setFileName(sourceJPGFullFilename); + + QImage image = reader.read(); + + QImageWriter writer; + writer.setFileName(destinationPNGFullFilename); + writer.write(image); +} + +QStringList Test::createListOfAll_imagesInDirectory(QString imageFormat, QString pathToImageDirectory) { imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; - nameFilters << "*.jpg"; + nameFilters << "*." + imageFormat; return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); } -// Use regular expressions to check if files are in specific format -bool Test::isInSnapshotFilenameFormat(QString filename) { - return (snapshotFilenameFormat.match(filename).hasMatch()); +// Snapshots are files in the following format: +// Filename contains no periods (excluding period before exception +// Filename (i.e. without extension) contains _tests_ (this is based on all test scripts being within the tests folder +// Last 5 characters in filename are digits +// Extension is jpg +bool Test::isInSnapshotFilenameFormat(QString imageFormat, QString filename) { + QStringList filenameParts = filename.split("."); + + bool filnameHasNoPeriods = (filenameParts.size() == 2); + bool contains_tests = filenameParts[0].contains("_tests_"); + + bool last5CharactersAreDigits; + filenameParts[0].right(5).toInt(&last5CharactersAreDigits, 10); + + bool extensionIsIMAGE_FORMAT = (filenameParts[1] == imageFormat); + + return (filnameHasNoPeriods && contains_tests && last5CharactersAreDigits && extensionIsIMAGE_FORMAT); } -bool Test::isInExpectedImageFilenameFormat(QString filename) { - return (expectedImageFilenameFormat.match(filename).hasMatch()); -} \ No newline at end of file +// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is +// D:/GitHub/hifi-tests/tests/content/entity/zone/create +// This method assumes the filename is in the correct format +QString Test::getExpectedImageDestinationDirectory(QString filename) { + QString filenameWithoutExtension = filename.split(".")[0]; + QStringList filenameParts = filenameWithoutExtension.split("_"); + + QString result = filenameParts[0] + ":"; + + for (int i = 1; i < filenameParts.length() - 1; ++i) { + result += "/" + filenameParts[i]; + } + + return result; +} + +// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the source directory on GitHub +// is ...tests/content/entity/zone/create +// This is used to create the full URL +// This method assumes the filename is in the correct format +QString Test::getExpectedImagePartialSourceDirectory(QString filename) { + QString filenameWithoutExtension = filename.split(".")[0]; + QStringList filenameParts = filenameWithoutExtension.split("_"); + + // Note that the bottom-most "tests" folder is assumed to be the root + // This is required because the tests folder is named hifi_tests + int i { filenameParts.length() - 1 }; + while (i >= 0 && filenameParts[i] != "tests") { + --i; + } + + if (i < 0) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad filename"); + exit(-1); + } + + QString result = filenameParts[i]; + + for (int j = i + 1; j < filenameParts.length() - 1; ++j) { + result += "/" + filenameParts[j]; + } + + return result; +} diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 3177df4d47..e69459fef2 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -19,31 +19,61 @@ #include "ImageComparer.h" #include "ui/MismatchWindow.h" +class Step { +public: + QString text; + bool takeSnapshot; +}; + +using StepList = std::vector; + +class ExtractedText { +public: + QString title; + QString platform; + QString display; + QString cpu; + QString gpu; + StepList stepList; +}; + class Test { public: Test(); - void evaluateTests(bool interactiveMode, QProgressBar* progressBar); - void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar); + void startTestsEvaluation(); + void finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar); + void createRecursiveScript(); + void createAllRecursiveScripts(); + void createRecursiveScript(QString topLevelDirectory, bool interactiveMode); + void createTest(); - void deleteOldSnapshots(); + void createMDFile(); + void createAllMDFiles(); + void createMDFile(QString topLevelDirectory); - bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar); + void createTestsOutline(); - QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); + bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); - bool isInSnapshotFilenameFormat(QString filename); - bool isInExpectedImageFilenameFormat(QString filename); + QStringList createListOfAll_imagesInDirectory(QString imageFormat, QString pathToImageDirectory); + + bool isInSnapshotFilenameFormat(QString imageFormat, QString filename); void importTest(QTextStream& textStream, const QString& testPathname); void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); - bool createTestResultsFolderPathIfNeeded(QString directory); + bool createTestResultsFolderPath(QString directory); void zipAndDeleteTestResultsFolder(); bool isAValidDirectory(QString pathname); + QString extractPathFromTestsDown(QString fullPath); + QString getExpectedImageDestinationDirectory(QString filename); + QString getExpectedImagePartialSourceDirectory(QString filename); + + void copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename); private: const QString TEST_FILENAME { "test.js" }; @@ -54,16 +84,28 @@ private: QDir imageDirectory; - QRegularExpression snapshotFilenameFormat; - QRegularExpression expectedImageFilenameFormat; - MismatchWindow mismatchWindow; ImageComparer imageComparer; - QString testResultsFolderPath { "" }; int index { 1 }; + + // Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit) + const int NUM_DIGITS { 5 }; + const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" }; + + QString pathToTestResultsDirectory; + QStringList expectedImagesFilenames; + QStringList expectedImagesFullFilenames; + QStringList resultImagesFullFilenames; + + // Used for accessing GitHub + const QString githubUser{ "highfidelity" }; + const QString gitHubBranch { "master" }; + const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" }; + + ExtractedText getTestScriptLines(QString testFileName); }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index 6e5e06b732..cd0ce22b13 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -10,11 +10,13 @@ #include #include "ui/AutoTester.h" +AutoTester* autoTester; + int main(int argc, char *argv[]) { QApplication application(argc, argv); - AutoTester autoTester; - autoTester.show(); + autoTester = new AutoTester(); + autoTester->show(); return application.exec(); } diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 2834ff81e0..21acfe9569 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -12,32 +12,91 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); - ui.checkBoxInteractiveMode->setChecked(true); - ui.progressBar->setVisible(false); + + test = new Test(); + + signalMapper = new QSignalMapper(); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); -} - -void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + test->startTestsEvaluation(); } void AutoTester::on_createRecursiveScriptButton_clicked() { - test.createRecursiveScript(); + test->createRecursiveScript(); +} + +void AutoTester::on_createAllRecursiveScriptsButton_clicked() { + test->createAllRecursiveScripts(); } void AutoTester::on_createTestButton_clicked() { - test.createTest(); + test->createTest(); } -void AutoTester::on_deleteOldSnapshotsButton_clicked() { - test.deleteOldSnapshots(); +void AutoTester::on_createMDFileButton_clicked() { + test->createMDFile(); +} + +void AutoTester::on_createAllMDFilesButton_clicked() { + test->createAllMDFiles(); +} + +void AutoTester::on_createTestsOutlineButton_clicked() { + test->createTestsOutline(); } void AutoTester::on_closeButton_clicked() { exit(0); -} \ No newline at end of file +} + +void AutoTester::downloadImage(const QUrl& url) { + downloaders.emplace_back(new Downloader(url, this)); + connect(downloaders[_index], SIGNAL (downloaded()), signalMapper, SLOT (map())); + + signalMapper->setMapping(downloaders[_index], _index); + + ++_index; +} + +void AutoTester::downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames) { + _directoryName = directoryName; + _filenames = filenames; + + _numberOfImagesToDownload = URLs.size(); + _numberOfImagesDownloaded = 0; + _index = 0; + + ui.progressBar->setMinimum(0); + ui.progressBar->setMaximum(_numberOfImagesToDownload - 1); + ui.progressBar->setValue(0); + ui.progressBar->setVisible(true); + + for (int i = 0; i < _numberOfImagesToDownload; ++i) { + QUrl imageURL(URLs[i]); + downloadImage(imageURL); + } + + connect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int))); +} + +void AutoTester::saveImage(int index) { + QPixmap pixmap; + pixmap.loadFromData(downloaders[index]->downloadedData()); + + QImage image = pixmap.toImage(); + image = image.convertToFormat(QImage::Format_ARGB32); + + QString fullPathname = _directoryName + "/" + _filenames[index]; + image.save(fullPathname, 0, 100); + + ++_numberOfImagesDownloaded; + + if (_numberOfImagesDownloaded == _numberOfImagesToDownload) { + test->finishTestsEvaluation(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + } else { + ui.progressBar->setValue(_numberOfImagesDownloaded); + } +} diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 35f609a89d..1788e97177 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -11,7 +11,10 @@ #define hifi_AutoTester_h #include +#include #include "ui_AutoTester.h" + +#include "../Downloader.h" #include "../Test.h" class AutoTester : public QMainWindow { @@ -19,19 +22,37 @@ class AutoTester : public QMainWindow { public: AutoTester(QWidget *parent = Q_NULLPTR); + void downloadImage(const QUrl& url); + void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); private slots: void on_evaluateTestsButton_clicked(); - void on_evaluateTestsRecursivelyButton_clicked(); void on_createRecursiveScriptButton_clicked(); - void on_createTestButton_clicked(); - void on_deleteOldSnapshotsButton_clicked(); + void on_createAllRecursiveScriptsButton_clicked(); + void on_createTestButton_clicked(); + void on_createMDFileButton_clicked(); + void on_createAllMDFilesButton_clicked(); + void on_createTestsOutlineButton_clicked(); void on_closeButton_clicked(); + void saveImage(int index); + private: Ui::AutoTesterClass ui; + Test* test; - Test test; + std::vector downloaders; + + // local storage for parameters - folder to store downloaded files in, and a list of their names + QString _directoryName; + QStringList _filenames; + + // Used to enable passing a parameter to slots + QSignalMapper* signalMapper; + + int _numberOfImagesToDownload; + int _numberOfImagesDownloaded; + int _index; }; #endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index d06255acf6..2eb1314481 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -7,7 +7,7 @@ 0 0 607 - 395 + 514 @@ -17,8 +17,8 @@ - 190 - 300 + 360 + 400 220 40 @@ -30,8 +30,8 @@ - 360 - 130 + 20 + 30 220 40 @@ -44,7 +44,7 @@ 20 - 75 + 285 220 40 @@ -57,7 +57,7 @@ 360 - 75 + 35 220 40 @@ -66,24 +66,11 @@ Create Recursive Script - - - - 20 - 130 - 220 - 40 - - - - Evaluate Tests Recursively - - 23 - 40 + 250 131 20 @@ -99,7 +86,7 @@ 20 - 190 + 340 255 23 @@ -108,17 +95,56 @@ 24 - + 360 - 240 + 100 220 40 - Delete Old Snapshots + Create all Recursive Scripts + + + + + + 20 + 80 + 220 + 40 + + + + Create MD file + + + + + + 20 + 130 + 220 + 40 + + + + Create all MD files + + + + + + 20 + 180 + 220 + 40 + + + + Create Tests Outline @@ -145,4 +171,4 @@ - \ No newline at end of file + diff --git a/tools/bake-tools/bake.py b/tools/bake-tools/bake.py new file mode 100644 index 0000000000..edbfdf9e9f --- /dev/null +++ b/tools/bake-tools/bake.py @@ -0,0 +1,112 @@ +import os, json, sys, shutil, subprocess, shlex, time +EXE = os.environ['HIFI_OVEN'] + +def getRelativePath(path1, path2, stop): + parts1 = path1.split('/'); + parts2 = path2.split('/'); + if len(parts1) <= len(parts2): + for part in parts1: + if part != stop and part != '': + index = parts2.index(part) + parts2.pop(index) + return os.path.join(*parts2) + +def listFiles(directory, extension): + items = os.listdir(directory) + fileList = [] + for f in items: + if f.endswith('.' + extension): + fileList.append(f) + return fileList + +def camelCaseString(string): + string = string.replace('-', ' ') + return ''.join(x for x in string.title() if not x.isspace()) + +def groupFiles(originalDirectory, newDirectory, files): + for file in files: + newPath = os.sep.join([newDirectory, file]) + originalPath = os.sep.join([originalDirectory, file]) + shutil.move(originalPath, newPath) + +def groupKTXFiles(directory, filePath): + baseFile = os.path.basename(filePath) + filename = os.path.splitext(baseFile)[0] + camelCaseFileName = camelCaseString(filename) + path = os.sep.join([directory, camelCaseFileName]) + files = listFiles(directory, 'ktx') + if len(files) > 0: + createDirectory(path) + groupFiles(directory, path, files) + + newFilePath = os.sep.join([path, baseFile+'.baked.fbx']) + originalFilePath = os.sep.join([directory, baseFile+'.baked.fbx']) + originalFilePath.strip() + shutil.move(originalFilePath, newFilePath) + +def bakeFile(filePath, outputDirectory, fileType): + createDirectory(outputDirectory) + cmd = EXE + ' -i ' + filePath + ' -o ' + outputDirectory + ' -t ' + fileType + args = shlex.split(cmd) + process = subprocess.Popen(cmd, stdout=False, stderr=False) + process.wait() + bakedFile = os.path.splitext(filePath)[0] + if fileType == 'fbx': + groupKTXFiles(outputDirectory, bakedFile) + +def bakeFilesInDirectory(directory, outputDirectory): + rootDirectory = os.path.basename(os.path.normpath(directory)) + for root, subFolders, filenames in os.walk(directory): + for filename in filenames: + appendPath = getRelativePath(directory, root, rootDirectory); + name, ext = os.path.splitext('file.txt') + if filename.endswith('.fbx'): + filePath = os.sep.join([root, filename]) + absFilePath = os.path.abspath(filePath) + outputFolder = os.path.join(outputDirectory, appendPath) + print "Baking file: " + filename + bakeFile(absFilePath, outputFolder, 'fbx') + elif os.path.basename(root) == 'skyboxes': + filePath = os.sep.join([root, filename]) + absFilePath = os.path.abspath(filePath) + outputFolder = os.path.join(outputDirectory, appendPath) + print "Baking file: " + filename + bakeType = os.path.splitext(filename)[1][1:] + bakeFile(absFilePath, outputFolder, bakeType) + else: + filePath = os.sep.join([root, filename]) + absFilePath = os.path.abspath(filePath) + outputFolder = os.path.join(outputDirectory, appendPath) + newFilePath = os.sep.join([outputFolder, filename]) + createDirectory(outputFolder) + print "moving file: " + filename + " to: " + outputFolder + shutil.copy(absFilePath, newFilePath) + +def createDirectory(directory): + if not os.path.exists(directory): + os.makedirs(directory) + +def checkIfExeExists(): + if not os.path.isfile(EXE) and os.access(EXE, os.X_OK): + print 'HIFI_OVEN evironment variable is not set' + sys.exit() + +def handleOptions(): + option = sys.argv[1] + if option == '--help' or option == '-h': + print 'Usage: bake.py INPUT_DIRECTORY[directory to bake] OUTPUT_DIRECTORY[directory to place backed files]' + print 'Note: Output directory will be created if directory does not exist' + sys.exit() + +def main(): + argsLength = len(sys.argv) + if argsLength == 3: + checkIfExeExists() + rootDirectory = sys.argv[1] + outputDirectory = os.path.abspath(sys.argv[2]) + createDirectory(outputDirectory) + bakeFilesInDirectory(rootDirectory, outputDirectory) + elif argsLength == 2: + handleOptions() + +main() diff --git a/tools/bake-tools/convertToRelativePaths.py b/tools/bake-tools/convertToRelativePaths.py new file mode 100644 index 0000000000..70a6a30cfb --- /dev/null +++ b/tools/bake-tools/convertToRelativePaths.py @@ -0,0 +1,95 @@ +import json, os, sys, gzip + +prefix = 'file:///~/' +MAP = {} +def createAssetMapping(assetDirectory): + baseDirectory = os.path.basename(os.path.normpath(assetDirectory)) + for root, subfolder, filenames in os.walk(assetDirectory): + for filename in filenames: + if not filename.endswith('.ktx') or os.path.basename(root) == 'skyboxes': + substring = os.path.commonprefix([assetDirectory, root]) + newPath = root.replace(substring, ''); + filePath = os.sep.join([newPath, filename]) + if filePath[0] == '\\': + filePath = filePath[1:] + finalPath = prefix + baseDirectory + '/' + filePath + finalPath = finalPath.replace('\\', '/') + file = os.path.splitext(filename)[0] + file = os.path.splitext(file)[0] + MAP[file] = finalPath + +def hasURL(prop): + if "URL" in prop: + return True + return False + + +def handleURL(url): + newUrl = url + if "atp:" in url: + baseFilename = os.path.basename(url) + filename = os.path.splitext(baseFilename)[0] + newUrl = MAP[filename] + print url + ' -> ' + newUrl + return newUrl + +def handleOptions(): + option = sys.argv[1] + if option == '--help' or option == '-h': + print 'Usage: convertToRelativePaths.py INPUT[json file you want to update the urls] INPUT[directory that the baked files are located in]' + sys.exit() + +def main(): + argsLength = len(sys.argv) + if argsLength == 3: + jsonFile = sys.argv[1] + assetDirectory = sys.argv[2] + inputFilename = os.path.basename(jsonFile); + absPath = os.path.abspath(assetDirectory) + finalPath = absPath.replace('\\', '/'); + gzipFile = finalPath + '/' + inputFilename + '.gz' + print gzipFile + createAssetMapping(assetDirectory) + f = open(jsonFile) + data = json.load(f) + f.close() + for entity in data['Entities']: + for prop in entity: + value = entity[prop] + if hasURL(prop): + value = handleURL(value) + if prop == "script": + value = handleURL(value) + if prop == "textures": + try: + tmp = json.loads(value) + for index in tmp: + tmp[index] = handleURL(tmp[index]) + value = json.dumps(tmp) + except: + value = handleURL(value) + + + if prop == "ambientLight": + for index in value: + value[index] = handleURL(value[index]) + + if prop == "skybox": + for index in value: + value[index] = handleURL(value[index]) + + if prop == "serverScripts": + value = handleURL(value) + + entity[prop] = value + + + jsonString = json.dumps(data) + jsonBytes= jsonString.encode('utf-8') + with gzip.GzipFile(gzipFile, 'w') as fout: # 4. gzip + fout.write(jsonBytes) + + elif argsLength == 2: + handleOptions() + +main() diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index bb556814e8..1f73f14b2b 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -24,13 +24,16 @@ exports.handlers = { '../../libraries/animation/src', '../../libraries/avatars/src', '../../libraries/controllers/src/controllers/', + '../../libraries/graphics-scripting/src/graphics-scripting/', '../../libraries/entities/src', + '../../libraries/model-networking/src/model-networking/', + '../../libraries/octree/src', '../../libraries/networking/src', + '../../libraries/physics/src', '../../libraries/pointers/src', - '../../libraries/render-utils/src', + '../../libraries/script-engine/src', '../../libraries/shared/src', '../../libraries/shared/src/shared', - '../../libraries/script-engine/src', ]; var exts = ['.h', '.cpp']; diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 549414cbab..71bb997303 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -18,4 +18,6 @@ elseif (APPLE) set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") endif() -install_beside_console() +if (BUILD_SERVER) + install_beside_console() +endif () diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 5af65c4dc0..35550cdca8 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -14,13 +14,15 @@ #include #include +#include "OvenCLIApplication.h" #include "ModelBakingLoggingCategory.h" -#include "Oven.h" #include "BakerCLI.h" #include "FBXBaker.h" +#include "JSBaker.h" #include "TextureBaker.h" -BakerCLI::BakerCLI(Oven* parent) : QObject(parent) { +BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { + } void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type) { @@ -33,6 +35,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& qDebug() << "Baking file type: " << type; static const QString MODEL_EXTENSION { "fbx" }; + static const QString SCRIPT_EXTENSION { "js" }; QString extension = type; @@ -43,6 +46,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& // check what kind of baker we should be creating bool isFBX = extension == MODEL_EXTENSION; + bool isScript = extension == SCRIPT_EXTENSION; bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1()); @@ -50,14 +54,22 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& // create our appropiate baker if (isFBX) { - _baker = std::unique_ptr { new FBXBaker(inputUrl, []() -> QThread* { return qApp->getNextWorkerThread(); }, outputPath) }; - _baker->moveToThread(qApp->getNextWorkerThread()); + _baker = std::unique_ptr { + new FBXBaker(inputUrl, + []() -> QThread* { return Oven::instance().getNextWorkerThread(); }, + outputPath) + }; + _baker->moveToThread(Oven::instance().getNextWorkerThread()); + } else if (isScript) { + _baker = std::unique_ptr { new JSBaker(inputUrl, outputPath) }; + _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else if (isSupportedImage) { _baker = std::unique_ptr { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; - _baker->moveToThread(qApp->getNextWorkerThread()); + _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; - QApplication::exit(OVEN_STATUS_CODE_FAIL); + QCoreApplication::exit(OVEN_STATUS_CODE_FAIL); + return; } // invoke the bake method on the baker thread @@ -81,5 +93,5 @@ void BakerCLI::handleFinishedBaker() { errorFile.close(); } } - QApplication::exit(exitCode); + QCoreApplication::exit(exitCode); } diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index 7d362eb898..bf33b625dd 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -14,11 +14,12 @@ #include #include +#include #include #include "Baker.h" -#include "Oven.h" +#include "OvenCLIApplication.h" static const int OVEN_STATUS_CODE_SUCCESS { 0 }; static const int OVEN_STATUS_CODE_FAIL { 1 }; @@ -27,10 +28,12 @@ static const int OVEN_STATUS_CODE_ABORT { 2 }; static const QString OVEN_ERROR_FILENAME = "errors.txt"; class BakerCLI : public QObject { - Q_OBJECT + Q_OBJECT public: - BakerCLI(Oven* parent); + BakerCLI(OvenCLIApplication* parent); + +public slots: void bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type = QString::null); private slots: diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index edc2492e82..3c6799db88 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DomainBaker.h" + #include #include #include @@ -17,10 +19,9 @@ #include #include "Gzip.h" - #include "Oven.h" - -#include "DomainBaker.h" +#include "FBXBaker.h" +#include "OBJBaker.h" DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, const QString& baseOutputPath, const QUrl& destinationPath, @@ -167,15 +168,18 @@ void DomainBaker::enumerateEntities() { // check if the file pointed to by this URL is a bakeable model, by comparing extensions auto modelFileName = modelURL.fileName(); - static const QString BAKEABLE_MODEL_EXTENSION { ".fbx" }; + static const QString BAKEABLE_MODEL_FBX_EXTENSION { ".fbx" }; + static const QString BAKEABLE_MODEL_OBJ_EXTENSION { ".obj" }; static const QString BAKED_MODEL_EXTENSION = ".baked.fbx"; - bool isBakedFBX = modelFileName.endsWith(BAKED_MODEL_EXTENSION, Qt::CaseInsensitive); - bool isUnbakedFBX = modelFileName.endsWith(BAKEABLE_MODEL_EXTENSION, Qt::CaseInsensitive) && !isBakedFBX; + bool isBakedModel = modelFileName.endsWith(BAKED_MODEL_EXTENSION, Qt::CaseInsensitive); + bool isBakeableFBX = modelFileName.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive); + bool isBakeableOBJ = modelFileName.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive); + bool isBakeable = isBakeableFBX || isBakeableOBJ; - if (isUnbakedFBX || (_shouldRebakeOriginals && isBakedFBX)) { + if (isBakeable || (_shouldRebakeOriginals && isBakedModel)) { - if (isBakedFBX) { + if (isBakedModel) { // grab a URL to the original, that we assume is stored a directory up, in the "original" folder // with just the fbx extension qDebug() << "Re-baking original for" << modelURL; @@ -190,7 +194,7 @@ void DomainBaker::enumerateEntities() { modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); } - // setup an FBXBaker for this URL, as long as we don't already have one + // setup a ModelBaker for this URL, as long as we don't already have one if (!_modelBakers.contains(modelURL)) { auto filename = modelURL.fileName(); auto baseName = filename.left(filename.lastIndexOf('.')); @@ -199,12 +203,23 @@ void DomainBaker::enumerateEntities() { while (QDir(_contentOutputPath + subDirName).exists()) { subDirName = "/" + baseName + "-" + QString::number(i++); } - QSharedPointer baker { - new FBXBaker(modelURL, []() -> QThread* { - return qApp->getNextWorkerThread(); - }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), - &FBXBaker::deleteLater - }; + + QSharedPointer baker; + if (isBakeableFBX) { + baker = { + new FBXBaker(modelURL, []() -> QThread* { + return Oven::instance().getNextWorkerThread(); + }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), + &FBXBaker::deleteLater + }; + } else { + baker = { + new OBJBaker(modelURL, []() -> QThread* { + return Oven::instance().getNextWorkerThread(); + }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), + &OBJBaker::deleteLater + }; + } // make sure our handler is called when the baker is done connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker); @@ -214,7 +229,7 @@ void DomainBaker::enumerateEntities() { // move the baker to the baker thread // and kickoff the bake - baker->moveToThread(qApp->getNextWorkerThread()); + baker->moveToThread(Oven::instance().getNextWorkerThread()); QMetaObject::invokeMethod(baker.data(), "bake"); // keep track of the total number of baking entities @@ -285,7 +300,7 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { _skyboxBakers.insert(skyboxURL, skyboxBaker); // move the baker to a worker thread and kickoff the bake - skyboxBaker->moveToThread(qApp->getNextWorkerThread()); + skyboxBaker->moveToThread(Oven::instance().getNextWorkerThread()); QMetaObject::invokeMethod(skyboxBaker.data(), "bake"); // keep track of the total number of baking entities @@ -299,16 +314,16 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { } void DomainBaker::handleFinishedModelBaker() { - auto baker = qobject_cast(sender()); + auto baker = qobject_cast(sender()); if (baker) { if (!baker->hasErrors()) { // this FBXBaker is done and everything went according to plan - qDebug() << "Re-writing entity references to" << baker->getFBXUrl(); + qDebug() << "Re-writing entity references to" << baker->getModelURL(); // enumerate the QJsonRef values for the URL of this FBX from our multi hash of // entity objects needing a URL re-write - for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getFBXUrl())) { + for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getModelURL())) { // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL auto entity = entityValue.toObject(); @@ -317,7 +332,7 @@ void DomainBaker::handleFinishedModelBaker() { QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; // setup a new URL using the prefix we were passed - auto relativeFBXFilePath = baker->getBakedFBXFilePath().remove(_contentOutputPath); + auto relativeFBXFilePath = baker->getBakedModelFilePath().remove(_contentOutputPath); if (relativeFBXFilePath.startsWith("/")) { relativeFBXFilePath = relativeFBXFilePath.right(relativeFBXFilePath.length() - 1); } @@ -370,10 +385,10 @@ void DomainBaker::handleFinishedModelBaker() { } // remove the baked URL from the multi hash of entities needing a re-write - _entitiesNeedingRewrite.remove(baker->getFBXUrl()); + _entitiesNeedingRewrite.remove(baker->getModelURL()); // drop our shared pointer to this baker so that it gets cleaned up - _modelBakers.remove(baker->getFBXUrl()); + _modelBakers.remove(baker->getModelURL()); // emit progress to tell listeners how many models we have baked emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes); diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 6426af0710..e0286a51ff 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -61,7 +61,7 @@ private: QJsonArray _entities; - QHash> _modelBakers; + QHash> _modelBakers; QHash> _skyboxBakers; QMultiHash _entitiesNeedingRewrite; diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 69d2ef84ce..c3fec2d15e 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -9,35 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Oven.h" + #include #include -#include #include -#include "ui/OvenMainWindow.h" -#include "Oven.h" -#include "BakerCLI.h" +#include +#include +#include -static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; +Oven* Oven::_staticInstance { nullptr }; -static const QString CLI_INPUT_PARAMETER = "i"; -static const QString CLI_OUTPUT_PARAMETER = "o"; -static const QString CLI_TYPE_PARAMETER = "t"; - -Oven::Oven(int argc, char* argv[]) : - QApplication(argc, argv) -{ - // parse the command line parameters - QCommandLineParser parser; - - parser.addOptions({ - { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, - { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }, - { CLI_TYPE_PARAMETER, "Type of asset.", "type" } - }); - parser.addHelpOption(); - parser.process(*this); +Oven::Oven() { + _staticInstance = this; // enable compression in image library image::setColorTexturesCompressionEnabled(true); @@ -48,40 +34,35 @@ Oven::Oven(int argc, char* argv[]) : // setup our worker threads setupWorkerThreads(QThread::idealThreadCount()); - // check if we were passed any command line arguments that would tell us just to run without the GUI - if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) { - if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) { - BakerCLI* cli = new BakerCLI(this); - QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); - QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); - QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; - cli->bakeFile(inputUrl, outputUrl.toString(), type); - } else { - parser.showHelp(); - QApplication::quit(); - } - } else { - // setup the GUI - _mainWindow = new OvenMainWindow; - _mainWindow->show(); - } + // Initialize dependencies for OBJ Baker + DependencyManager::set(); + DependencyManager::set(false); } Oven::~Oven() { - // cleanup the worker threads - for (auto i = 0; i < _workerThreads.size(); ++i) { - _workerThreads[i]->quit(); - _workerThreads[i]->wait(); + DependencyManager::get()->cleanup(); + + // quit all worker threads and wait on them + for (auto& thread : _workerThreads) { + thread->quit(); } + + for (auto& thread: _workerThreads) { + thread->wait(); + } + + _staticInstance = nullptr; } void Oven::setupWorkerThreads(int numWorkerThreads) { + _workerThreads.reserve(numWorkerThreads); + for (auto i = 0; i < numWorkerThreads; ++i) { // setup a worker thread yet and add it to our concurrent vector - auto newThread = new QThread(this); + auto newThread = std::unique_ptr { new QThread }; newThread->setObjectName("Oven Worker Thread " + QString::number(i + 1)); - _workerThreads.push_back(newThread); + _workerThreads.push_back(std::move(newThread)); } } @@ -92,13 +73,13 @@ QThread* Oven::getNextWorkerThread() { // (for the FBX Baker Thread to have room), and cycle through them to hand a usable running thread back to our callers. auto nextIndex = ++_nextWorkerThreadIndex; - auto nextThread = _workerThreads[nextIndex % _workerThreads.size()]; + auto& nextThread = _workerThreads[nextIndex % _workerThreads.size()]; // start the thread if it isn't running yet if (!nextThread->isRunning()) { nextThread->start(); } - return nextThread; + return nextThread.get(); } diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 928ba4eb11..c1acc07efb 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -12,27 +12,19 @@ #ifndef hifi_Oven_h #define hifi_Oven_h -#include - -#include - #include +#include +#include -#if defined(qApp) -#undef qApp -#endif -#define qApp (static_cast(QCoreApplication::instance())) +class QThread; -class OvenMainWindow; - -class Oven : public QApplication { - Q_OBJECT +class Oven { public: - Oven(int argc, char* argv[]); + Oven(); ~Oven(); - OvenMainWindow* getMainWindow() const { return _mainWindow; } + static Oven& instance() { return *_staticInstance; } QThread* getNextWorkerThread(); @@ -40,11 +32,12 @@ private: void setupWorkerThreads(int numWorkerThreads); void setupFBXBakerThread(); - OvenMainWindow* _mainWindow; - QList _workerThreads; + std::vector> _workerThreads; - std::atomic _nextWorkerThreadIndex; + std::atomic _nextWorkerThreadIndex; int _numWorkerThreads; + + static Oven* _staticInstance; }; diff --git a/tools/oven/src/OvenCLIApplication.cpp b/tools/oven/src/OvenCLIApplication.cpp new file mode 100644 index 0000000000..2fb8ea03f2 --- /dev/null +++ b/tools/oven/src/OvenCLIApplication.cpp @@ -0,0 +1,50 @@ +// +// OvenCLIApplication.cpp +// tools/oven/src +// +// Created by Stephen Birarda on 2/20/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "BakerCLI.h" + +#include "OvenCLIApplication.h" + +static const QString CLI_INPUT_PARAMETER = "i"; +static const QString CLI_OUTPUT_PARAMETER = "o"; +static const QString CLI_TYPE_PARAMETER = "t"; + +OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : + QCoreApplication(argc, argv) +{ + // parse the command line parameters + QCommandLineParser parser; + + parser.addOptions({ + { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, + { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }, + { CLI_TYPE_PARAMETER, "Type of asset.", "type" } + }); + + parser.addHelpOption(); + parser.process(*this); + + if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) { + BakerCLI* cli = new BakerCLI(this); + QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); + QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); + QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; + QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl), + Q_ARG(QString, outputUrl.toString()), Q_ARG(QString, type)); + } else { + parser.showHelp(); + QCoreApplication::quit(); + } + +} diff --git a/tools/oven/src/OvenCLIApplication.h b/tools/oven/src/OvenCLIApplication.h new file mode 100644 index 0000000000..5d81166f69 --- /dev/null +++ b/tools/oven/src/OvenCLIApplication.h @@ -0,0 +1,27 @@ +// +// OvenCLIApplication.h +// tools/oven/src +// +// Created by Stephen Birarda on 2/20/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OvenCLIApplication_h +#define hifi_OvenCLIApplication_h + +#include + +#include "Oven.h" + +class OvenCLIApplication : public QCoreApplication, public Oven { + Q_OBJECT +public: + OvenCLIApplication(int argc, char* argv[]); + + static OvenCLIApplication* instance() { return dynamic_cast(QCoreApplication::instance()); } +}; + +#endif // hifi_OvenCLIApplication_h diff --git a/tools/oven/src/OvenGUIApplication.cpp b/tools/oven/src/OvenGUIApplication.cpp new file mode 100644 index 0000000000..33dc375634 --- /dev/null +++ b/tools/oven/src/OvenGUIApplication.cpp @@ -0,0 +1,19 @@ +// +// OvenGUIApplication.cpp +// tools/src/oven +// +// Created by Stephen Birarda on 2/20/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OvenGUIApplication.h" + +OvenGUIApplication::OvenGUIApplication(int argc, char* argv[]) : + QApplication(argc, argv) +{ + // setup the GUI + _mainWindow.show(); +} diff --git a/tools/oven/src/OvenGUIApplication.h b/tools/oven/src/OvenGUIApplication.h new file mode 100644 index 0000000000..7f5bbf3eaf --- /dev/null +++ b/tools/oven/src/OvenGUIApplication.h @@ -0,0 +1,33 @@ +// +// OvenGUIApplication.h +// tools/oven/src +// +// Created by Stephen Birarda on 2/20/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OvenGUIApplication_h +#define hifi_OvenGUIApplication_h + +#include + +#include "ui/OvenMainWindow.h" + +#include "Oven.h" + +class OvenGUIApplication : public QApplication, public Oven { + Q_OBJECT +public: + OvenGUIApplication(int argc, char* argv[]); + + static OvenGUIApplication* instance() { return dynamic_cast(QApplication::instance()); } + + OvenMainWindow* getMainWindow() { return &_mainWindow; } +private: + OvenMainWindow _mainWindow; +}; + +#endif // hifi_OvenGUIApplication_h diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index 3f4afe1f15..7009089402 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -8,7 +8,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -#include "Oven.h" +#include "OvenCLIApplication.h" +#include "OvenGUIApplication.h" #include #include @@ -20,6 +21,12 @@ int main (int argc, char** argv) { // init the settings interface so we can save and load settings Setting::init(); - Oven app(argc, argv); - return app.exec(); + // figure out if we're launching our GUI application or just the simple command line interface + if (argc > 1) { + OvenCLIApplication app { argc, argv }; + return app.exec(); + } else { + OvenGUIApplication app { argc, argv }; + return app.exec(); + } } diff --git a/tools/oven/src/ui/BakeWidget.cpp b/tools/oven/src/ui/BakeWidget.cpp index 9fb8f2f880..43f4c50328 100644 --- a/tools/oven/src/ui/BakeWidget.cpp +++ b/tools/oven/src/ui/BakeWidget.cpp @@ -11,8 +11,7 @@ #include -#include "../Oven.h" -#include "OvenMainWindow.h" +#include "../OvenGUIApplication.h" #include "BakeWidget.h" @@ -28,7 +27,7 @@ BakeWidget::~BakeWidget() { auto it = _bakers.begin(); while (it != _bakers.end()) { auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); resultsWindow->changeStatusForRow(resultRow, "Cancelled"); @@ -42,5 +41,5 @@ void BakeWidget::cancelButtonClicked() { auto stackedWidget = qobject_cast(parentWidget()); stackedWidget->removeWidget(this); - this->deleteLater(); + deleteLater(); } diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 41452c7283..bf79319458 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -21,8 +21,7 @@ #include #include -#include "../Oven.h" -#include "OvenMainWindow.h" +#include "../OvenGUIApplication.h" #include "DomainBakeWidget.h" @@ -223,14 +222,14 @@ void DomainBakeWidget::bakeButtonClicked() { connect(domainBaker.get(), &DomainBaker::bakeProgress, this, &DomainBakeWidget::handleBakerProgress); // move the baker to the next available Oven worker thread - auto nextThread = qApp->getNextWorkerThread(); + auto nextThread = Oven::instance().getNextWorkerThread(); domainBaker->moveToThread(nextThread); // kickoff the domain baker on its thread QMetaObject::invokeMethod(domainBaker.get(), "bake"); // add a pending row to the results window to show that this bake is in process - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); auto resultsRowName = _domainNameLineEdit->text().isEmpty() ? fileToBakeURL.fileName() : _domainNameLineEdit->text(); auto resultsRow = resultsWindow->addPendingResultRow(resultsRowName, outputDirectory); @@ -250,7 +249,7 @@ void DomainBakeWidget::handleBakerProgress(int baked, int total) { auto resultRow = it->second; // grab the results window, don't force it to be brought to the top - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(false); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(false); int percentage = roundf(float(baked) / float(total) * 100.0f); @@ -269,7 +268,7 @@ void DomainBakeWidget::handleFinishedBaker() { if (it != _bakers.end()) { auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); if (baker->hasErrors()) { auto errors = baker->getErrors(); diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 7963b3f3c4..f80185df0f 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -22,18 +22,20 @@ #include #include "../Oven.h" +#include "../OvenGUIApplication.h" #include "OvenMainWindow.h" - +#include "FBXBaker.h" +#include "OBJBaker.h" #include "ModelBakeWidget.h" + static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory"; static const auto MODEL_START_DIR_SETTING_KEY = "model_search_directory"; ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : BakeWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), - _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) -{ + _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) { setupUI(); } @@ -114,7 +116,7 @@ void ModelBakeWidget::chooseFileButtonClicked() { startDir = QDir::homePath(); } - auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx)"); + auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx *.obj)"); if (!selectedFiles.isEmpty()) { // set the contents of the model file text box to be the path to the selected file @@ -190,7 +192,7 @@ void ModelBakeWidget::bakeButtonClicked() { subFolderName = modelName + "-" + QString::number(++iteration) + "/"; } - outputDirectory.mkdir(subFolderName); + outputDirectory.mkpath(subFolderName); if (!outputDirectory.exists()) { QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory."); @@ -201,28 +203,37 @@ void ModelBakeWidget::bakeButtonClicked() { QDir bakedOutputDirectory = outputDirectory.absoluteFilePath("baked"); QDir originalOutputDirectory = outputDirectory.absoluteFilePath("original"); - + bakedOutputDirectory.mkdir("."); originalOutputDirectory.mkdir("."); - // everything seems to be in place, kick off a bake for this model now - auto baker = std::unique_ptr { - new FBXBaker(modelToBakeURL, []() -> QThread* { - return qApp->getNextWorkerThread(); - }, bakedOutputDirectory.absolutePath(), originalOutputDirectory.absolutePath()) + std::unique_ptr baker; + auto getWorkerThreadCallback = []() -> QThread* { + return Oven::instance().getNextWorkerThread(); }; + // everything seems to be in place, kick off a bake for this model now + if (modelToBakeURL.fileName().endsWith(".fbx")) { + baker.reset(new FBXBaker(modelToBakeURL, getWorkerThreadCallback, bakedOutputDirectory.absolutePath(), + originalOutputDirectory.absolutePath())); + } else if (modelToBakeURL.fileName().endsWith(".obj")) { + baker.reset(new OBJBaker(modelToBakeURL, getWorkerThreadCallback, bakedOutputDirectory.absolutePath(), + originalOutputDirectory.absolutePath())); + } else { + qWarning() << "Unknown model type: " << modelToBakeURL.fileName(); + continue; + } // move the baker to the FBX baker thread - baker->moveToThread(qApp->getNextWorkerThread()); + baker->moveToThread(Oven::instance().getNextWorkerThread()); // invoke the bake method on the baker thread QMetaObject::invokeMethod(baker.get(), "bake"); // make sure we hear about the results of this baker when it is done - connect(baker.get(), &FBXBaker::finished, this, &ModelBakeWidget::handleFinishedBaker); + connect(baker.get(), &Baker::finished, this, &ModelBakeWidget::handleFinishedBaker); // add a pending row to the results window to show that this bake is in process - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); auto resultsRow = resultsWindow->addPendingResultRow(modelToBakeURL.fileName(), outputDirectory); // keep a unique_ptr to this baker @@ -232,27 +243,31 @@ void ModelBakeWidget::bakeButtonClicked() { } void ModelBakeWidget::handleFinishedBaker() { - if (auto baker = qobject_cast(sender())) { - // add the results of this bake to the results window - auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { - return value.first.get() == baker; - }); + Baker* baker = dynamic_cast(sender()); + if (!baker) { + qWarning() << "Received signal from unexpected sender"; + return; + } - for (auto& file : baker->getOutputFiles()) { - qDebug() << "Baked file: " << file; + // add the results of this bake to the results window + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); + + for (auto& file : baker->getOutputFiles()) { + qDebug() << "Baked file: " << file; + } + + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); + + if (baker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); } - if (it != _bakers.end()) { - auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - - if (baker->hasErrors()) { - resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); - } else { - resultsWindow->changeStatusForRow(resultRow, "Success"); - } - - _bakers.erase(it); - } + _bakers.erase(it); } } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index b42b8725f6..fad623bf24 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -16,8 +16,6 @@ #include -#include - #include "BakeWidget.h" class QLineEdit; diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp index dd40fb1f8f..bebc2fa7dc 100644 --- a/tools/oven/src/ui/OvenMainWindow.cpp +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -46,7 +46,7 @@ ResultsWindow* OvenMainWindow::showResultsWindow(bool shouldRaise) { _resultsWindow->show(); // place the results window initially below our window - _resultsWindow->move(_resultsWindow->x(), this->frameGeometry().bottom()); + _resultsWindow->move(_resultsWindow->x(), frameGeometry().bottom()); } // show the results window and make sure it is in front diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp index cbaaa5ec0a..369b06c39f 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.cpp +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -21,8 +21,7 @@ #include #include -#include "../Oven.h" -#include "OvenMainWindow.h" +#include "../OvenGUIApplication.h" #include "SkyboxBakeWidget.h" @@ -184,7 +183,7 @@ void SkyboxBakeWidget::bakeButtonClicked() { }; // move the baker to a worker thread - baker->moveToThread(qApp->getNextWorkerThread()); + baker->moveToThread(Oven::instance().getNextWorkerThread()); // invoke the bake method on the baker thread QMetaObject::invokeMethod(baker.get(), "bake"); @@ -193,7 +192,7 @@ void SkyboxBakeWidget::bakeButtonClicked() { connect(baker.get(), &TextureBaker::finished, this, &SkyboxBakeWidget::handleFinishedBaker); // add a pending row to the results window to show that this bake is in process - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); auto resultsRow = resultsWindow->addPendingResultRow(skyboxToBakeURL.fileName(), outputDirectory); // keep a unique_ptr to this baker @@ -211,7 +210,7 @@ void SkyboxBakeWidget::handleFinishedBaker() { if (it != _bakers.end()) { auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); if (baker->hasErrors()) { resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index 0b12cb64ed..a52e948f01 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -41,18 +41,17 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } try { QByteArray fbxContents = fbx.readAll(); - FBXGeometry* geom; + FBXGeometry::Pointer geom; if (filename.toLower().endsWith(".obj")) { bool combineParts = false; geom = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts); } else if (filename.toLower().endsWith(".fbx")) { - geom = readFBX(fbxContents, QVariantHash(), filename); + geom.reset(readFBX(fbxContents, QVariantHash(), filename)); } else { qWarning() << "file has unknown extension" << filename; return false; } result = *geom; - delete geom; reSortFBXGeometryMeshes(result); } catch (const QString& error) {