diff --git a/.github/workflows/dump.yml b/.github/workflows/dump.yml new file mode 100644 index 0000000000..dc7dd686d0 --- /dev/null +++ b/.github/workflows/dump.yml @@ -0,0 +1,46 @@ +name: Dump Contexts + +on: + push: + branches: + - master + + +jobs: + one: + strategy: + matrix: + os: [windows-latest, macOS-latest] + + runs-on: ${{ matrix.os }} + steps: + - name: Dump GitHub context + shell: bash + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Dump job context + shell: bash + env: + JOB_CONTEXT: ${{ toJson(job) }} + run: echo "$JOB_CONTEXT" + - name: Dump steps context + shell: bash + env: + STEPS_CONTEXT: ${{ toJson(steps) }} + run: echo "$STEPS_CONTEXT" + - name: Dump runner context + shell: bash + env: + RUNNER_CONTEXT: ${{ toJson(runner) }} + run: echo "$RUNNER_CONTEXT" + - name: Dump strategy context + shell: bash + env: + STRATEGY_CONTEXT: ${{ toJson(strategy) }} + run: echo "$STRATEGY_CONTEXT" + - name: Dump matrix context + shell: bash + env: + MATRIX_CONTEXT: ${{ toJson(matrix) }} + run: echo "$MATRIX_CONTEXT" diff --git a/.github/workflows/master_build.yml b/.github/workflows/master_build.yml new file mode 100644 index 0000000000..9ef4728cb0 --- /dev/null +++ b/.github/workflows/master_build.yml @@ -0,0 +1,256 @@ +name: Master CI Build + +on: + push: + branches: + - master + +env: + #APP_NAME: gpu-frame-player + APP_NAME: interface + BUILD_TYPE: Release + BUCKET_NAME: hifi-gh-builds + CI_BUILD: Github + CMAKE_BACKTRACE_URL: https://highfidelity.sp.backtrace.io:6098 + CMAKE_BACKTRACE_TOKEN: ${{ secrets.backtrace_token }} + CMAKE_BACKTRACE_SYMBOLS_TOKEN: ${{ secrets.backtrace_symbols_token }} + GIT_COMMIT: ${{ github.sha }} + HIFI_VCPKG_BOOTSTRAP: true + LAUNCHER_HMAC_SECRET: ${{ secrets.launcher_hmac_secret }} + OCULUS_APP_ID: '${{ secrets.oculus_app_id }}' + RELEASE_TYPE: PRODUCTION + RELEASE_DYNAMODB_V2: ReleaseManager2-ReleaseQueue-prod + STABLE_BUILD: 0 + + + # OSX specific variables + DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer + MACOSX_DEPLOYMENT_TARGET: '10.11' + + # WIN32 specific variables + PreferredToolArchitecture: X64 + +# Mac OS +#PLATFORM_CMAKE_GENERATOR=Xcode +#PLATFORM_BUILD_ARGUMENTS=--config Release --target package +#ARTIFACT_EXPRESSION=build/*.dmg,build/*.zip + +# Windows +#PLATFORM_CMAKE_GENERATOR=Visual Studio 15 2017 Win64 +#PLATFORM_BUILD_ARGUMENTS=--target package --config release +#ARTIFACT_EXPRESSION=build/*.exe,build/*.zip,*-symbols.zip + +# Ubuntu +#PLATFORM_CMAKE_GENERATOR=Unix Makefiles +#PLATFORM_BUILD_ARGUMENTS=--target all -- -j4 +#ARTIFACT_EXPRESSION=build/assignment-client/**,build/domain-server/**,build/ice-server/ice-server,build/tools/ice-client/ice-client,build/tools/ac-client/ac-client,build/tools/oven,build/ext/makefiles/nvtt/project/lib/**,build/ext/makefiles/quazip/project/lib/** + +# Android +# branch: master +# GA_TRACKING_ID: ${{ secrets.ga_tracking_id }} +# ANDROID_OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_NIGHTLY} +# ANDROID_OAUTH_CLIENT_ID=6c7d2349c0614640150db37457a1f75dce98a28ffe8f14d47f6cfae4de5b262a +# ANDROID_OAUTH_REDIRECT_URI=https://dev-android-interface.highfidelity.com/auth +# branch: !master +# GA_TRACKING_ID=UA-39558647-11 +# ANDROID_OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_RELEASE} +# ANDROID_OAUTH_CLIENT_ID= c1063ea5d0b0c405e0c9cd77351328e211a91496a3f25985a99e861f1661db1d +# ANDROID_OAUTH_REDIRECT_URI=https://android-interface.highfidelity.com/auth +# ARTIFACT_EXPRESSION=android/*.apk +# ANDROID_APK_NAME=HighFidelity-Beta-PR${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk +# ANDROID_BUILT_APK_NAME=interface-debug.apk +# ANDROID_APP=interface +# ANDROID_BUILD_DIR=debug +# ANDROID_BUILD_TARGET=assembleDebug +# STABLE_BUILD=0 + + + +jobs: + generate_build_number: + runs-on: ubuntu-latest + steps: + - name: Generate build number + id: buildnumber + uses: highfidelity/build-number@v3 + with: + token: ${{secrets.github_token}} + - name: Upload build number + uses: actions/upload-artifact@v1 + with: + name: BUILD_NUMBER + path: BUILD_NUMBER + + build: + strategy: + matrix: + os: [windows-latest, macOS-latest] + build_type: [full, client] + #os: [windows-latest, macOS-latest, ubuntu-latest] + # exclude: + # - os: ubuntu-latest + # build_type: client + runs-on: ${{matrix.os}} + needs: generate_build_number + steps: + - name: Download build number + uses: actions/download-artifact@v1 + with: + name: BUILD_NUMBER + - name: Restore build number + id: buildnumber + uses: highfidelity/build-number@v3 + with: + output_name: RELEASE_NUMBER + - name: Configure Build Environment 1 + shell: bash + id: buildenv1 + run: | + echo ::set-env name=UPLOAD_PREFIX::master + echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7` + # Linux build variables + if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + echo ::set-env name=INSTALLER_EXT::tgz + fi + # Mac build variables + if [ "${{ matrix.os }}" = "macOS-latest" ]; then + echo ::set-env name=PYTHON_EXEC::python3 + echo ::set-env name=ZIP_COMMAND::zip + echo ::set-env name=ZIP_ARGS::-r + echo ::set-env name=INSTALLER_EXT::dmg + echo ::set-env name=SYMBOL_REGEX::dSYM + echo "::set-output name=symbols_archive::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-mac-symbols.zip" + fi + # Windows build variables + if [ "${{ matrix.os }}" = "windows-latest" ]; then + echo ::set-env name=PYTHON_EXEC::python + echo ::set-env name=ZIP_COMMAND::7z + echo ::set-env name=ZIP_ARGS::a + echo ::set-env name=INSTALLER_EXT::exe + echo "::set-env name=SYMBOL_REGEX::\(exe\|dll\|pdb\)" + echo "::set-output name=symbols_archive::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-win-symbols.zip" + fi + # Configuration is broken into two steps because you can't set an env var and also reference it in the same step + - name: Configure Build Environment 2 + shell: bash + run: | + echo "${{ steps.buildenv1.outputs.symbols_archive }}" + echo ::set-env name=ARTIFACT_PATTERN::HighFidelity-Beta-*.$INSTALLER_EXT + # Build type variables + if [ "${{ matrix.build_type }}" = "full" ]; then + echo ::set-env name=CLIENT_ONLY::FALSE + echo ::set-env name=INSTALLER::HighFidelity-Beta-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT + else + echo ::set-env name=CLIENT_ONLY::TRUE + echo ::set-env name=INSTALLER::HighFidelity-Beta-Interface-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT + fi + # Linux build variables + if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + echo ::set-env name=PYTHON_EXEC::python3 + echo ::set-env name=CMAKE_EXTRA::"" + fi + # Mac build variables + if [ "${{ matrix.os }}" = "macOS-latest" ]; then + echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode" + fi + # Windows build variables + if [ "${{ matrix.os }}" = "windows-latest" ]; then + echo ::set-env name=CMAKE_EXTRA::"-A x64" + echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}} + echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx" + fi + - name: Clear Working Directory + if: matrix.os == 'windows-latest' + shell: bash + working-directory: ${{runner.workspace}} + run: rm -rf ./* + - uses: actions/checkout@v1 + with: + submodules: true + fetch-depth: 1 + - name: Create Build Directory + run: cmake -E make_directory ${{runner.workspace}}/build + - name: Decrypt Signing Key (Windows) + if: matrix.os == 'windows-latest' + working-directory: ${{runner.workspace}}/build + shell: bash + run: gpg --batch --yes -o codesign.pfx --passphrase "${{secrets.gpg_symmetric_key}}" --decrypt $GITHUB_WORKSPACE/tools/ci-scripts/codesign.pfx.gpg + - name: Import Signing Key (Windows) + if: matrix.os == 'windows-latest' + working-directory: ${{runner.workspace}}/build + shell: powershell + run: | + $mypwd=ConvertTo-SecureString -String ${{ secrets.pfx_key }} -Force -AsPlainText + Import-PfxCertificate -Password $mypwd -CertStoreLocation Cert:\CurrentUser\My -FilePath ${{runner.workspace}}\build\codesign.pfx + Import-PfxCertificate -Password $mypwd -CertStoreLocation Cert:\LocalMachine\My -FilePath ${{runner.workspace}}\build\codesign.pfx + - name: Install Python modules + if: matrix.os != 'ubuntu-latest' + shell: bash + run: $PYTHON_EXEC -m pip install boto3 PyGithub + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY $CMAKE_EXTRA + - name: Build Application + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target $APP_NAME + - name: Build Console + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target packaged-server-console + - name: Build Domain Server (FullBuild) + if: matrix.build_type == 'full' + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config $BUILD_TYPE --target domain-server + - name: Build Assignment Client (FullBuild) + if: matrix.build_type == 'full' + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . --config $BUILD_TYPE --target assignment-client + - name: Build Installer + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target package + - name: Sign Installer (Windows) + if: matrix.os == 'windows-latest' + shell: powershell + working-directory: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64 + run: .\signtool.exe sign /fd sha256 /f ${{runner.workspace}}\build\codesign.pfx /p ${{secrets.pfx_key}} /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 ${{runner.workspace}}\build\${env:INSTALLER} + - name: Upload Artifact + if: matrix.os != 'ubuntu-latest' + shell: bash + working-directory: ${{runner.workspace}}/build + env: + AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.aws_secret_access_key }} + run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py + - name: Archive Symbols + if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') + working-directory: ${{runner.workspace}} + shell: bash + run: | + SYMBOLS_TEMP="symbols-temp" + mkdir $SYMBOLS_TEMP + find "./build" \( -path '*/tools/gpu-frame-player/*' -or -path '*/interface/*' -or -path '*/plugins/*' \) -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \; + cd $SYMBOLS_TEMP + $ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} . + - name: Upload Symbols + if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') + working-directory: ${{runner.workspace}} + shell: bash + run: | + curl --data-binary @${{ steps.buildenv1.outputs.symbols_archive }} "$CMAKE_BACKTRACE_URL/post?format=symbols&token=$CMAKE_BACKTRACE_SYMBOLS_TOKEN&upload_file=${{steps.buildenv1.outputs.symbols_archive}}&tag=$RELEASE_NUMBER" + # - name: Debug List Symbols + # if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') + # working-directory: ${{runner.workspace}} + # shell: bash + # run: | + # unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}" + # - name: Debug Upload Symbols Artifact + # if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') + # uses: actions/upload-artifact@v1 + # with: + # name: symbols + # path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }} diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml new file mode 100644 index 0000000000..d1ecb1bb02 --- /dev/null +++ b/.github/workflows/pr_build.yml @@ -0,0 +1,133 @@ +name: Pull Request CI Build + +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + +env: + APP_NAME: interface + BUILD_TYPE: Release + CI_BUILD: Github + GIT_COMMIT: ${{ github.sha }} + HIFI_VCPKG_BOOTSTRAP: true + RELEASE_TYPE: PR + RELEASE_NUMBER: ${{ github.event.number }} + VERSION_CODE: ${{ github.event.number }} + + + # OSX specific variables + DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer + MACOSX_DEPLOYMENT_TARGET: '10.11' + + # WIN32 specific variables + PreferredToolArchitecture: X64 + GIT_COMMIT_SHORT: ${{ github.sha }} + + +jobs: + build: + strategy: + matrix: + os: [windows-latest, macOS-latest] + build_type: [full] + fail-fast: false + runs-on: ${{matrix.os}} + if: github.event.action != 'labeled' || github.event.label.name == 'rebuild' + steps: + - name: Configure Build Environment 1 + shell: bash + id: buildenv1 + run: | + echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7` + # Linux build variables + if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + echo ::set-env name=PYTHON_EXEC::python3 + echo ::set-env name=INSTALLER_EXT::tgz + fi + # Mac build variables + if [ "${{ matrix.os }}" = "macOS-latest" ]; then + echo ::set-env name=PYTHON_EXEC::python3 + echo ::set-env name=INSTALLER_EXT::dmg + echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode" + fi + # Windows build variables + if [ "${{ matrix.os }}" = "windows-latest" ]; then + echo ::set-env name=PYTHON_EXEC::python + echo ::set-env name=INSTALLER_EXT::exe + echo ::set-env name=CMAKE_EXTRA::"-A x64" + fi + # Configuration is broken into two steps because you can't set an env var and also reference it in the same step + - name: Configure Build Environment 2 + shell: bash + run: | + echo "${{ steps.buildenv1.outputs.symbols_archive }}" + echo ::set-env name=ARTIFACT_PATTERN::HighFidelity-Beta-*.$INSTALLER_EXT + # Build type variables + echo ::set-env name=INSTALLER::HighFidelity-Beta-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT + - name: Clear Working Directory + if: matrix.os[1] == 'windows' + shell: bash + working-directory: ${{runner.workspace}} + run: rm -rf ./* + - uses: actions/checkout@v1 + with: + submodules: true + fetch-depth: 1 + - name: Create Build Environment + shell: bash + run: cmake -E make_directory "${{runner.workspace}}/build" + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE $CMAKE_EXTRA + - name: Build Application + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target $APP_NAME + - name: Build Domain Server + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target domain-server + - name: Build Assignment Client + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target assignment-client + - name: Build Console + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target packaged-server-console + - name: Build Installer + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target package + - name: Output Installer Logs + if: failure() && matrix.os == 'windows-latest' + shell: bash + working-directory: ${{runner.workspace}}/build + run: cat ./_CPack_Packages/win64/NSIS/NSISOutput.log + + build_full_linux: + runs-on: ubuntu-latest + if: github.event.action != 'labeled' || github.event.label.name == 'rebuild' + steps: + - uses: actions/checkout@v1 + with: + submodules: true + fetch-depth: 1 + - name: Update apt repository index + run: sudo apt update + - name: Install apt packages + run: sudo apt install -y mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0 + - name: Install python modules + shell: bash + run: pip install boto3 distro PyGithub + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + - name: Configure CMake + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_TOOLS:BOOLEAN=FALSE + - name: + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake --build . -- -j3 diff --git a/BUILD.md b/BUILD.md index 87b9c14c7b..bc032d4ec7 100644 --- a/BUILD.md +++ b/BUILD.md @@ -13,6 +13,8 @@ - [git](https://git-scm.com/downloads): >= 1.6 - [cmake](https://cmake.org/download/): 3.9 - [Python](https://www.python.org/downloads/): 3.6 or higher +- [Node.JS](https://nodejs.org/en/): >= 12.13.1 LTS + - Used to build the Screen Sharing executable. ### CMake External Project Dependencies diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index b549d222da..f88e7173c8 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -89,6 +89,16 @@ Then checkout the main branch with: git checkout kasen/core ``` +### Using a custom Qt build + +Qt binaries are only provided for Ubuntu. In order to build on other distributions, a Qt5 install needs to be provided as follows: + +* Set `VIRCADIA_USE_PREBUILT_QT=1` +* Set `VIRCADIA_USE_QT_VERSION` to the Qt version (defaults to `5.12.3`) +* Set `HIFI_QT_BASE=/path/to/qt` + +Qt must be installed in `$HIFI_QT_BASE/$VIRCADIA_USE_QT_VERSION/qt5-install`. + ### Compiling Create the build directory: diff --git a/CMakeLists.txt b/CMakeLists.txt index b2dc89d105..4bf7b8bc56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ endif() # (needs to be set before first project() call and before prebuild.py) # Will affect VCPKG dependencies if (APPLE) - set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9) + set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.11) endif() set(RELEASE_TYPE "$ENV{RELEASE_TYPE}") @@ -82,9 +82,21 @@ if(NOT EXISTS "${CMAKE_BINARY_DIR}/vcpkg.cmake") endif() include("${CMAKE_BINARY_DIR}/vcpkg.cmake") +if (HIFI_ANDROID) + set(QT_CMAKE_PREFIX_PATH "$ENV{HIFI_ANDROID_PRECOMPILED}/qt/lib/cmake") +else() + if(NOT EXISTS "${CMAKE_BINARY_DIR}/qt.cmake") + message(FATAL_ERROR "qt configuration missing.") + endif() + include("${CMAKE_BINARY_DIR}/qt.cmake") +endif() + +option(VCPKG_APPLOCAL_DEPS OFF) + project(athena) include("cmake/init.cmake") include("cmake/compiler.cmake") +option(VCPKG_APPLOCAL_DEPS OFF) add_paths_to_fixup_libs(${VCPKG_INSTALL_ROOT}/bin) add_paths_to_fixup_libs(${VCPKG_INSTALL_ROOT}/debug/bin) @@ -254,6 +266,7 @@ find_package( Threads ) add_definitions(-DGLM_FORCE_RADIANS) add_definitions(-DGLM_ENABLE_EXPERIMENTAL) add_definitions(-DGLM_FORCE_CTOR_INIT) +add_definitions(-DGLM_LANG_STL11_FORCED) # Workaround for GLM not detecting support for C++11 templates on Android if (WIN32) # Deal with fakakta Visual Studo 2017 bug @@ -278,6 +291,11 @@ if (ANDROID) return() endif() +if (BUILD_GPU_FRAME_PLAYER_ONLY) + # This is for CI build testing + add_subdirectory(tools/gpu-frame-player) +else() + # add subdirectories for all targets if (BUILD_SERVER) add_subdirectory(assignment-client) @@ -290,6 +308,7 @@ endif() if (BUILD_CLIENT) add_subdirectory(interface) + add_subdirectory(screenshare) set_target_properties(interface PROPERTIES FOLDER "Apps") option(USE_SIXENSE "Build Interface with sixense library/plugin" OFF) @@ -303,6 +322,8 @@ endif() # BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway add_subdirectory(tools) +endif() + if (BUILD_TESTS) # Turn on testing so that add_test works # MUST be in the root cmake file for ctest to work diff --git a/README.md b/README.md index 2f43bb4048..f61a60d431 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,14 @@ -# Project Athena +# Vircadia ### What is this? -Project Athena is a 3D social software project seeking to incrementally bring about a truly free and open metaverse, in desktop and XR. +Vircadia is a 3D social software project seeking to incrementally bring about a truly free and open metaverse, in desktop and XR. -### [Download](https://projectathena.io/download-athena/) +### [Download](https://vircadia.com/download-vircadia/) ### Releases -#### v0.86.0 K2 (TBD) - -##### Features, Bugs, and Housekeeping -Check out the releases page for more information! - -#### v0.86.0 K1 (12/3/19) - -* Audio Buffer choppy audio bugfix by increasing the buffer size. -* User Activity Logger disabled, option in code to log the reports to console. -* CMakeLists.txt configured to work for Polyvox, Interface JSDocs. (May be obsolete.) -* Custom Application Title. -* Entity Script Whitelist, no scripts are whitelisted by default. -* Background CMD outputs full log, instant close of application on closing of the CMD-line. +[View Releases here](https://github.com/kasenvr/project-athena/releases/) ### How to build the Interface @@ -30,13 +18,21 @@ Check out the releases page for more information! [For Linux - Athena Builder](https://github.com/daleglass/athena-builder) +### How to deploy a Server + +[For Windows and Linux](https://vircadia.com/download-vircadia/#server) + +### How to build a Server + +[For Linux - Athena Builder](https://github.com/daleglass/athena-builder) + ### Boot to Metaverse: The Goal Having a place to experience adventure, a place to relax with calm breath, that's a world to live in. An engine to support infinite combinations and possibilities of worlds without censorship and interruption, that's a metaverse. Finding a way to make infinite realities our reality, that's the dream. ### Boot to Metaverse: The Technicals -Many developers have personal combinations of High Fidelity from C++ modifications to different default scripts, all of which are lost to time as their fullest potential is never truly shared and propagated through the system. +Many developers have had personal combinations of High Fidelity from C++ modifications to different default scripts, all of which are lost to time as their fullest potential is never truly shared and propagated through the system. The goal of this project is to achieve the metaverse dream through shared contribution and building. Setting goals that are achievable yet meaningful is key to making proper forward progress on the technical front whilst maintaining morale. @@ -52,6 +48,6 @@ We need to do the best we can with what we've got and our best bet as open sourc ### Contribution -A special thanks to the contributors of the Project Athena. +A special thanks to the contributors of Vircadia. [Contribution](CONTRIBUTING.md) diff --git a/android/apps/questInterface/src/main/AndroidManifest.xml b/android/apps/questInterface/src/main/AndroidManifest.xml index a5de47bdce..27cc18072b 100644 --- a/android/apps/questInterface/src/main/AndroidManifest.xml +++ b/android/apps/questInterface/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ + - $) -else() - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink - $ - $/oven) -endif() + if (WIN32) + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + $ + $) + else() + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink + $ + $/oven) + endif() +endif (BUILD_TOOLS) if (WIN32) package_libraries_for_deployment() diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index e5e9f89984..204095ab94 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -782,7 +782,7 @@ float computeGain(float masterAvatarGain, gain *= std::max(1.0f - d / (distanceLimit - ATTN_DISTANCE_REF), 0.0f); gain = std::min(gain, ATTN_GAIN_MAX); - } else { + } else if (attenuationPerDoublingInDistance < 1.0f) { // translate a positive zone setting to gain per log2(distance) const float MIN_ATTENUATION_COEFFICIENT = 0.001f; // -60dB per log2(distance) float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, MIN_ATTENUATION_COEFFICIENT, 1.0f); @@ -792,6 +792,10 @@ float computeGain(float masterAvatarGain, float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); gain *= fastExp2f(fastLog2f(g) * fastLog2f(d)); gain = std::min(gain, ATTN_GAIN_MAX); + + } else { + // translate a zone setting of 1.0 be silent at any distance + gain = 0.0f; } return gain; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c7ab810c9a..807f54953e 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -1081,6 +1081,12 @@ void AvatarMixer::setupEntityQuery() { priorityZoneQuery["avatarPriority"] = true; priorityZoneQuery["type"] = "Zone"; + QJsonObject queryFlags; + queryFlags["includeAncestors"] = true; + queryFlags["includeDescendants"] = true; + priorityZoneQuery["flags"] = queryFlags; + priorityZoneQuery["name"] = true; // Handy for debugging. + _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery); _slaveSharedData.entityTree = entityTree; } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 1195f0d801..f86dc7f766 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -23,8 +23,7 @@ #include "AvatarMixerSlave.h" -AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : - NodeData(nodeID, nodeLocalID) { +AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : NodeData(nodeID, nodeLocalID) { // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); } @@ -92,39 +91,48 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData } namespace { - using std::static_pointer_cast; +using std::static_pointer_cast; - // Operator to find if a point is within an avatar-priority (hero) Zone Entity. - struct FindPriorityZone { - glm::vec3 position; - bool isInPriorityZone { false }; - float zoneVolume { std::numeric_limits::max() }; +// Operator to find if a point is within an avatar-priority (hero) Zone Entity. +struct FindContainingZone { + glm::vec3 position; + bool isInPriorityZone { false }; + bool isInScreenshareZone { false }; + float priorityZoneVolume { std::numeric_limits::max() }; + float screenshareZoneVolume { priorityZoneVolume }; + EntityItemID screenshareZoneid{}; - static bool operation(const OctreeElementPointer& element, void* extraData) { - auto findPriorityZone = static_cast(extraData); - if (element->getAACube().contains(findPriorityZone->position)) { - const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); - entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) { - if (item->getType() == EntityTypes::Zone - && item->contains(findPriorityZone->position)) { - auto zoneItem = static_pointer_cast(item); - if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) { - float volume = zoneItem->getVolumeEstimate(); - if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins - findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED; - findPriorityZone->zoneVolume = volume; - } - } + static bool operation(const OctreeElementPointer& element, void* extraData) { + auto findContainingZone = static_cast(extraData); + if (element->getAACube().contains(findContainingZone->position)) { + const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); + entityTreeElement->forEachEntity([&findContainingZone](EntityItemPointer item) { + if (item->getType() == EntityTypes::Zone && item->contains(findContainingZone->position)) { + auto zoneItem = static_pointer_cast(item); + auto avatarPriorityProperty = zoneItem->getAvatarPriority(); + auto screenshareProperty = zoneItem->getScreenshare(); + float volume = zoneItem->getVolumeEstimate(); + if (avatarPriorityProperty != COMPONENT_MODE_INHERIT + && volume < findContainingZone->priorityZoneVolume) { // Smaller volume wins + findContainingZone->isInPriorityZone = avatarPriorityProperty == COMPONENT_MODE_ENABLED; + findContainingZone->priorityZoneVolume = volume; } - }); - return true; // Keep recursing - } else { // Position isn't within this subspace, so end recursion. - return false; - } + if (screenshareProperty != COMPONENT_MODE_INHERIT + && volume < findContainingZone->screenshareZoneVolume) { + findContainingZone->isInScreenshareZone = screenshareProperty == COMPONENT_MODE_ENABLED; + findContainingZone->screenshareZoneVolume = volume; + findContainingZone->screenshareZoneid = zoneItem->getEntityItemID(); + } + } + }); + return true; // Keep recursing + } else { // Position isn't within this subspace, so end recursion. + return false; } - }; + } +}; -} // Close anonymous namespace. +} // namespace int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) { // pull the sequence number from the data first @@ -150,9 +158,24 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared auto newPosition = _avatar->getClientGlobalPosition(); if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) { EntityTree& entityTree = *slaveSharedData.entityTree; - FindPriorityZone findPriorityZone { newPosition } ; - entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); - _avatar->setHasPriority(findPriorityZone.isInPriorityZone); + FindContainingZone findContainingZone{ newPosition }; + entityTree.recurseTreeWithOperation(&FindContainingZone::operation, &findContainingZone); + bool currentlyHasPriority = findContainingZone.isInPriorityZone; + if (currentlyHasPriority != _avatar->getHasPriority()) { + _avatar->setHasPriority(currentlyHasPriority); + } + bool isInScreenshareZone = findContainingZone.isInScreenshareZone; + if (isInScreenshareZone != _avatar->isInScreenshareZone() + || findContainingZone.screenshareZoneid != _avatar->getScreenshareZone()) { + _avatar->setInScreenshareZone(isInScreenshareZone); + _avatar->setScreenshareZone(findContainingZone.screenshareZoneid); + const QUuid& zoneId = isInScreenshareZone ? findContainingZone.screenshareZoneid : QUuid(); + auto nodeList = DependencyManager::get(); + auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); + packet->write(_avatar->getSessionUUID().toRfc4122()); + packet->write(zoneId.toRfc4122()); + nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); + } _avatar->setNeedsHeroCheck(false); } @@ -217,8 +240,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, } } else { // Trying to read more bytes than available, bail - if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID + - sizeof(AvatarTraits::TraitWireSize))) { + if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID + sizeof(AvatarTraits::TraitWireSize))) { qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr(); return; } @@ -234,8 +256,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, break; } - if (traitType == AvatarTraits::AvatarEntity || - traitType == AvatarTraits::Grab) { + if (traitType == AvatarTraits::AvatarEntity || traitType == AvatarTraits::Grab) { auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID); if (packetTraitVersion > instanceVersionRef) { @@ -293,7 +314,8 @@ void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& m auto simpleReceivedIt = traitVersions.simpleCBegin(); while (simpleReceivedIt != traitVersions.simpleCEnd()) { if (*simpleReceivedIt != AvatarTraits::DEFAULT_TRAIT_VERSION) { - auto traitType = static_cast(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt)); + auto traitType = + static_cast(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt)); _perNodeAckedTraitVersions[nodeId][traitType] = *simpleReceivedIt; } simpleReceivedIt++; @@ -351,8 +373,8 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa // make sure we're not unecessarily overriding the default avatar with the default avatar if (_avatar->getWireSafeSkeletonModelURL() != slaveSharedData.skeletonReplacementURL) { // we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change - qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL() - << "to replacement" << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID(); + qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL() << "to replacement" + << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID(); _avatar->setSkeletonModelURL(slaveSharedData.skeletonReplacementURL); auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true); @@ -453,9 +475,7 @@ void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) { return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums), - [&](const ConicalViewFrustum& viewFrustum) { - return viewFrustum.intersects(otherAvatarBox); - }); + [&](const ConicalViewFrustum& viewFrustum) { return viewFrustum.intersects(otherAvatarBox); }); } void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { @@ -474,7 +494,8 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView; } -AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const { +AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint( + Node::LocalID otherAvatar) const { auto it = _lastSentTraitsTimestamps.find(otherAvatar); if (it != _lastSentTraitsTimestamps.end()) { diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index ec24d4e6bc..09a3522067 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -43,6 +43,11 @@ public: }; Q_ENUM(VerifyState) + bool isInScreenshareZone() const { return _inScreenshareZone; } + void setInScreenshareZone(bool value = true) { _inScreenshareZone = value; } + const QUuid& getScreenshareZone() const { return _screenshareZone; } + void setScreenshareZone(QUuid zone) { _screenshareZone = zone; } + private: bool _needsHeroCheck { false }; static const char* stateToName(VerifyState state); @@ -65,6 +70,8 @@ private: int _numberChallenges { 0 }; bool _certifyFailed { false }; bool _needsIdentityUpdate { false }; + bool _inScreenshareZone { false }; + QUuid _screenshareZone; bool generateFSTHash(); bool validateFSTHash(const QString& publicKey) const; diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 54eb499be1..3e91cd05e8 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -144,10 +144,10 @@ void ScriptableAvatar::update(float deltatime) { } _animationDetails.currentFrame = currentFrame; - const QVector& modelJoints = _bind->getHFMModel().joints; + const std::vector& modelJoints = _bind->getHFMModel().joints; QStringList animationJointNames = _animation->getJointNames(); - const int nJoints = modelJoints.size(); + const auto nJoints = (int)modelJoints.size(); if (_jointData.size() != nJoints) { _jointData.resize(nJoints); } @@ -280,6 +280,17 @@ void ScriptableAvatar::setJointMappingsFromNetworkReply() { } AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const { + auto data = getAvatarEntityDataInternal(true); + return data; +} + +AvatarEntityMap ScriptableAvatar::getAvatarEntityDataNonDefault() const { + auto data = getAvatarEntityDataInternal(false); + return data; + +} + +AvatarEntityMap ScriptableAvatar::getAvatarEntityDataInternal(bool allProperties) const { // DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive! // Avoid calling this method if possible. AvatarEntityMap data; @@ -288,9 +299,18 @@ AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const { for (const auto& itr : _entities) { QUuid id = itr.first; EntityItemPointer entity = itr.second; - EntityItemProperties properties = entity->getProperties(); + + EncodeBitstreamParams params; + auto desiredProperties = entity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + desiredProperties += PROP_LOCAL_VELOCITY; + desiredProperties += PROP_LOCAL_ANGULAR_VELOCITY; + desiredProperties += PROP_LOCAL_DIMENSIONS; + EntityItemProperties properties = entity->getProperties(desiredProperties); + QByteArray blob; - EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob); + EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob, allProperties); data[id] = blob; } }); diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index f2f5a1e6f4..1e6046ba7e 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -76,6 +76,22 @@ * size in the virtual world. Read-only. * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. * Read-only. + * @property {boolean} hasScriptedBlendshapes=false - true if blend shapes are controlled by scripted actions, + * otherwise false. Set this to true before using the {@link MyAvatar.setBlendshape} method, + * and set back to false after you no longer want scripted control over the blend shapes. + *

Note: This property will automatically be set to true if the Controller system has valid facial + * blend shape actions.

+ * @property {boolean} hasProceduralBlinkFaceMovement=true - true if avatars blink automatically by animating + * facial blend shapes, false if automatic blinking is disabled. Set to false to fully control + * the blink facial blend shapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasProceduralEyeFaceMovement=true - true if the facial blend shapes for an avatar's eyes + * adjust automatically as the eyes move, false if this automatic movement is disabled. Set this property + * to true to prevent the iris from being obscured by the upper or lower lids. Set to false to + * fully control the eye blend shapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasAudioEnabledFaceMovement=true - true if the avatar's mouth blend shapes animate + * automatically based on detected microphone input, false if this automatic movement is disabled. Set + * this property to false to fully control the mouth facial blend shapes via the + * {@link MyAvatar.setBlendshape} method. * * @example Create a scriptable avatar. * (function () { @@ -157,13 +173,17 @@ public: * Gets details of all avatar entities. *

Warning: Potentially an expensive call. Do not use if possible.

* @function Avatar.getAvatarEntityData - * @returns {AvatarEntityMap} Details of the avatar entities. + * @returns {AvatarEntityMap} Details of all avatar entities. * @example Report the current avatar entities. * var avatarEntityData = Avatar.getAvatarEntityData(); * print("Avatar entities: " + JSON.stringify(avatarEntityData)); */ Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const override; + AvatarEntityMap getAvatarEntityDataNonDefault() const override; + + AvatarEntityMap getAvatarEntityDataInternal(bool allProperties) const; + /**jsdoc * Sets all avatar entities from an object. *

Warning: Potentially an expensive call. Do not use if possible.

diff --git a/cmake/externals/glad32es/CMakeLists.txt b/cmake/externals/glad32es/CMakeLists.txt deleted file mode 100644 index 8487e5aaaf..0000000000 --- a/cmake/externals/glad32es/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -set(EXTERNAL_NAME glad32es) - -include(ExternalProject) -include(SelectLibraryConfigurations) - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL "${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad32es.zip" - URL_MD5 6a641d8c49dee4c895fa59315f5682a6 - CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -if (UNIX) - set(LIB_PREFIX "lib") - set(LIB_EXT "a") -elseif (WIN32) - set(LIB_EXT "lib") -endif () - -set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT}) -set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT}) -select_library_configurations(${EXTERNAL_NAME_UPPER}) - -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories") -set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries") diff --git a/cmake/externals/glad41/CMakeLists.txt b/cmake/externals/glad41/CMakeLists.txt deleted file mode 100644 index 76382dd05d..0000000000 --- a/cmake/externals/glad41/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -set(EXTERNAL_NAME glad41) - -include(ExternalProject) -include(SelectLibraryConfigurations) - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL "${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad41.zip" - URL_MD5 1324eeec33abe91e67d19ae551ba624d - CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -if (UNIX) - set(LIB_PREFIX "lib") - set(LIB_EXT "a") -elseif (WIN32) - set(LIB_EXT "lib") -endif () - -set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT}) -set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT}) -select_library_configurations(${EXTERNAL_NAME_UPPER}) - -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories") -set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries") diff --git a/cmake/externals/glad45/CMakeLists.txt b/cmake/externals/glad45/CMakeLists.txt deleted file mode 100644 index 477a5f79d5..0000000000 --- a/cmake/externals/glad45/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -set(EXTERNAL_NAME glad45) - -include(ExternalProject) -include(SelectLibraryConfigurations) - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL "${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad45.zip" - URL_MD5 cfb19b3cb5b2f8f1d1669fb3150e5f05 - CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -if (UNIX) - set(LIB_PREFIX "lib") - set(LIB_EXT "a") -elseif (WIN32) - set(LIB_EXT "lib") -endif () - -set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT}) -set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT}) -select_library_configurations(${EXTERNAL_NAME_UPPER}) - -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories") -set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries") diff --git a/cmake/externals/polyvox/CMakeLists.txt b/cmake/externals/polyvox/CMakeLists.txt deleted file mode 100644 index 63a4bee043..0000000000 --- a/cmake/externals/polyvox/CMakeLists.txt +++ /dev/null @@ -1,82 +0,0 @@ -set(EXTERNAL_NAME polyvox) - -include(ExternalProject) - -message(STATUS "===== POLYVOX BUILD_TYPE ${BUILD_TYPE} ${CMAKE_BUILD_TYPE}") -set(CMAKE_BUILD_TYPE Release) - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL "${EXTERNAL_BUILD_ASSETS}/dependencies/polyvox-master-2015-7-15.zip" - URL_MD5 9ec6323b87e849ae36e562ae1c7494a9 - CMAKE_ARGS -DENABLE_EXAMPLES=OFF -DENABLE_BINDINGS=OFF -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -if (APPLE) - set(INSTALL_NAME_LIBRARY_DIR ${INSTALL_DIR}/lib) - - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-debug - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR}/Debug -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) - - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-release - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR}/Release -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) -endif () - - -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) - -if (WIN32) - set(${EXTERNAL_NAME_UPPER}_CORE_INCLUDE_DIRS ${INSTALL_DIR}/PolyVoxCore/include CACHE FILEPATH - "Path to polyvox core include directory") - set(${EXTERNAL_NAME_UPPER}_UTIL_INCLUDE_DIRS ${INSTALL_DIR}/PolyVoxUtil/include CACHE FILEPATH - "Path to polyvox util include directory") -else () - set(${EXTERNAL_NAME_UPPER}_CORE_INCLUDE_DIRS ${INSTALL_DIR}/include/PolyVoxCore CACHE FILEPATH - "Path to polyvox core include directory") - set(${EXTERNAL_NAME_UPPER}_UTIL_INCLUDE_DIRS ${INSTALL_DIR}/include/PolyVoxUtil CACHE FILEPATH - "Path to polyvox util include directory") -endif () - - -if (WIN32) - set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY_DEBUG ${INSTALL_DIR}/PolyVoxCore/lib/Debug/PolyVoxCore.lib CACHE FILEPATH "polyvox core library") - - # use generator expression to ensure the correct library is found when building different configurations in VS - set(_LIB_FOLDER "$<$:PolyVoxCore/lib/RelWithDebInfo>") - set(_LIB_FOLDER "${_LIB_FOLDER}$<$:build/library/PolyVoxCore/MinSizeRel>") - set(_LIB_FOLDER "${_LIB_FOLDER}$<$,$>:PolyVoxCore/lib/Release>") - - set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY_RELEASE "${INSTALL_DIR}/${_LIB_FOLDER}/PolyVoxCore.lib" CACHE FILEPATH "polyvox core library") -# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/PolyVoxUtil/lib/PolyVoxUtil.lib CACHE FILEPATH "polyvox util library") -elseif (APPLE) - set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/libPolyVoxCore.dylib CACHE FILEPATH "polyvox core library") - set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY_RELEASE ${INSTALL_DIR}/lib/Release/libPolyVoxCore.dylib CACHE FILEPATH "polyvox core library") -# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxUtil.dylib CACHE FILEPATH "polyvox util library") -else () - set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/libPolyVoxCore.so CACHE FILEPATH "polyvox core library") - set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY_RELEASE ${INSTALL_DIR}/lib/Release/libPolyVoxCore.so CACHE FILEPATH "polyvox core library") -# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxUtil.so CACHE FILEPATH "polyvox util library") -endif () diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt deleted file mode 100644 index 8190a35988..0000000000 --- a/cmake/externals/quazip/CMakeLists.txt +++ /dev/null @@ -1,60 +0,0 @@ -set(EXTERNAL_NAME quazip) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) - -include(ExternalProject) - -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 - -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - -DZLIB_ROOT=${VCPKG_INSTALL_ROOT} - -DCMAKE_POSITION_INDEPENDENT_CODE=ON) - -if (NOT APPLE) - set(QUAZIP_CMAKE_ARGS ${QUAZIP_CMAKE_ARGS} -DCMAKE_CXX_STANDARD=11) -endif () - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL "${EXTERNAL_BUILD_ASSETS}/dependencies/quazip-0.7.3.zip" - URL_MD5 ed03754d39b9da1775771819b8001d45 - BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - CMAKE_ARGS ${QUAZIP_CMAKE_ARGS} - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES - FOLDER "hidden/externals" - INSTALL_NAME_DIR ${INSTALL_DIR}/lib - BUILD_WITH_INSTALL_RPATH True) - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories") -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories") -set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL") - -if (APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library") -elseif (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library") -elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library") -else () - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") -endif () - -include(SelectLibraryConfigurations) -select_library_configurations(${EXTERNAL_NAME_UPPER}) - -# Force selected libraries into the cache -set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries") -set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries") diff --git a/cmake/externals/vhacd/CMakeLists.txt b/cmake/externals/vhacd/CMakeLists.txt deleted file mode 100644 index 5fb89b7e9b..0000000000 --- a/cmake/externals/vhacd/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -set(EXTERNAL_NAME vhacd) - -if (ANDROID) - set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") -endif () - -include(ExternalProject) -ExternalProject_Add( - ${EXTERNAL_NAME} - URL "${EXTERNAL_BUILD_ASSETS}/dependencies/v-hacd-master.zip" - URL_MD5 3bfc94f8dd3dfbfe8f4dc088f4820b3e - CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= - BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) - -if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/Debug/VHACD_LIB.lib CACHE FILEPATH "Path to V-HACD debug library") - - # use generator expression to ensure the correct library is found when building different configurations in VS - set(_LIB_FOLDER "$<$:build/src/VHACD_Lib/RelWithDebInfo>") - set(_LIB_FOLDER "${_LIB_FOLDER}$<$:build/src/VHACD_Lib/MinSizeRel>") - set(_LIB_FOLDER "${_LIB_FOLDER}$<$,$>:lib/Release>") - - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/${_LIB_FOLDER}/VHACD_LIB.lib CACHE FILEPATH "Path to V-HACD release library") -else () - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to V-HACD debug library") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libVHACD.a CACHE FILEPATH "Path to V-HACD release library") -endif () - -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to V-HACD include directory") diff --git a/cmake/init.cmake b/cmake/init.cmake index e97cfa476e..ca95ad1a72 100644 --- a/cmake/init.cmake +++ b/cmake/init.cmake @@ -61,6 +61,8 @@ endif () if (APPLE) set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep") + set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") + set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") endif() if (UNIX) diff --git a/cmake/macros/DumpCmakeVariables.cmake b/cmake/macros/DumpCmakeVariables.cmake new file mode 100644 index 0000000000..f0434ec90b --- /dev/null +++ b/cmake/macros/DumpCmakeVariables.cmake @@ -0,0 +1,14 @@ +function(dump_cmake_variables) + get_cmake_property(_variableNames VARIABLES) + list (SORT _variableNames) + foreach (_variableName ${_variableNames}) + if (ARGV0) + unset(MATCHED) + string(REGEX MATCH ${ARGV0} MATCHED ${_variableName}) + if (NOT MATCHED) + continue() + endif() + endif() + message(STATUS "${_variableName}=${${_variableName}}") + endforeach() +endfunction() \ No newline at end of file diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index d279ae8db9..935996850b 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -91,39 +91,11 @@ macro(SET_PACKAGING_PARAMETERS) endif () if ((PRODUCTION_BUILD OR PR_BUILD) AND NOT STABLE_BUILD) + set(GIT_COMMIT_SHORT $ENV{GIT_COMMIT_SHORT}) # append the abbreviated commit SHA to the build version # since this is a PR build or master/nightly builds - - # for PR_BUILDS, we need to grab the abbreviated SHA - # for the second parent of HEAD (not HEAD) since that is the - # SHA of the commit merged to master for the build - if (PR_BUILD) - set(_GIT_LOG_FORMAT "%p %h") - else () - set(_GIT_LOG_FORMAT "%h") - endif () - - execute_process( - COMMAND git log -1 --abbrev=7 --format=${_GIT_LOG_FORMAT} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE _GIT_LOG_OUTPUT - ERROR_VARIABLE _GIT_LOG_ERROR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if (PR_BUILD) - separate_arguments(_COMMIT_PARENTS UNIX_COMMAND ${_GIT_LOG_OUTPUT}) - list(GET _COMMIT_PARENTS 1 GIT_COMMIT_HASH) - else () - set(GIT_COMMIT_HASH ${_GIT_LOG_OUTPUT}) - endif () - - if (_GIT_LOG_ERROR OR NOT GIT_COMMIT_HASH) - message(FATAL_ERROR "Could not retreive abbreviated SHA for PR or production master build") - endif () - set(BUILD_VERSION_NO_SHA ${BUILD_VERSION}) - set(BUILD_VERSION "${BUILD_VERSION}-${GIT_COMMIT_HASH}") + set(BUILD_VERSION "${BUILD_VERSION}-${GIT_COMMIT_SHORT}") # pass along a release number without the SHA in case somebody # wants to compare master or PR builds as integers @@ -146,23 +118,27 @@ macro(SET_PACKAGING_PARAMETERS) set(DMG_SUBFOLDER_ICON "${HF_CMAKE_DIR}/installer/install-folder.rsrc") - set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) - set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) - set(NITPICK_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(SCREENSHARE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(NITPICK_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) if (CLIENT_ONLY) set(CONSOLE_EXEC_NAME "Console.app") else () set(CONSOLE_EXEC_NAME "Sandbox.app") endif() - set(CONSOLE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${CONSOLE_EXEC_NAME}") + set(SCREENSHARE_EXEC_NAME "hifi-screenshare.app") + set(SCREENSHARE_INSTALL_APP_PATH "${SCREENSHARE_INSTALL_DIR}/${SCREENSHARE_EXEC_NAME}") + set(CONSOLE_APP_CONTENTS "${CONSOLE_INSTALL_APP_PATH}/Contents") set(COMPONENT_APP_PATH "${CONSOLE_APP_CONTENTS}/MacOS/Components.app") set(COMPONENT_INSTALL_DIR "${COMPONENT_APP_PATH}/Contents/MacOS") set(CONSOLE_PLUGIN_INSTALL_DIR "${COMPONENT_APP_PATH}/Contents/PlugIns") - + + set(SCREENSHARE_APP_CONTENTS "${SCREENSHARE_INSTALL_APP_PATH}/Contents") set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app") set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns") @@ -170,9 +146,11 @@ macro(SET_PACKAGING_PARAMETERS) else () if (WIN32) set(CONSOLE_INSTALL_DIR "server-console") + set(SCREENSHARE_INSTALL_DIR "hifi-screenshare") set(NITPICK_INSTALL_DIR "nitpick") else () set(CONSOLE_INSTALL_DIR ".") + set(SCREENSHARE_INSTALL_DIR ".") set(NITPICK_INSTALL_DIR ".") endif () @@ -186,6 +164,7 @@ macro(SET_PACKAGING_PARAMETERS) set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.ico") set(CONSOLE_EXEC_NAME "server-console.exe") + set(SCREENSHARE_EXEC_NAME "hifi-screenshare.exe") set(DS_EXEC_NAME "domain-server.exe") set(AC_EXEC_NAME "assignment-client.exe") diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake index c09c2b0f6b..bd198a2195 100644 --- a/cmake/macros/SetupQt.cmake +++ b/cmake/macros/SetupQt.cmake @@ -48,19 +48,13 @@ macro(setup_qt) # if we are in a development build and QT_CMAKE_PREFIX_PATH is specified # then use it, # otherwise, use the vcpkg'ed version - if(NOT DEFINED VCPKG_QT_CMAKE_PREFIX_PATH) - message(FATAL_ERROR "VCPKG_QT_CMAKE_PREFIX_PATH should have been set by hifi_vcpkg.py") + if(NOT DEFINED QT_CMAKE_PREFIX_PATH) + message(FATAL_ERROR "QT_CMAKE_PREFIX_PATH should have been set by hifi_qt.py") endif() - if (NOT DEV_BUILD) - message("override QT_CMAKE_PREFIX_PATH with VCPKG_QT_CMAKE_PREFIX_PATH") - set(QT_CMAKE_PREFIX_PATH ${VCPKG_QT_CMAKE_PREFIX_PATH}) - else() - # DEV_BUILD - if (DEFINED ENV{QT_CMAKE_PREFIX_PATH}) - set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) - else() - set(QT_CMAKE_PREFIX_PATH ${VCPKG_QT_CMAKE_PREFIX_PATH}) - endif() + if (DEV_BUILD) + if (DEFINED ENV{QT_CMAKE_PREFIX_PATH}) + set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) + endif() endif() message("QT_CMAKE_PREFIX_PATH = " ${QT_CMAKE_PREFIX_PATH}) diff --git a/cmake/macros/TargetAristo.cmake b/cmake/macros/TargetAristo.cmake new file mode 100644 index 0000000000..a28e7b5cdd --- /dev/null +++ b/cmake/macros/TargetAristo.cmake @@ -0,0 +1,15 @@ +# +# Copyright 2019 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_ARISTO) + +if (WIN32) + target_include_directories(${TARGET_NAME} SYSTEM PUBLIC "${VCPKG_INSTALL_ROOT}/include") + find_library(ARISTO_LIBRARY NAMES aristo_interface PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH) + target_link_libraries(${TARGET_NAME} ${ARISTO_LIBRARY}) +endif() + +endmacro() diff --git a/cmake/macros/TargetGlad.cmake b/cmake/macros/TargetGlad.cmake index c9a2529986..aa7258e345 100644 --- a/cmake/macros/TargetGlad.cmake +++ b/cmake/macros/TargetGlad.cmake @@ -16,21 +16,15 @@ macro(TARGET_GLAD) find_library(EGL EGL) target_link_libraries(${TARGET_NAME} ${EGL}) else() - if (USE_GLES) - set(GLAD_VER "32es") - else() - set(GLAD_VER "45") - endif() find_package(OpenGL REQUIRED) list(APPEND GLAD_EXTRA_LIBRARIES ${OPENGL_LIBRARY}) + find_library(GLAD_LIBRARY_RELEASE glad PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH) + find_library(GLAD_LIBRARY_DEBUG glad_d PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH) + select_library_configurations(GLAD) if (NOT WIN32) list(APPEND GLAD_EXTRA_LIBRARIES dl) endif() - set(GLAD "glad${GLAD_VER}") - string(TOUPPER ${GLAD} GLAD_UPPER) - add_dependency_external_projects(${GLAD}) set(GLAD_INCLUDE_DIRS ${${GLAD_UPPER}_INCLUDE_DIRS}) - set(GLAD_LIBRARY ${${GLAD_UPPER}_LIBRARY}) endif() target_include_directories(${TARGET_NAME} PUBLIC ${GLAD_INCLUDE_DIRS}) diff --git a/cmake/macros/TargetLiblo.cmake b/cmake/macros/TargetLiblo.cmake new file mode 100644 index 0000000000..dac4197b1e --- /dev/null +++ b/cmake/macros/TargetLiblo.cmake @@ -0,0 +1,4 @@ +macro(target_liblo) + find_library(LIBLO LIBLO) + target_link_libraries(${TARGET_NAME} ${LIBLO}) +endmacro() \ No newline at end of file diff --git a/cmake/macros/TargetPolyvox.cmake b/cmake/macros/TargetPolyvox.cmake index b2c4e30dd2..576b454f57 100644 --- a/cmake/macros/TargetPolyvox.cmake +++ b/cmake/macros/TargetPolyvox.cmake @@ -13,12 +13,16 @@ macro(TARGET_POLYVOX) list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/libPolyVoxUtil.so) list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/Release/libPolyVoxCore.so) else() - add_dependency_external_projects(polyvox) - find_package(PolyVox REQUIRED) + find_library(POLYVOX_LIBRARY_RELEASE PolyVoxCore PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH) + find_library(POLYVOX_UTIL_LIBRARY_RELEASE PolyVoxUtil PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH) + list(APPEND POLYVOX_LIBRARY_RELEASE ${POLYVOX_UTIL_LIBRARY_RELEASE}) + find_library(POLYVOX_LIBRARY_DEBUG PolyVoxCore PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH) + find_library(POLYVOX_UTIL_LIBRARY_DEBUG PolyVoxUtil PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH) + list(APPEND POLYVOX_LIBRARY_DEBUG ${POLYVOX_UTIL_LIBRARY_DEBUG}) + select_library_configurations(POLYVOX) + list(APPEND POLYVOX_INCLUDE_DIRS ${VCPKG_INSTALL_ROOT}/include) endif() - target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) + target_include_directories(${TARGET_NAME} PUBLIC ${POLYVOX_INCLUDE_DIRS}) endmacro() - - diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake index f704f03050..fa05ec55eb 100644 --- a/cmake/macros/TargetQuazip.cmake +++ b/cmake/macros/TargetQuazip.cmake @@ -6,11 +6,8 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_QUAZIP) - add_dependency_external_projects(quazip) - find_package(QuaZip REQUIRED) - target_include_directories(${TARGET_NAME} PUBLIC ${QUAZIP_INCLUDE_DIRS}) + find_library(QUAZIP_LIBRARY_RELEASE quazip5 PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH) + find_library(QUAZIP_LIBRARY_DEBUG quazip5 PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH) + select_library_configurations(QUAZIP) target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) - if (WIN32) - add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) - endif () -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/macros/TargetSPIRV.cmake b/cmake/macros/TargetSPIRV.cmake deleted file mode 100644 index 94c9df9d13..0000000000 --- a/cmake/macros/TargetSPIRV.cmake +++ /dev/null @@ -1,15 +0,0 @@ -macro(TARGET_SPIRV) - add_dependency_external_projects(spirv_cross) - target_link_libraries(${TARGET_NAME} ${SPIRV_CROSS_LIBRARIES}) - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SPIRV_CROSS_INCLUDE_DIRS}) - - # spirv-tools requires spirv-headers - add_dependency_external_projects(spirv_headers) - add_dependency_external_projects(spirv_tools) - target_link_libraries(${TARGET_NAME} ${SPIRV_TOOLS_LIBRARIES}) - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SPIRV_TOOLS_INCLUDE_DIRS}) - - add_dependency_external_projects(glslang) - target_link_libraries(${TARGET_NAME} ${GLSLANG_LIBRARIES}) - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${GLSLANG_INCLUDE_DIRS}) -endmacro() diff --git a/cmake/macros/TargetSRanipalEye.cmake b/cmake/macros/TargetSRanipalEye.cmake new file mode 100644 index 0000000000..5644d30e92 --- /dev/null +++ b/cmake/macros/TargetSRanipalEye.cmake @@ -0,0 +1,18 @@ +# +# Copyright 2019 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_SRANIPAL) + +if (WIN32) + target_include_directories(${TARGET_NAME} SYSTEM PUBLIC "${VCPKG_INSTALL_ROOT}/include") + find_library(SRANIPAL_LIBRARY NAMES SRanipal PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH) + target_link_libraries(${TARGET_NAME} ${SRANIPAL_LIBRARY}) + + find_library(SRANIPAL_LIBRARY NAMES SRanipal PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH) + target_link_libraries(${TARGET_NAME} ${SRANIPAL_LIBRARY}) +endif() + +endmacro() diff --git a/cmake/macros/TargetSpirvBinaries.cmake b/cmake/macros/TargetSpirvBinaries.cmake deleted file mode 100644 index 6cc102d38e..0000000000 --- a/cmake/macros/TargetSpirvBinaries.cmake +++ /dev/null @@ -1,10 +0,0 @@ -# -# Created by Bradley Austin Davis on 2016/02/16 -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# -macro(TARGET_SPIRV_BINARIES) - add_dependency_external_projects(spirv_binaries) -endmacro() - diff --git a/cmake/macros/TargetTBB.cmake b/cmake/macros/TargetTBB.cmake index b2aeeb99aa..087f3f3242 100644 --- a/cmake/macros/TargetTBB.cmake +++ b/cmake/macros/TargetTBB.cmake @@ -15,11 +15,6 @@ if (ANDROID) set(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY}) target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${TBB_LIBRARIES}) -elseif(APPLE) - add_dependency_external_projects(tbb) - find_package(TBB REQUIRED) - target_link_libraries(${TARGET_NAME} ${TBB_LIBRARIES}) - target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) else() # using VCPKG for TBB find_package(TBB CONFIG REQUIRED) diff --git a/cmake/macros/TargetVHACD.cmake b/cmake/macros/TargetVHACD.cmake new file mode 100644 index 0000000000..35e5f8b9c4 --- /dev/null +++ b/cmake/macros/TargetVHACD.cmake @@ -0,0 +1,6 @@ +macro(TARGET_VHACD) + find_library(VHACD_LIBRARY_RELEASE VHACD PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH) + find_library(VHACD_LIBRARY_DEBUG VHACD PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH) + select_library_configurations(VHACD) + target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES}) +endmacro() diff --git a/cmake/ports/.gitattributes b/cmake/ports/.gitattributes new file mode 100644 index 0000000000..44b4224b17 --- /dev/null +++ b/cmake/ports/.gitattributes @@ -0,0 +1 @@ +* eol=lf \ No newline at end of file diff --git a/cmake/ports/aristo/CONTROL b/cmake/ports/aristo/CONTROL new file mode 100644 index 0000000000..a022c6ea9f --- /dev/null +++ b/cmake/ports/aristo/CONTROL @@ -0,0 +1,3 @@ +Source: aristo +Version: 0.8.1 +Description: Aristo diff --git a/cmake/ports/aristo/portfile.cmake b/cmake/ports/aristo/portfile.cmake new file mode 100644 index 0000000000..532e1304f4 --- /dev/null +++ b/cmake/ports/aristo/portfile.cmake @@ -0,0 +1,20 @@ +include(vcpkg_common_functions) +set(ARISTO_VERSION 0.8.1) +set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src) + +if (WIN32) + vcpkg_download_distfile( + ARISTO_SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/seth/aristo-0.8.1-windows.zip + SHA512 05179c63b72a1c9f5be8a7a2b7389025da683400dbf819e5a6199dd6473c56774d2885182dc5a11cb6324058d228a4ead832222e8b3e1bebaa4c61982e85f0a8 + FILENAME aristo-0.8.1-windows.zip + ) + + vcpkg_extract_source_archive(${ARISTO_SOURCE_ARCHIVE}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/aristo/include DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/aristo/lib DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/aristo/debug DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/aristo/bin DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/aristo/share DESTINATION ${CURRENT_PACKAGES_DIR}) + +endif () diff --git a/cmake/ports/glad/CONTROL b/cmake/ports/glad/CONTROL new file mode 100644 index 0000000000..e243d3c6f8 --- /dev/null +++ b/cmake/ports/glad/CONTROL @@ -0,0 +1,3 @@ +Source: glad +Version: 20191029 +Description: OpenGL function loader diff --git a/cmake/ports/glad/copyright b/cmake/ports/glad/copyright new file mode 100644 index 0000000000..3b9a7d61a6 --- /dev/null +++ b/cmake/ports/glad/copyright @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2018 David Herberth + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/cmake/ports/glad/portfile.cmake b/cmake/ports/glad/portfile.cmake new file mode 100644 index 0000000000..56ee59e0d4 --- /dev/null +++ b/cmake/ports/glad/portfile.cmake @@ -0,0 +1,36 @@ +include(vcpkg_common_functions) +vcpkg_check_linkage(ONLY_STATIC_LIBRARY) + +if (ANDROID) + vcpkg_download_distfile( + SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/dependencies/glad/glad32es.zip + SHA512 2e02ac633eed8f2ba2adbf96ea85d08998f48dd2e9ec9a88ec3c25f48eaf1405371d258066327c783772fcb3793bdb82bd7375fdabb2ba5e2ce0835468b17f65 + ) +else() + # else Linux desktop + vcpkg_download_distfile( + SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/dependencies/glad/glad45.zip + SHA512 653a7b873f9fbc52e0ab95006cc3143bc7b6f62c6e032bc994e87669273468f37978525c9af5efe36f924cb4acd221eb664ad9af0ce4bf711b4f1be724c0065e + FILENAME glad45.zip + ) +endif() + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${SOURCE_ARCHIVE} + NO_REMOVE_ONE_LEVEL +) + +vcpkg_configure_cmake( + SOURCE_PATH ${SOURCE_PATH} + PREFER_NINJA + OPTIONS -DCMAKE_POSITION_INDEPENDENT_CODE=ON +) + +vcpkg_install_cmake() + +file(COPY ${CMAKE_CURRENT_LIST_DIR}/copyright DESTINATION ${CURRENT_PACKAGES_DIR}/share/glad) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) + diff --git a/cmake/ports/hifi-client-deps/CONTROL b/cmake/ports/hifi-client-deps/CONTROL index 7d4727b364..7070cb6fb9 100644 --- a/cmake/ports/hifi-client-deps/CONTROL +++ b/cmake/ports/hifi-client-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-client-deps -Version: 0 +Version: 0.1 Description: Collected dependencies for High Fidelity applications -Build-Depends: hifi-deps, glslang, nlohmann-json, openvr (windows), sdl2 (!android), spirv-cross (!android), spirv-tools (!android), vulkanmemoryallocator +Build-Depends: hifi-deps, aristo (windows), glslang, liblo (windows), nlohmann-json, openvr (windows), quazip (!android), sdl2 (!android), spirv-cross (!android), spirv-tools (!android), sranipal (windows), vulkanmemoryallocator diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL index b1a7f96a00..d4b4acd4c5 100644 --- a/cmake/ports/hifi-deps/CONTROL +++ b/cmake/ports/hifi-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-deps -Version: 0.4 +Version: 0.1.4-github-actions Description: Collected dependencies for High Fidelity applications -Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), opus, tbb (!android&!osx), zlib, webrtc (!android) +Build-Depends: bullet3, draco, etc2comp, glad, glm, nvtt, openexr (!android), openssl (windows), opus, polyvox, tbb (!android), vhacd, webrtc (!android), zlib diff --git a/cmake/ports/liblo/CONTROL b/cmake/ports/liblo/CONTROL new file mode 100644 index 0000000000..5e05c95e3e --- /dev/null +++ b/cmake/ports/liblo/CONTROL @@ -0,0 +1,3 @@ +Source: liblo +Version: 0.30 +Description: liblo is an implementation of the Open Sound Control protocol for POSIX systems \ No newline at end of file diff --git a/cmake/ports/liblo/portfile.cmake b/cmake/ports/liblo/portfile.cmake new file mode 100644 index 0000000000..27e41af186 --- /dev/null +++ b/cmake/ports/liblo/portfile.cmake @@ -0,0 +1,36 @@ +include(vcpkg_common_functions) + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO radarsat1/liblo + REF 0.30 + SHA512 d36c141c513f869e6d1963bd0d584030038019b8be0b27bb9a684722b6e7a38e942ad2ee7c2e67ac13b965560937aad97259435ed86034aa2dc8cb92d23845d8 + HEAD_REF master +) + +vcpkg_configure_cmake( + SOURCE_PATH ${SOURCE_PATH}/cmake + PREFER_NINJA # Disable this option if project cannot be built with Ninja + OPTIONS -DTHREADING=1 +) + +vcpkg_install_cmake() + +# Install needed files into package directory +vcpkg_fixup_cmake_targets(CONFIG_PATH lib/cmake/liblo) + +file(INSTALL ${CURRENT_PACKAGES_DIR}/bin/oscsend.exe DESTINATION ${CURRENT_PACKAGES_DIR}/tools/liblo) +file(INSTALL ${CURRENT_PACKAGES_DIR}/bin/oscdump.exe DESTINATION ${CURRENT_PACKAGES_DIR}/tools/liblo) +vcpkg_copy_tool_dependencies(${CURRENT_PACKAGES_DIR}/tools/liblo) + +# Remove unnecessary files +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/oscsend.exe ${CURRENT_PACKAGES_DIR}/bin/oscdump.exe) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/oscsend.exe ${CURRENT_PACKAGES_DIR}/debug/bin/oscdump.exe) + +if(VCPKG_LIBRARY_LINKAGE STREQUAL static) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin) +endif() + +# Handle copyright +file(INSTALL ${SOURCE_PATH}/COPYING DESTINATION ${CURRENT_PACKAGES_DIR}/share/liblo RENAME copyright) diff --git a/cmake/ports/openssl-android/portfile.cmake b/cmake/ports/openssl-android/portfile.cmake index 372a9fceef..fd74637d00 100644 --- a/cmake/ports/openssl-android/portfile.cmake +++ b/cmake/ports/openssl-android/portfile.cmake @@ -7,7 +7,7 @@ file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSE message("MASTER_COPY_SOURCE_PATH ${MASTER_COPY_SOURCE_PATH}") vcpkg_download_distfile( OPENSSL_SOURCE_ARCHIVE - URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/android/openssl-1.1.0g_armv8.tgz?versionId=AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW" + URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/android/openssl-1.1.0g_armv8.tgz%3FversionId=AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW" SHA512 5d7bb6e5d3db2340449e2789bcd72da821f0e57483bac46cf06f735dffb5d73c1ca7cc53dd48f3b3979d0fe22b3ae61997c516fc0c4611af4b4b7f480e42b992 FILENAME openssl-1.1.0g_armv8.tgz ) diff --git a/cmake/ports/openssl-unix/CMakeLists.txt b/cmake/ports/openssl-unix/CMakeLists.txt index 7757a4fe78..3badf07eef 100644 --- a/cmake/ports/openssl-unix/CMakeLists.txt +++ b/cmake/ports/openssl-unix/CMakeLists.txt @@ -26,6 +26,7 @@ get_filename_component(COMPILER_ROOT "${CMAKE_C_COMPILER}" DIRECTORY) message("CMAKE_C_COMPILER=${CMAKE_C_COMPILER}") message("COMPILER_ROOT=${COMPILER_ROOT}") message("CMAKE_SYSROOT=${CMAKE_SYSROOT}") +message("CMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT}") message("CMAKE_C_FLAGS=${CMAKE_C_FLAGS}") message("CMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}") message("CMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG}") @@ -43,6 +44,8 @@ if(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN) endif() if(CMAKE_SYSROOT AND CMAKE_C_COMPILE_OPTIONS_SYSROOT) set(CFLAGS "${CFLAGS} ${CMAKE_C_COMPILE_OPTIONS_SYSROOT}${CMAKE_SYSROOT}") +elseif(CMAKE_OSX_SYSROOT AND CMAKE_C_COMPILE_OPTIONS_SYSROOT) + set(CFLAGS "${CFLAGS} ${CMAKE_C_COMPILE_OPTIONS_SYSROOT}${CMAKE_OSX_SYSROOT}") endif() string(REGEX REPLACE "^ " "" CFLAGS "${CFLAGS}") @@ -73,9 +76,27 @@ file(WRITE "${BUILDDIR}/Configure" "${_contents}") if(BUILD_SHARED_LIBS) set(SHARED shared) + file(STRINGS "${BUILDDIR}/crypto/opensslv.h" SHLIB_VERSION + REGEX "^#[\t ]*define[\t ]+SHLIB_VERSION_NUMBER[\t ]+\".*\".*") + string(REGEX REPLACE "^.*SHLIB_VERSION_NUMBER[\t ]+\"([^\"]*)\".*$" "\\1" + SHLIB_VERSION "${SHLIB_VERSION}") + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(LIB_EXT dylib) + set(LIB_EXTS ${SHLIB_VERSION}.${LIB_EXT}) + else() + set(LIB_EXT so) + set(LIB_EXTS ${LIB_EXT}.${SHLIB_VERSION}) + endif() + list(APPEND LIB_EXTS ${LIB_EXT}) else() set(SHARED no-shared) + set(LIB_EXTS a) endif() +foreach(lib ssl crypto) + foreach(ext ${LIB_EXTS}) + list(APPEND INSTALL_LIBS "${BUILDDIR}/lib${lib}.${ext}") + endforeach() +endforeach() if(CMAKE_HOST_WIN32) set(ENV_COMMAND set) @@ -127,10 +148,10 @@ add_custom_target(build_libs ALL VERBATIM WORKING_DIRECTORY "${BUILDDIR}" DEPENDS depend - BYPRODUCTS "${BUILDDIR}/libssl.a" "${BUILDDIR}/libcrypto.a" + BYPRODUCTS ${INSTALL_LIBS} ) install( - FILES "${BUILDDIR}/libssl.a" "${BUILDDIR}/libcrypto.a" + FILES ${INSTALL_LIBS} DESTINATION lib ) diff --git a/cmake/ports/openssl-unix/CONTROL b/cmake/ports/openssl-unix/CONTROL index 6413eb3712..873046ca85 100644 --- a/cmake/ports/openssl-unix/CONTROL +++ b/cmake/ports/openssl-unix/CONTROL @@ -1,3 +1,3 @@ Source: openssl-unix -Version: 1.0.2p +Version: 1.0.2s-1 Description: OpenSSL is an open source project that provides a robust, commercial-grade, and full-featured toolkit for the Transport Layer Security (TLS) and Secure Sockets Layer (SSL) protocols. It is also a general-purpose cryptography library. diff --git a/cmake/ports/openssl-unix/portfile.cmake b/cmake/ports/openssl-unix/portfile.cmake index 1484fc66c6..1047bdeecb 100644 --- a/cmake/ports/openssl-unix/portfile.cmake +++ b/cmake/ports/openssl-unix/portfile.cmake @@ -1,25 +1,37 @@ +include(vcpkg_common_functions) + if(VCPKG_CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" OR NOT VCPKG_CMAKE_SYSTEM_NAME) message(FATAL_ERROR "This port is only for openssl on Unix-like systems") endif() -include(vcpkg_common_functions) -set(OPENSSL_VERSION 1.0.2p) -set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src/openssl-${OPENSSL_VERSION}) +if(EXISTS "${CURRENT_INSTALLED_DIR}/include/openssl/ssl.h") + message(WARNING "Can't build openssl if libressl is installed. Please remove libressl, and try install openssl again if you need it. Build will continue but there might be problems since libressl is only a subset of openssl") + set(VCPKG_POLICY_EMPTY_PACKAGE enabled) + return() +endif() + +if (VCPKG_LIBRARY_LINKAGE STREQUAL static) + set(VCPKG_LIBRARY_LINKAGE dynamic) +endif() vcpkg_find_acquire_program(PERL) +set(OPENSSL_VERSION 1.0.2s) + vcpkg_download_distfile(OPENSSL_SOURCE_ARCHIVE URLS "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz" "https://www.openssl.org/source/old/1.0.2/openssl-${OPENSSL_VERSION}.tar.gz" FILENAME "openssl-${OPENSSL_VERSION}.tar.gz" - SHA512 958c5a7c3324bbdc8f07dfb13e11329d9a1b4452c07cf41fbd2d42b5fe29c95679332a3476d24c2dc2b88be16e4a24744aba675a05a388c0905756c77a8a2f16 + SHA512 9f745452c4f777df694158e95003cde78a2cf8199bc481a563ec36644664c3c1415a774779b9791dd18f2aeb57fa1721cb52b3db12d025955e970071d5b66d2a ) -vcpkg_extract_source_archive(${OPENSSL_SOURCE_ARCHIVE}) -vcpkg_apply_patches( - SOURCE_PATH ${MASTER_COPY_SOURCE_PATH} - PATCHES ${CMAKE_CURRENT_LIST_DIR}/ConfigureIncludeQuotesFix.patch - ${CMAKE_CURRENT_LIST_DIR}/STRINGIFYPatch.patch - ${CMAKE_CURRENT_LIST_DIR}/EmbedSymbolsInStaticLibsZ7.patch +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH MASTER_COPY_SOURCE_PATH + ARCHIVE ${OPENSSL_SOURCE_ARCHIVE} + REF ${OPENSSL_VERSION} + PATCHES + ConfigureIncludeQuotesFix.patch + STRINGIFYPatch.patch + EmbedSymbolsInStaticLibsZ7.patch ) if(CMAKE_HOST_WIN32) diff --git a/cmake/ports/polyvox/CONTROL b/cmake/ports/polyvox/CONTROL new file mode 100644 index 0000000000..89a01813c7 --- /dev/null +++ b/cmake/ports/polyvox/CONTROL @@ -0,0 +1,3 @@ +Source: polyvox +Version: 20150715 +Description: Polyvox diff --git a/cmake/ports/polyvox/portfile.cmake b/cmake/ports/polyvox/portfile.cmake new file mode 100644 index 0000000000..54cc74d4dd --- /dev/null +++ b/cmake/ports/polyvox/portfile.cmake @@ -0,0 +1,90 @@ +include(vcpkg_common_functions) + +# else Linux desktop +vcpkg_download_distfile( + SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/dependencies/polyvox-master-2015-7-15.zip + SHA512 cc04cd43ae74b9c7bb065953540c0048053fcba6b52dc4218b3d9431fba178d65ad4f6c53cc1122ba61d0ab4061e99a7ebbb15db80011d607c5070ebebf8eddc + FILENAME polyvox.zip +) + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${SOURCE_ARCHIVE} +) + +vcpkg_configure_cmake( + SOURCE_PATH ${SOURCE_PATH} + PREFER_NINJA + OPTIONS -DENABLE_EXAMPLES=OFF -DENABLE_BINDINGS=OFF +) + +vcpkg_install_cmake() + +file(INSTALL ${SOURCE_PATH}/LICENSE.TXT DESTINATION ${CURRENT_PACKAGES_DIR}/share/polyvox RENAME copyright) +file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/include) +file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/lib) +file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/lib) +if(WIN32) + file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxCore/lib/Release/PolyVoxCore.lib ${CURRENT_PACKAGES_DIR}/lib/PolyVoxCore.lib) + file(RENAME ${CURRENT_PACKAGES_DIR}/debug/PolyVoxCore/lib/Debug/PolyVoxCore.lib ${CURRENT_PACKAGES_DIR}/debug/lib/PolyVoxCore.lib) + file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxUtil/lib/PolyVoxUtil.lib ${CURRENT_PACKAGES_DIR}/lib/PolyVoxUtil.lib) + file(RENAME ${CURRENT_PACKAGES_DIR}/debug/PolyVoxUtil/lib/PolyVoxUtil.lib ${CURRENT_PACKAGES_DIR}/debug/lib/PolyVoxUtil.lib) + file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxCore/include/PolyVoxCore ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore) + file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxUtil/include/PolyVoxUtil ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil) + file(RENAME ${CURRENT_PACKAGES_DIR}/cmake/PolyVoxConfig.cmake ${CURRENT_PACKAGES_DIR}/share/polyvox/polyvoxConfig.cmake) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/cmake) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/cmake) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/PolyVoxCore) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/PolyVoxUtil) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/PolyVoxUtil) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/PolyVoxCore) +else() + file(GLOB LIBS ${CURRENT_PACKAGES_DIR}/lib/Release/*) + foreach(_lib ${LIBS}) + file(RELATIVE_PATH _libName ${CURRENT_PACKAGES_DIR}/lib/Release ${_lib}) + file(RENAME ${_lib} ${CURRENT_PACKAGES_DIR}/lib/${_libName}) + endforeach() + file(GLOB LIBS ${CURRENT_PACKAGES_DIR}/debug/lib/Debug/*) + foreach(_lib ${LIBS}) + file(RELATIVE_PATH _libName ${CURRENT_PACKAGES_DIR}/debug/lib/Debug ${_lib}) + file(RENAME ${_lib} ${CURRENT_PACKAGES_DIR}/debug/lib/${_libName}) + endforeach() + file(RENAME ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore.temp) + file(RENAME ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore.temp/PolyVoxCore ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore.temp) + file(RENAME ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil.temp) + file(RENAME ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil.temp/PolyVoxUtil ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil.temp) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/share/doc) +endif() +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/Release) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/RelWithDebInfo) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/Debug) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/Release) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/RelWithDebInfo) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/Debug) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share) + +# if (APPLE) +# set(INSTALL_NAME_LIBRARY_DIR ${INSTALL_DIR}/lib) +# ExternalProject_Add_Step( +# ${EXTERNAL_NAME} +# change-install-name-debug +# COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" +# COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR}/Debug -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake +# DEPENDEES install +# WORKING_DIRECTORY +# LOG 1 +# ) +# ExternalProject_Add_Step( +# ${EXTERNAL_NAME} +# change-install-name-release +# COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" +# COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR}/Release -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake +# DEPENDEES install +# WORKING_DIRECTORY +# LOG 1 +# ) +# endif () diff --git a/cmake/ports/quazip/CONTROL b/cmake/ports/quazip/CONTROL new file mode 100644 index 0000000000..392c47d7c1 --- /dev/null +++ b/cmake/ports/quazip/CONTROL @@ -0,0 +1,4 @@ +Source: quazip +Version: 0.7.3 +Description: Zip file manipulation for Qt +Build-Depends: zlib diff --git a/cmake/ports/quazip/portfile.cmake b/cmake/ports/quazip/portfile.cmake new file mode 100644 index 0000000000..07a8ebc3c3 --- /dev/null +++ b/cmake/ports/quazip/portfile.cmake @@ -0,0 +1,91 @@ +include(vcpkg_common_functions) + +file(READ "${VCPKG_ROOT_DIR}/_env/QT_CMAKE_PREFIX_PATH.txt" QT_CMAKE_PREFIX_PATH) + +vcpkg_download_distfile( + SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/dependencies/quazip-0.7.3.zip + SHA512 b2d812b6346317fd6d8f4f1344ad48b721d697c429acc8b7e7cb776ce5cba15a59efd64b2c5ae1f31b5a3c928014f084aa1379fd55d8a452a6cf4fd510b3afcc + FILENAME quazip.zip +) + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${SOURCE_ARCHIVE} + NO_REMOVE_ONE_LEVEL +) + +vcpkg_configure_cmake( + SOURCE_PATH ${SOURCE_PATH} + PREFER_NINJA + OPTIONS -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DBUILD_WITH_QT4=OFF +) + +vcpkg_install_cmake() + +if (WIN32) + file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/bin) + file(RENAME ${CURRENT_PACKAGES_DIR}/lib/quazip5.dll ${CURRENT_PACKAGES_DIR}/bin/quazip5.dll) + file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/bin) + file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/quazip5d.dll ${CURRENT_PACKAGES_DIR}/debug/bin/quazip5.dll) +elseif(DEFINED VCPKG_TARGET_IS_LINUX) + # We only want static libs. + file(GLOB QUAZIP5_DYNAMIC_LIBS ${CURRENT_PACKAGES_DIR}/lib/libquazip5.so* ${CURRENT_PACKAGES_DIR}/debug/lib/libquazip5d.so*) + file(REMOVE ${QUAZIP5_DYNAMIC_LIBS}) +endif() +file(INSTALL ${SOURCE_PATH}/COPYING DESTINATION ${CURRENT_PACKAGES_DIR}/share/quazip RENAME copyright) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) + +# set(QUAZIP_CMAKE_ARGS +# -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} +# -DCMAKE_INSTALL_NAME_DIR:PATH=/lib +# -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} +# -DZLIB_ROOT=${VCPKG_INSTALL_ROOT} +# -DCMAKE_POSITION_INDEPENDENT_CODE=ON) + +# if (NOT APPLE) +# set(QUAZIP_CMAKE_ARGS ${QUAZIP_CMAKE_ARGS} -DCMAKE_CXX_STANDARD=11) +# endif () + +# ExternalProject_Add( +# ${EXTERNAL_NAME} +# URL +# URL_MD5 ed03754d39b9da1775771819b8001d45 +# BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build +# CMAKE_ARGS ${QUAZIP_CMAKE_ARGS} +# LOG_DOWNLOAD 1 +# LOG_CONFIGURE 1 +# LOG_BUILD 1 +# ) + +# # Hide this external target (for ide users) +# set_target_properties(${EXTERNAL_NAME} PROPERTIES +# FOLDER "hidden/externals" +# INSTALL_NAME_DIR ${INSTALL_DIR}/lib +# BUILD_WITH_INSTALL_RPATH True) + +# ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +# set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories") +# set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories") +# set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL") + +# if (APPLE) +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library") +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library") +# elseif (WIN32) +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library") +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library") +# elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library") +# else () +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") +# set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") +# endif () + +# include(SelectLibraryConfigurations) +# select_library_configurations(${EXTERNAL_NAME_UPPER}) + +# # Force selected libraries into the cache +# set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries") +# set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries") diff --git a/cmake/ports/sdl2/disable-hidapi-for-uwp.patch b/cmake/ports/sdl2/disable-hidapi-for-uwp.patch index 13d1ca8d1f..670aa801a5 100644 --- a/cmake/ports/sdl2/disable-hidapi-for-uwp.patch +++ b/cmake/ports/sdl2/disable-hidapi-for-uwp.patch @@ -1,11 +1,11 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -172,7 +172,7 @@ - # requires root permissions to open devices, so that's not generally - # useful, and we'll disable this by default on Unix. Windows and macOS - # can use it without root access, though, so enable by default there. --if(WINDOWS OR APPLE OR ANDROID) -+if((WINDOWS AND NOT WINDOWS_STORE) OR APPLE OR ANDROID) - set(HIDAPI_SKIP_LIBUSB TRUE) - else() - set(HIDAPI_SKIP_LIBUSB FALSE) +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -172,7 +172,7 @@ + # requires root permissions to open devices, so that's not generally + # useful, and we'll disable this by default on Unix. Windows and macOS + # can use it without root access, though, so enable by default there. +-if(WINDOWS OR APPLE OR ANDROID) ++if((WINDOWS AND NOT WINDOWS_STORE) OR APPLE OR ANDROID) + set(HIDAPI_SKIP_LIBUSB TRUE) + else() + set(HIDAPI_SKIP_LIBUSB FALSE) diff --git a/cmake/ports/sdl2/enable-winrt-cmake.patch b/cmake/ports/sdl2/enable-winrt-cmake.patch index 836ffcdd70..93aeda0b84 100644 --- a/cmake/ports/sdl2/enable-winrt-cmake.patch +++ b/cmake/ports/sdl2/enable-winrt-cmake.patch @@ -1,175 +1,175 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 0128c7a..bd534e4 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -5,6 +5,18 @@ endif() - cmake_minimum_required(VERSION 2.8.11) - project(SDL2 C CXX) - -+if(WINDOWS_STORE) -+ enable_language(CXX) -+ cmake_minimum_required(VERSION 3.11) -+ add_definitions(-DSDL_BUILDING_WINRT=1 -ZW) -+ link_libraries( -+ -nodefaultlib:vccorlib$<$:d> -+ -nodefaultlib:msvcrt$<$:d> -+ vccorlib$<$:d>.lib -+ msvcrt$<$:d>.lib -+ ) -+endif() -+ - # !!! FIXME: this should probably do "MACOSX_RPATH ON" as a target property - # !!! FIXME: for the SDL2 shared library (so you get an - # !!! FIXME: install_name ("soname") of "@rpath/libSDL-whatever.dylib" -@@ -1209,6 +1221,11 @@ elseif(WINDOWS) - file(GLOB CORE_SOURCES ${SDL2_SOURCE_DIR}/src/core/windows/*.c) - set(SOURCE_FILES ${SOURCE_FILES} ${CORE_SOURCES}) - -+ if(WINDOWS_STORE) -+ file(GLOB WINRT_SOURCE_FILES ${SDL2_SOURCE_DIR}/src/core/winrt/*.c ${SDL2_SOURCE_DIR}/src/core/winrt/*.cpp) -+ list(APPEND SOURCE_FILES ${WINRT_SOURCE_FILES}) -+ endif() -+ - if(MSVC) - # Prevent codegen that would use the VC runtime libraries. - set_property(DIRECTORY . APPEND PROPERTY COMPILE_OPTIONS "/GS-") -@@ -1254,6 +1271,9 @@ elseif(WINDOWS) - check_include_file(ddraw.h HAVE_DDRAW_H) - check_include_file(dsound.h HAVE_DSOUND_H) - check_include_file(dinput.h HAVE_DINPUT_H) -+ if(WINDOWS_STORE OR VCPKG_TARGET_TRIPLET MATCHES "arm-windows") -+ set(HAVE_DINPUT_H 0) -+ endif() - check_include_file(dxgi.h HAVE_DXGI_H) - if(HAVE_D3D_H OR HAVE_D3D11_H OR HAVE_DDRAW_H OR HAVE_DSOUND_H OR HAVE_DINPUT_H) - set(HAVE_DIRECTX TRUE) -@@ -1272,18 +1292,20 @@ elseif(WINDOWS) - check_include_file(endpointvolume.h HAVE_ENDPOINTVOLUME_H) - - if(SDL_AUDIO) -+ if(NOT WINDOWS_STORE) - set(SDL_AUDIO_DRIVER_WINMM 1) - file(GLOB WINMM_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/winmm/*.c) - set(SOURCE_FILES ${SOURCE_FILES} ${WINMM_AUDIO_SOURCES}) -+ endif() - set(HAVE_SDL_AUDIO TRUE) - -- if(HAVE_DSOUND_H) -+ if(HAVE_DSOUND_H AND NOT WINDOWS_STORE) - set(SDL_AUDIO_DRIVER_DSOUND 1) - file(GLOB DSOUND_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/directsound/*.c) - set(SOURCE_FILES ${SOURCE_FILES} ${DSOUND_AUDIO_SOURCES}) - endif() - -- if(WASAPI AND HAVE_AUDIOCLIENT_H AND HAVE_MMDEVICEAPI_H) -+ if(WASAPI AND HAVE_AUDIOCLIENT_H AND HAVE_MMDEVICEAPI_H AND NOT WINDOWS_STORE) - set(SDL_AUDIO_DRIVER_WASAPI 1) - file(GLOB WASAPI_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/wasapi/*.c) - set(SOURCE_FILES ${SOURCE_FILES} ${WASAPI_AUDIO_SOURCES}) -@@ -1295,11 +1317,20 @@ elseif(WINDOWS) - if(NOT SDL_LOADSO) - message_error("SDL_VIDEO requires SDL_LOADSO, which is not enabled") - endif() -+ if(WINDOWS_STORE) -+ set(SDL_VIDEO_DRIVER_WINRT 1) -+ file(GLOB WIN_VIDEO_SOURCES -+ ${SDL2_SOURCE_DIR}/src/video/winrt/*.c -+ ${SDL2_SOURCE_DIR}/src/video/winrt/*.cpp -+ ${SDL2_SOURCE_DIR}/src/render/direct3d11/*.cpp -+ ) -+ else() - set(SDL_VIDEO_DRIVER_WINDOWS 1) - file(GLOB WIN_VIDEO_SOURCES ${SDL2_SOURCE_DIR}/src/video/windows/*.c) -+ endif() - set(SOURCE_FILES ${SOURCE_FILES} ${WIN_VIDEO_SOURCES}) - -- if(RENDER_D3D AND HAVE_D3D_H) -+ if(RENDER_D3D AND HAVE_D3D_H AND NOT WINDOWS_STORE) - set(SDL_VIDEO_RENDER_D3D 1) - set(HAVE_RENDER_D3D TRUE) - endif() -@@ -1322,20 +1353,31 @@ elseif(WINDOWS) - endif() - - if(SDL_POWER) -+ if(WINDOWS_STORE) -+ set(SDL_POWER_WINRT 1) -+ set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/power/winrt/SDL_syspower.cpp) -+ else() - set(SDL_POWER_WINDOWS 1) - set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/power/windows/SDL_syspower.c) -+ endif() - set(HAVE_SDL_POWER TRUE) - endif() - - if(SDL_FILESYSTEM) - set(SDL_FILESYSTEM_WINDOWS 1) -+ if(WINDOWS_STORE) -+ file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/winrt/*.cpp) -+ else() - file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/windows/*.c) -+ endif() - set(SOURCE_FILES ${SOURCE_FILES} ${FILESYSTEM_SOURCES}) - set(HAVE_SDL_FILESYSTEM TRUE) - endif() - - # Libraries for Win32 native and MinGW -+ if(NOT WINDOWS_STORE) - list(APPEND EXTRA_LIBS user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32) -+ endif() - - # TODO: in configure.ac the check for timers is set on - # cygwin | mingw32* - does this include mingw32CE? -@@ -1357,7 +1399,7 @@ elseif(WINDOWS) - set(SOURCE_FILES ${SOURCE_FILES} ${CORE_SOURCES}) - - if(SDL_VIDEO) -- if(VIDEO_OPENGL) -+ if(VIDEO_OPENGL AND NOT WINDOWS_STORE) - set(SDL_VIDEO_OPENGL 1) - set(SDL_VIDEO_OPENGL_WGL 1) - set(SDL_VIDEO_RENDER_OGL 1) -@@ -1788,12 +1830,14 @@ endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") - - # Always build SDLmain -+if(NOT WINDOWS_STORE) - add_library(SDL2main STATIC ${SDLMAIN_SOURCES}) - target_include_directories(SDL2main PUBLIC "$" $) - set(_INSTALL_LIBS "SDL2main") - if (NOT ANDROID) - set_target_properties(SDL2main PROPERTIES DEBUG_POSTFIX ${SDL_CMAKE_DEBUG_POSTFIX}) - endif() -+endif() - - if(SDL_SHARED) - add_library(SDL2 SHARED ${SOURCE_FILES} ${VERSION_SOURCES}) -diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake -index 48dd2d4..0c4fa28 100644 ---- a/include/SDL_config.h.cmake -+++ b/include/SDL_config.h.cmake -@@ -326,6 +326,7 @@ - #cmakedefine SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC @SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC@ - #cmakedefine SDL_VIDEO_DRIVER_DUMMY @SDL_VIDEO_DRIVER_DUMMY@ - #cmakedefine SDL_VIDEO_DRIVER_WINDOWS @SDL_VIDEO_DRIVER_WINDOWS@ -+#cmakedefine SDL_VIDEO_DRIVER_WINRT @SDL_VIDEO_DRIVER_WINRT@ - #cmakedefine SDL_VIDEO_DRIVER_WAYLAND @SDL_VIDEO_DRIVER_WAYLAND@ - #cmakedefine SDL_VIDEO_DRIVER_RPI @SDL_VIDEO_DRIVER_RPI@ - #cmakedefine SDL_VIDEO_DRIVER_VIVANTE @SDL_VIDEO_DRIVER_VIVANTE@ -@@ -391,6 +392,7 @@ - #cmakedefine SDL_POWER_ANDROID @SDL_POWER_ANDROID@ - #cmakedefine SDL_POWER_LINUX @SDL_POWER_LINUX@ - #cmakedefine SDL_POWER_WINDOWS @SDL_POWER_WINDOWS@ -+#cmakedefine SDL_POWER_WINRT @SDL_POWER_WINRT@ - #cmakedefine SDL_POWER_MACOSX @SDL_POWER_MACOSX@ - #cmakedefine SDL_POWER_HAIKU @SDL_POWER_HAIKU@ - #cmakedefine SDL_POWER_EMSCRIPTEN @SDL_POWER_EMSCRIPTEN@ -@@ -413,7 +415,7 @@ - #cmakedefine SDL_LIBSAMPLERATE_DYNAMIC @SDL_LIBSAMPLERATE_DYNAMIC@ - - /* Platform specific definitions */ --#if !defined(__WIN32__) -+#if !defined(__WIN32__) && !defined(__WINRT__) - # if !defined(_STDINT_H_) && !defined(_STDINT_H) && !defined(HAVE_STDINT_H) && !defined(_HAVE_STDINT_H) - typedef unsigned int size_t; - typedef signed char int8_t; +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 0128c7a..bd534e4 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -5,6 +5,18 @@ endif() + cmake_minimum_required(VERSION 2.8.11) + project(SDL2 C CXX) + ++if(WINDOWS_STORE) ++ enable_language(CXX) ++ cmake_minimum_required(VERSION 3.11) ++ add_definitions(-DSDL_BUILDING_WINRT=1 -ZW) ++ link_libraries( ++ -nodefaultlib:vccorlib$<$:d> ++ -nodefaultlib:msvcrt$<$:d> ++ vccorlib$<$:d>.lib ++ msvcrt$<$:d>.lib ++ ) ++endif() ++ + # !!! FIXME: this should probably do "MACOSX_RPATH ON" as a target property + # !!! FIXME: for the SDL2 shared library (so you get an + # !!! FIXME: install_name ("soname") of "@rpath/libSDL-whatever.dylib" +@@ -1209,6 +1221,11 @@ elseif(WINDOWS) + file(GLOB CORE_SOURCES ${SDL2_SOURCE_DIR}/src/core/windows/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${CORE_SOURCES}) + ++ if(WINDOWS_STORE) ++ file(GLOB WINRT_SOURCE_FILES ${SDL2_SOURCE_DIR}/src/core/winrt/*.c ${SDL2_SOURCE_DIR}/src/core/winrt/*.cpp) ++ list(APPEND SOURCE_FILES ${WINRT_SOURCE_FILES}) ++ endif() ++ + if(MSVC) + # Prevent codegen that would use the VC runtime libraries. + set_property(DIRECTORY . APPEND PROPERTY COMPILE_OPTIONS "/GS-") +@@ -1254,6 +1271,9 @@ elseif(WINDOWS) + check_include_file(ddraw.h HAVE_DDRAW_H) + check_include_file(dsound.h HAVE_DSOUND_H) + check_include_file(dinput.h HAVE_DINPUT_H) ++ if(WINDOWS_STORE OR VCPKG_TARGET_TRIPLET MATCHES "arm-windows") ++ set(HAVE_DINPUT_H 0) ++ endif() + check_include_file(dxgi.h HAVE_DXGI_H) + if(HAVE_D3D_H OR HAVE_D3D11_H OR HAVE_DDRAW_H OR HAVE_DSOUND_H OR HAVE_DINPUT_H) + set(HAVE_DIRECTX TRUE) +@@ -1272,18 +1292,20 @@ elseif(WINDOWS) + check_include_file(endpointvolume.h HAVE_ENDPOINTVOLUME_H) + + if(SDL_AUDIO) ++ if(NOT WINDOWS_STORE) + set(SDL_AUDIO_DRIVER_WINMM 1) + file(GLOB WINMM_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/winmm/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${WINMM_AUDIO_SOURCES}) ++ endif() + set(HAVE_SDL_AUDIO TRUE) + +- if(HAVE_DSOUND_H) ++ if(HAVE_DSOUND_H AND NOT WINDOWS_STORE) + set(SDL_AUDIO_DRIVER_DSOUND 1) + file(GLOB DSOUND_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/directsound/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${DSOUND_AUDIO_SOURCES}) + endif() + +- if(WASAPI AND HAVE_AUDIOCLIENT_H AND HAVE_MMDEVICEAPI_H) ++ if(WASAPI AND HAVE_AUDIOCLIENT_H AND HAVE_MMDEVICEAPI_H AND NOT WINDOWS_STORE) + set(SDL_AUDIO_DRIVER_WASAPI 1) + file(GLOB WASAPI_AUDIO_SOURCES ${SDL2_SOURCE_DIR}/src/audio/wasapi/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${WASAPI_AUDIO_SOURCES}) +@@ -1295,11 +1317,20 @@ elseif(WINDOWS) + if(NOT SDL_LOADSO) + message_error("SDL_VIDEO requires SDL_LOADSO, which is not enabled") + endif() ++ if(WINDOWS_STORE) ++ set(SDL_VIDEO_DRIVER_WINRT 1) ++ file(GLOB WIN_VIDEO_SOURCES ++ ${SDL2_SOURCE_DIR}/src/video/winrt/*.c ++ ${SDL2_SOURCE_DIR}/src/video/winrt/*.cpp ++ ${SDL2_SOURCE_DIR}/src/render/direct3d11/*.cpp ++ ) ++ else() + set(SDL_VIDEO_DRIVER_WINDOWS 1) + file(GLOB WIN_VIDEO_SOURCES ${SDL2_SOURCE_DIR}/src/video/windows/*.c) ++ endif() + set(SOURCE_FILES ${SOURCE_FILES} ${WIN_VIDEO_SOURCES}) + +- if(RENDER_D3D AND HAVE_D3D_H) ++ if(RENDER_D3D AND HAVE_D3D_H AND NOT WINDOWS_STORE) + set(SDL_VIDEO_RENDER_D3D 1) + set(HAVE_RENDER_D3D TRUE) + endif() +@@ -1322,20 +1353,31 @@ elseif(WINDOWS) + endif() + + if(SDL_POWER) ++ if(WINDOWS_STORE) ++ set(SDL_POWER_WINRT 1) ++ set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/power/winrt/SDL_syspower.cpp) ++ else() + set(SDL_POWER_WINDOWS 1) + set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/power/windows/SDL_syspower.c) ++ endif() + set(HAVE_SDL_POWER TRUE) + endif() + + if(SDL_FILESYSTEM) + set(SDL_FILESYSTEM_WINDOWS 1) ++ if(WINDOWS_STORE) ++ file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/winrt/*.cpp) ++ else() + file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/windows/*.c) ++ endif() + set(SOURCE_FILES ${SOURCE_FILES} ${FILESYSTEM_SOURCES}) + set(HAVE_SDL_FILESYSTEM TRUE) + endif() + + # Libraries for Win32 native and MinGW ++ if(NOT WINDOWS_STORE) + list(APPEND EXTRA_LIBS user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32) ++ endif() + + # TODO: in configure.ac the check for timers is set on + # cygwin | mingw32* - does this include mingw32CE? +@@ -1357,7 +1399,7 @@ elseif(WINDOWS) + set(SOURCE_FILES ${SOURCE_FILES} ${CORE_SOURCES}) + + if(SDL_VIDEO) +- if(VIDEO_OPENGL) ++ if(VIDEO_OPENGL AND NOT WINDOWS_STORE) + set(SDL_VIDEO_OPENGL 1) + set(SDL_VIDEO_OPENGL_WGL 1) + set(SDL_VIDEO_RENDER_OGL 1) +@@ -1788,12 +1830,14 @@ endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + + # Always build SDLmain ++if(NOT WINDOWS_STORE) + add_library(SDL2main STATIC ${SDLMAIN_SOURCES}) + target_include_directories(SDL2main PUBLIC "$" $) + set(_INSTALL_LIBS "SDL2main") + if (NOT ANDROID) + set_target_properties(SDL2main PROPERTIES DEBUG_POSTFIX ${SDL_CMAKE_DEBUG_POSTFIX}) + endif() ++endif() + + if(SDL_SHARED) + add_library(SDL2 SHARED ${SOURCE_FILES} ${VERSION_SOURCES}) +diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake +index 48dd2d4..0c4fa28 100644 +--- a/include/SDL_config.h.cmake ++++ b/include/SDL_config.h.cmake +@@ -326,6 +326,7 @@ + #cmakedefine SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC @SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC@ + #cmakedefine SDL_VIDEO_DRIVER_DUMMY @SDL_VIDEO_DRIVER_DUMMY@ + #cmakedefine SDL_VIDEO_DRIVER_WINDOWS @SDL_VIDEO_DRIVER_WINDOWS@ ++#cmakedefine SDL_VIDEO_DRIVER_WINRT @SDL_VIDEO_DRIVER_WINRT@ + #cmakedefine SDL_VIDEO_DRIVER_WAYLAND @SDL_VIDEO_DRIVER_WAYLAND@ + #cmakedefine SDL_VIDEO_DRIVER_RPI @SDL_VIDEO_DRIVER_RPI@ + #cmakedefine SDL_VIDEO_DRIVER_VIVANTE @SDL_VIDEO_DRIVER_VIVANTE@ +@@ -391,6 +392,7 @@ + #cmakedefine SDL_POWER_ANDROID @SDL_POWER_ANDROID@ + #cmakedefine SDL_POWER_LINUX @SDL_POWER_LINUX@ + #cmakedefine SDL_POWER_WINDOWS @SDL_POWER_WINDOWS@ ++#cmakedefine SDL_POWER_WINRT @SDL_POWER_WINRT@ + #cmakedefine SDL_POWER_MACOSX @SDL_POWER_MACOSX@ + #cmakedefine SDL_POWER_HAIKU @SDL_POWER_HAIKU@ + #cmakedefine SDL_POWER_EMSCRIPTEN @SDL_POWER_EMSCRIPTEN@ +@@ -413,7 +415,7 @@ + #cmakedefine SDL_LIBSAMPLERATE_DYNAMIC @SDL_LIBSAMPLERATE_DYNAMIC@ + + /* Platform specific definitions */ +-#if !defined(__WIN32__) ++#if !defined(__WIN32__) && !defined(__WINRT__) + # if !defined(_STDINT_H_) && !defined(_STDINT_H) && !defined(HAVE_STDINT_H) && !defined(_HAVE_STDINT_H) + typedef unsigned int size_t; + typedef signed char int8_t; diff --git a/cmake/ports/sdl2/fix-arm64-headers.patch b/cmake/ports/sdl2/fix-arm64-headers.patch index 862b65f0b5..3caaf493cd 100644 --- a/cmake/ports/sdl2/fix-arm64-headers.patch +++ b/cmake/ports/sdl2/fix-arm64-headers.patch @@ -1,14 +1,14 @@ ---- a/include/SDL_cpuinfo.h Tue Jul 23 21:41:00 2019 -0400 -+++ b/include/SDL_cpuinfo.h Tue Aug 13 20:26:27 2019 -0700 -@@ -73,8 +73,8 @@ - # define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */ - # endif - # if defined (_M_ARM64) --# include --# include -+# include -+# include - # define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */ - # endif - # endif +--- a/include/SDL_cpuinfo.h Tue Jul 23 21:41:00 2019 -0400 ++++ b/include/SDL_cpuinfo.h Tue Aug 13 20:26:27 2019 -0700 +@@ -73,8 +73,8 @@ + # define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */ + # endif + # if defined (_M_ARM64) +-# include +-# include ++# include ++# include + # define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */ + # endif + # endif \ No newline at end of file diff --git a/cmake/ports/sdl2/fix-x86-windows.patch b/cmake/ports/sdl2/fix-x86-windows.patch index 853b68722c..c83773b765 100644 --- a/cmake/ports/sdl2/fix-x86-windows.patch +++ b/cmake/ports/sdl2/fix-x86-windows.patch @@ -1,15 +1,15 @@ -diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c -index ff23c5e..fc90bba 100644 ---- a/src/events/SDL_mouse.c -+++ b/src/events/SDL_mouse.c -@@ -20,6 +20,10 @@ - */ - #include "../SDL_internal.h" - -+#ifdef __WIN32__ -+#include "../core/windows/SDL_windows.h" -+#endif -+ - /* General mouse handling code for SDL */ - - #include "SDL_assert.h" +diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c +index ff23c5e..fc90bba 100644 +--- a/src/events/SDL_mouse.c ++++ b/src/events/SDL_mouse.c +@@ -20,6 +20,10 @@ + */ + #include "../SDL_internal.h" + ++#ifdef __WIN32__ ++#include "../core/windows/SDL_windows.h" ++#endif ++ + /* General mouse handling code for SDL */ + + #include "SDL_assert.h" diff --git a/cmake/ports/sdl2/vcpkg-cmake-wrapper.cmake b/cmake/ports/sdl2/vcpkg-cmake-wrapper.cmake index c99178db1a..067828055f 100644 --- a/cmake/ports/sdl2/vcpkg-cmake-wrapper.cmake +++ b/cmake/ports/sdl2/vcpkg-cmake-wrapper.cmake @@ -1,8 +1,8 @@ -_find_package(${ARGS}) -if(TARGET SDL2::SDL2 AND NOT TARGET SDL2::SDL2-static) - add_library( SDL2::SDL2-static INTERFACE IMPORTED) - set_target_properties(SDL2::SDL2-static PROPERTIES INTERFACE_LINK_LIBRARIES "SDL2::SDL2") -elseif(TARGET SDL2::SDL2-static AND NOT TARGET SDL2::SDL2) - add_library( SDL2::SDL2 INTERFACE IMPORTED) - set_target_properties(SDL2::SDL2 PROPERTIES INTERFACE_LINK_LIBRARIES "SDL2::SDL2-static") -endif() +_find_package(${ARGS}) +if(TARGET SDL2::SDL2 AND NOT TARGET SDL2::SDL2-static) + add_library( SDL2::SDL2-static INTERFACE IMPORTED) + set_target_properties(SDL2::SDL2-static PROPERTIES INTERFACE_LINK_LIBRARIES "SDL2::SDL2") +elseif(TARGET SDL2::SDL2-static AND NOT TARGET SDL2::SDL2) + add_library( SDL2::SDL2 INTERFACE IMPORTED) + set_target_properties(SDL2::SDL2 PROPERTIES INTERFACE_LINK_LIBRARIES "SDL2::SDL2-static") +endif() diff --git a/cmake/ports/sranipal/CONTROL b/cmake/ports/sranipal/CONTROL new file mode 100644 index 0000000000..3f878b1c4d --- /dev/null +++ b/cmake/ports/sranipal/CONTROL @@ -0,0 +1,3 @@ +Source: sranipal +Version: 1.1.0.1 +Description: SRanipal diff --git a/cmake/ports/sranipal/portfile.cmake b/cmake/ports/sranipal/portfile.cmake new file mode 100644 index 0000000000..da4646be1a --- /dev/null +++ b/cmake/ports/sranipal/portfile.cmake @@ -0,0 +1,20 @@ +include(vcpkg_common_functions) +set(SRANIPAL_VERSION 1.1.0.1) +set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src) + +if (WIN32) + vcpkg_download_distfile( + SRANIPAL_SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/seth/sranipal-1.1.0.1-windows.zip + SHA512 b09ce012abe4e3c71e8e69626bdd7823ff6576601a821ab365275f2764406a3e5f7b65fcf2eb1d0962eff31eb5958a148b00901f67c229dc6ace56eb5e6c9e1b + FILENAME sranipal-1.1.0.1-windows.zip + ) + + vcpkg_extract_source_archive(${SRANIPAL_SOURCE_ARCHIVE}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/sranipal/include DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/sranipal/lib DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/sranipal/debug DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/sranipal/bin DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/sranipal/share DESTINATION ${CURRENT_PACKAGES_DIR}) + +endif () diff --git a/cmake/ports/tbb/CMakeLists.txt b/cmake/ports/tbb/CMakeLists.txt index edfaf63200..863abb52de 100644 --- a/cmake/ports/tbb/CMakeLists.txt +++ b/cmake/ports/tbb/CMakeLists.txt @@ -4,7 +4,14 @@ file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*) file(COPY ${SOURCES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/src) include(${CMAKE_CURRENT_BINARY_DIR}/src/cmake/TBBBuild.cmake REQUIRED) -tbb_build(TBB_ROOT ${CMAKE_CURRENT_BINARY_DIR}/src MAKE_ARGS extra_inc=big_iron.inc) +if(NOT BUILD_SHARED_LIBS) + set(TBB_STATIC_INCLUDE extra_inc=big_iron.inc) +endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(FORWARD_SDK_ROOT "SDKROOT=${CMAKE_OSX_SYSROOT}") +endif() + +tbb_build(TBB_ROOT ${CMAKE_CURRENT_BINARY_DIR}/src MAKE_ARGS ${TBB_STATIC_INCLUDE} ${FORWARD_SDK_ROOT}) set(SUBDIR ${CMAKE_CURRENT_BINARY_DIR}/tbb_cmake_build/tbb_cmake_build_subdir) if(CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/cmake/ports/tbb/CONTROL b/cmake/ports/tbb/CONTROL index e87106dc3c..da29e48794 100644 --- a/cmake/ports/tbb/CONTROL +++ b/cmake/ports/tbb/CONTROL @@ -1,3 +1,4 @@ Source: tbb -Version: 2018_U5-4 +Version: 2019_U8-1 +Homepage: https://github.com/01org/tbb Description: Intel's Threading Building Blocks. diff --git a/cmake/ports/tbb/fix-static-build.patch b/cmake/ports/tbb/fix-static-build.patch new file mode 100644 index 0000000000..18f3e2b493 --- /dev/null +++ b/cmake/ports/tbb/fix-static-build.patch @@ -0,0 +1,13 @@ +diff --git a/src/tbb/cilk-tbb-interop.h b/src/tbb/cilk-tbb-interop.h +index 295734b..f35531e 100644 +--- a/src/tbb/cilk-tbb-interop.h ++++ b/src/tbb/cilk-tbb-interop.h +@@ -29,6 +29,8 @@ + #else + #ifdef IN_CILK_RUNTIME + #define CILK_EXPORT __declspec(dllexport) ++#elif defined(IN_CILK_STATIC) ++#define CILK_EXPORT /* nothing */ + #else + #define CILK_EXPORT __declspec(dllimport) + #endif // IN_CILK_RUNTIME diff --git a/cmake/ports/tbb/portfile.cmake b/cmake/ports/tbb/portfile.cmake index 9d0eb70ac1..4e81df37d1 100644 --- a/cmake/ports/tbb/portfile.cmake +++ b/cmake/ports/tbb/portfile.cmake @@ -1,20 +1,17 @@ include(vcpkg_common_functions) -if(NOT VCPKG_CMAKE_SYSTEM_NAME OR VCPKG_CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") - vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) -endif() - vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH - REPO 01org/tbb - REF 2018_U5 - SHA512 d86a110df9e55654c3638af8107fdfed2284434158cb5b3a38b3fc7cf143aa2346ee15da4e141e03fcfed864865462e6893c535b8dc227ebdd6ccd584c8a1e9b - HEAD_REF tbb_2018 + REPO oneapi-src/oneTBB + REF 4bdba61bafc6ba2d636f31564f1de5702d365cf7 + SHA512 f2a8d7e0476f846039390f4a79af3fe13770e23b01bf4741e738136f7ddb401357a0e50f35212e8d0fa5fc4cf1563418337309227d7243fc3676edd406ae652d + HEAD_REF tbb_2019 + PATCHES fix-static-build.patch ) file(COPY ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt DESTINATION ${SOURCE_PATH}) -if(VCPKG_CMAKE_SYSTEM_NAME AND NOT VCPKG_CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") +if (NOT VCPKG_TARGET_IS_WINDOWS) vcpkg_configure_cmake( SOURCE_PATH ${SOURCE_PATH} PREFER_NINJA @@ -22,7 +19,7 @@ if(VCPKG_CMAKE_SYSTEM_NAME AND NOT VCPKG_CMAKE_SYSTEM_NAME STREQUAL "WindowsStor vcpkg_install_cmake() - # Settings for TBBConfigForSource.cmake.in + # Settings for TBBConfigInternal.cmake.in set(TBB_LIB_EXT a) set(TBB_LIB_PREFIX lib) else() @@ -33,6 +30,32 @@ else() set(RELEASE_CONFIGURATION Release) set(DEBUG_CONFIGURATION Debug) endif() + + macro(CONFIGURE_PROJ_FILE arg) + set(CONFIGURE_FILE_NAME ${arg}) + set(CONFIGURE_BAK_FILE_NAME ${arg}.bak) + if (NOT EXISTS ${CONFIGURE_BAK_FILE_NAME}) + configure_file(${CONFIGURE_FILE_NAME} ${CONFIGURE_BAK_FILE_NAME} COPYONLY) + endif() + configure_file(${CONFIGURE_BAK_FILE_NAME} ${CONFIGURE_FILE_NAME} COPYONLY) + if (VCPKG_LIBRARY_LINKAGE STREQUAL static) + file(READ ${CONFIGURE_FILE_NAME} SLN_CONFIGURE) + string(REPLACE "DynamicLibrary<\/ConfigurationType>" + "StaticLibrary<\/ConfigurationType>" SLN_CONFIGURE "${SLN_CONFIGURE}") + string(REPLACE "\/D_CRT_SECURE_NO_DEPRECATE" + "\/D_CRT_SECURE_NO_DEPRECATE \/DIN_CILK_STATIC" SLN_CONFIGURE "${SLN_CONFIGURE}") + file(WRITE ${CONFIGURE_FILE_NAME} "${SLN_CONFIGURE}") + else() + file(READ ${CONFIGURE_FILE_NAME} SLN_CONFIGURE) + string(REPLACE "\/D_CRT_SECURE_NO_DEPRECATE" + "\/D_CRT_SECURE_NO_DEPRECATE \/DIN_CILK_RUNTIME" SLN_CONFIGURE "${SLN_CONFIGURE}") + file(WRITE ${CONFIGURE_FILE_NAME} "${SLN_CONFIGURE}") + endif() + endmacro() + + CONFIGURE_PROJ_FILE(${SOURCE_PATH}/build/vs2013/tbb.vcxproj) + CONFIGURE_PROJ_FILE(${SOURCE_PATH}/build/vs2013/tbbmalloc.vcxproj) + CONFIGURE_PROJ_FILE(${SOURCE_PATH}/build/vs2013/tbbmalloc_proxy.vcxproj) vcpkg_install_msbuild( SOURCE_PATH ${SOURCE_PATH} @@ -40,7 +63,7 @@ else() RELEASE_CONFIGURATION ${RELEASE_CONFIGURATION} DEBUG_CONFIGURATION ${DEBUG_CONFIGURATION} ) - # Settings for TBBConfigForSource.cmake.in + # Settings for TBBConfigInternal.cmake.in set(TBB_LIB_EXT lib) set(TBB_LIB_PREFIX) endif() @@ -50,12 +73,13 @@ file(COPY ${SOURCE_PATH}/include/serial DESTINATION ${CURRENT_PACKAGES_DIR}/include) -# Settings for TBBConfigForSource.cmake.in +# Settings for TBBConfigInternal.cmake.in if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") set(TBB_DEFAULT_COMPONENTS tbb tbbmalloc) else() set(TBB_DEFAULT_COMPONENTS tbb tbbmalloc tbbmalloc_proxy) endif() + file(READ "${SOURCE_PATH}/include/tbb/tbb_stddef.h" _tbb_stddef) string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" _tbb_ver_major "${_tbb_stddef}") string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" _tbb_ver_minor "${_tbb_stddef}") @@ -65,7 +89,7 @@ set(TBB_RELEASE_DIR "\${_tbb_root}/lib") set(TBB_DEBUG_DIR "\${_tbb_root}/debug/lib") configure_file( - ${SOURCE_PATH}/cmake/templates/TBBConfigForSource.cmake.in + ${SOURCE_PATH}/cmake/templates/TBBConfigInternal.cmake.in ${CURRENT_PACKAGES_DIR}/share/tbb/TBBConfig.cmake @ONLY ) @@ -76,6 +100,18 @@ string(REPLACE _contents "${_contents}" ) +string(REPLACE + "set(_tbb_release_lib \"/${TBB_LIB_PREFIX}" + "set(_tbb_release_lib \"\${_tbb_root}/lib/${TBB_LIB_PREFIX}" + _contents + "${_contents}" +) +string(REPLACE + "set(_tbb_debug_lib \"/${TBB_LIB_PREFIX}" + "set(_tbb_debug_lib \"\${_tbb_root}/debug/lib/${TBB_LIB_PREFIX}" + _contents + "${_contents}" +) string(REPLACE "SHARED IMPORTED)" "UNKNOWN IMPORTED)" _contents "${_contents}") file(WRITE ${CURRENT_PACKAGES_DIR}/share/tbb/TBBConfig.cmake "${_contents}") @@ -84,3 +120,4 @@ file(COPY ${SOURCE_PATH}/LICENSE ${CMAKE_CURRENT_LIST_DIR}/usage DESTINATION ${C file(RENAME ${CURRENT_PACKAGES_DIR}/share/tbb/LICENSE ${CURRENT_PACKAGES_DIR}/share/tbb/copyright) vcpkg_test_cmake(PACKAGE_NAME TBB) +# diff --git a/cmake/ports/vhacd/CONTROL b/cmake/ports/vhacd/CONTROL new file mode 100644 index 0000000000..a324451b00 --- /dev/null +++ b/cmake/ports/vhacd/CONTROL @@ -0,0 +1,3 @@ +Source: vhacd +Version: 20191029 +Description: vhacd diff --git a/cmake/ports/vhacd/copyright b/cmake/ports/vhacd/copyright new file mode 100644 index 0000000000..6706bf78c2 --- /dev/null +++ b/cmake/ports/vhacd/copyright @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2011, Khaled Mamou (kmamou at gmail dot com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/cmake/ports/vhacd/portfile.cmake b/cmake/ports/vhacd/portfile.cmake new file mode 100644 index 0000000000..02d90cab18 --- /dev/null +++ b/cmake/ports/vhacd/portfile.cmake @@ -0,0 +1,31 @@ +include(vcpkg_common_functions) +vcpkg_check_linkage(ONLY_STATIC_LIBRARY) + +# else Linux desktop +vcpkg_download_distfile( + SOURCE_ARCHIVE + URLS https://athena-public.s3.amazonaws.com/dependencies/v-hacd-master.zip + SHA512 5d9bd4872ead9eb3574e4806d6c4f490353a04036fd5f571e1e44f47cb66b709e311abcd53af30bae0015a690152170aeed93209a626c28ebcfd6591f3bb036f + FILENAME vhacd.zip +) + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${SOURCE_ARCHIVE} +) + +vcpkg_configure_cmake( + SOURCE_PATH ${SOURCE_PATH} + PREFER_NINJA +) + +vcpkg_install_cmake() + +file(COPY ${CMAKE_CURRENT_LIST_DIR}/copyright DESTINATION ${CURRENT_PACKAGES_DIR}/share/vhacd) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) +if (WIN32) + file(RENAME ${CURRENT_PACKAGES_DIR}/lib/Release/VHACD_LIB.lib ${CURRENT_PACKAGES_DIR}/lib/VHACD.lib) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/Release) + file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/Debug/VHACD_LIB.lib ${CURRENT_PACKAGES_DIR}/debug/lib/VHACD.lib) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/Debug) +endif() \ No newline at end of file diff --git a/cmake/ports/zlib/CONTROL b/cmake/ports/zlib/CONTROL index c559637834..aa7c7b6e92 100644 --- a/cmake/ports/zlib/CONTROL +++ b/cmake/ports/zlib/CONTROL @@ -1,3 +1,4 @@ Source: zlib -Version: 1.2.11-3 +Version: 1.2.11-5 +Homepage: https://www.zlib.net/ Description: A compression library diff --git a/cmake/ports/zlib/portfile.cmake b/cmake/ports/zlib/portfile.cmake index f2cc68b7a3..78030309b1 100644 --- a/cmake/ports/zlib/portfile.cmake +++ b/cmake/ports/zlib/portfile.cmake @@ -1,16 +1,19 @@ include(vcpkg_common_functions) -set(SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src/zlib-1.2.11) + +set(VERSION 1.2.11) + vcpkg_download_distfile(ARCHIVE_FILE - URLS "http://www.zlib.net/zlib-1.2.11.tar.gz" "https://downloads.sourceforge.net/project/libpng/zlib/1.2.11/zlib-1.2.11.tar.gz" + URLS "http://www.zlib.net/zlib-${VERSION}.tar.gz" "https://downloads.sourceforge.net/project/libpng/zlib/${VERSION}/zlib-${VERSION}.tar.gz" FILENAME "zlib1211.tar.gz" SHA512 73fd3fff4adeccd4894084c15ddac89890cd10ef105dd5e1835e1e9bbb6a49ff229713bd197d203edfa17c2727700fce65a2a235f07568212d820dca88b528ae ) -vcpkg_extract_source_archive(${ARCHIVE_FILE}) -vcpkg_apply_patches( - SOURCE_PATH ${SOURCE_PATH} +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${ARCHIVE_FILE} + REF ${VERSION} PATCHES - ${CMAKE_CURRENT_LIST_DIR}/cmake_dont_build_more_than_needed.patch + "cmake_dont_build_more_than_needed.patch" ) # This is generated during the cmake build @@ -44,4 +47,4 @@ vcpkg_copy_pdbs() file(COPY ${CMAKE_CURRENT_LIST_DIR}/usage DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT}) -#vcpkg_test_cmake(PACKAGE_NAME ZLIB MODULE) +vcpkg_test_cmake(PACKAGE_NAME ZLIB MODULE) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a9bc24c483..9fea49d2da 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -767,6 +767,7 @@ void DomainServer::setupNodeListAndAssignments() { packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket"); + packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacket"); // NodeList won't be available to the settings manager when it is created, so call registerListener here packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); @@ -3614,3 +3615,81 @@ void DomainServer::handleOctreeFileReplacementRequest(QSharedPointerreadAll(), QString(), QString(), username); } } + +void DomainServer::processAvatarZonePresencePacket(QSharedPointer message) { + QUuid avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid zoneID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + if (avatarID.isNull()) { + qCWarning(domain_server) << "Ignoring null avatar presence"; + return; + } + static const int SCREENSHARE_EXPIRATION_SECONDS = 24 * 60 * 60; + screensharePresence(zoneID.isNull() ? "" : zoneID.toString(), avatarID, SCREENSHARE_EXPIRATION_SECONDS); +} + +void DomainServer::screensharePresence(QString roomname, QUuid avatarID, int expirationSeconds) { + if (!DependencyManager::get()->hasValidAccessToken()) { + static std::once_flag presenceAuthorityWarning; + std::call_once(presenceAuthorityWarning, [] { + qCDebug(domain_server) << "No authority to send screensharePresence."; + }); + return; + } + + auto limitedNodeList = DependencyManager::get(); + auto matchingNode = limitedNodeList->nodeWithUUID(avatarID); + if (!matchingNode) { + qCWarning(domain_server) << "Ignoring avatar presence for unknown avatar ID" << avatarID; + return; + } + QString verifiedUsername = matchingNode->getPermissions().getVerifiedUserName(); + if (verifiedUsername.isEmpty()) { // Silently bail for users who are not logged in. + return; + } + + JSONCallbackParameters callbackParams; + callbackParams.callbackReceiver = this; + callbackParams.jsonCallbackMethod = "handleSuccessfulScreensharePresence"; + callbackParams.errorCallbackMethod = "handleFailedScreensharePresence"; + // Construct `callbackData`, which is data that will be available to the callback functions. + // In this case, the "success" callback needs access to the "roomname" (the zone ID) and the + // relevant avatar's UUID. + QJsonObject callbackData; + callbackData.insert("roomname", roomname); + callbackData.insert("avatarID", avatarID.toString()); + callbackParams.callbackData = callbackData; + const QString PATH = "api/v1/domains/%1/screenshare"; + QString domain_id = uuidStringWithoutCurlyBraces(getID()); + QJsonObject json, screenshare; + screenshare["username"] = verifiedUsername; + screenshare["roomname"] = roomname; + if (expirationSeconds > 0) { + screenshare["expiration"] = expirationSeconds; + } + json["screenshare"] = screenshare; + DependencyManager::get()->sendRequest( + PATH.arg(domain_id), + AccountManagerAuth::Required, + QNetworkAccessManager::PostOperation, + callbackParams, QJsonDocument(json).toJson() + ); +} + +void DomainServer::handleSuccessfulScreensharePresence(QNetworkReply* requestReply, QJsonObject callbackData) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); + if (jsonObject["status"].toString() != "success") { + qCWarning(domain_server) << "screensharePresence api call failed:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); + return; + } + + // Tell the client that we just authorized to screenshare which zone ID in which they are authorized to screenshare. + auto nodeList = DependencyManager::get(); + auto packet = NLPacket::create(PacketType::AvatarZonePresence, NUM_BYTES_RFC4122_UUID, true); + packet->write(QUuid(callbackData["roomname"].toString()).toRfc4122()); + nodeList->sendPacket(std::move(packet), *(nodeList->nodeWithUUID(QUuid(callbackData["avatarID"].toString())))); +} + +void DomainServer::handleFailedScreensharePresence(QNetworkReply* requestReply) { + qCWarning(domain_server) << "screensharePresence api call failed:" << requestReply->error(); +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c725688b67..95b4b784cb 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -78,6 +78,8 @@ public: bool isAssetServerEnabled(); + void screensharePresence(QString roomname, QUuid avatarID, int expiration_seconds = 0); + public slots: /// Called by NodeList to inform us a node has been added void nodeAdded(SharedNodePointer node); @@ -96,6 +98,7 @@ private slots: void processNodeDisconnectRequestPacket(QSharedPointer message); void processICEServerHeartbeatDenialPacket(QSharedPointer message); void processICEServerHeartbeatACK(QSharedPointer message); + void processAvatarZonePresencePacket(QSharedPointer packet); void handleDomainContentReplacementFromURLRequest(QSharedPointer message); void handleOctreeFileReplacementRequest(QSharedPointer message); @@ -129,6 +132,9 @@ private slots: void handleSuccessfulICEServerAddressUpdate(QNetworkReply* requestReply); void handleFailedICEServerAddressUpdate(QNetworkReply* requestReply); + void handleSuccessfulScreensharePresence(QNetworkReply* requestReply, QJsonObject callbackData); + void handleFailedScreensharePresence(QNetworkReply* requestReply); + void updateReplicatedNodes(); void updateDownstreamNodes(); void updateUpstreamNodes(); diff --git a/hifi_qt.py b/hifi_qt.py new file mode 100644 index 0000000000..6cae3bf59d --- /dev/null +++ b/hifi_qt.py @@ -0,0 +1,109 @@ +import hifi_utils +import hifi_android +import hashlib +import os +import platform +import re +import shutil +import tempfile +import json +import xml.etree.ElementTree as ET +import functools + +print = functools.partial(print, flush=True) + +# Encapsulates the vcpkg system +class QtDownloader: + CMAKE_TEMPLATE = """ +# this file auto-generated by hifi_qt.py +get_filename_component(QT_CMAKE_PREFIX_PATH "{}" ABSOLUTE CACHE) +get_filename_component(QT_CMAKE_PREFIX_PATH_UNCACHED "{}" ABSOLUTE) + +# If the cached cmake toolchain path is different from the computed one, exit +if(NOT (QT_CMAKE_PREFIX_PATH_UNCACHED STREQUAL QT_CMAKE_PREFIX_PATH)) + message(FATAL_ERROR "QT_CMAKE_PREFIX_PATH has changed, please wipe the build directory and rerun cmake") +endif() +""" + def __init__(self, args): + self.args = args + self.configFilePath = os.path.join(args.build_root, 'qt.cmake') + self.version = os.getenv('VIRCADIA_USE_QT_VERSION', '5.12.3') + + self.assets_url = self.readVar('EXTERNAL_BUILD_ASSETS') + + defaultBasePath = os.path.expanduser('~/hifi/qt') + self.basePath = os.getenv('HIFI_QT_BASE', defaultBasePath) + if (not os.path.isdir(self.basePath)): + os.makedirs(self.basePath) + self.path = os.path.join(self.basePath, self.version) + self.fullPath = os.path.join(self.path, 'qt5-install') + self.cmakePath = os.path.join(self.fullPath, 'lib/cmake') + + print("Using qt path {}".format(self.path)) + lockDir, lockName = os.path.split(self.path) + lockName += '.lock' + if not os.path.isdir(lockDir): + os.makedirs(lockDir) + + self.lockFile = os.path.join(lockDir, lockName) + + if (os.getenv('VIRCADIA_USE_PREBUILT_QT')): + print("Using pre-built Qt5") + return + + # OS dependent information + system = platform.system() + + if 'Windows' == system: + self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-windows3.tar.gz%3FversionId=5ADqP0M0j5ZfimUHrx4zJld6vYceHEsI' + elif 'Darwin' == system: + self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-macos.tar.gz%3FversionId=bLAgnoJ8IMKpqv8NFDcAu8hsyQy3Rwwz' + elif 'Linux' == system: + import distro + dist = distro.linux_distribution() + + if distro.id() == 'ubuntu': + u_major = int( distro.major_version() ) + u_minor = int( distro.minor_version() ) + + if u_major == 16: + self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-16.04-with-symbols.tar.gz' + elif u_major == 18: + self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz' + elif u_major == 19 and u_minor == 10: + self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.6-ubuntu-19.10.tar.xz' + elif u_major > 18 and ( u_major != 19 and u_minor != 4): + print("We don't support " + distro.name(pretty=True) + " yet. Perhaps consider helping us out?") + raise Exception('UNSUPPORTED LINUX VERSION!!!') + else: + print("Sorry, " + distro.name(pretty=True) + " is old and won't be officially supported. Please consider upgrading."); + raise Exception('UNSUPPORTED LINUX VERSION!!!') + else: + print("Sorry, " + distro.name(pretty=True) + " is not supported. Please consider helping us out.") + print("It's also possible to build Qt for your distribution, please see the documentation at:") + print("https://github.com/kasenvr/project-athena/tree/kasen/core/tools/qt-builder") + raise Exception('UNKNOWN LINUX VERSION!!!') + else: + print("System : " + platform.system()) + print("Architecture: " + platform.architecture()) + print("Machine : " + platform.machine()) + raise Exception('UNKNOWN OPERATING SYSTEM!!!') + + def readVar(self, var): + with open(os.path.join(self.args.build_root, '_env', var + ".txt")) as fp: + return fp.read() + + def writeConfig(self): + print("Writing cmake config to {}".format(self.configFilePath)) + # Write out the configuration for use by CMake + cmakeConfig = QtDownloader.CMAKE_TEMPLATE.format(self.cmakePath, self.cmakePath).replace('\\', '/') + with open(self.configFilePath, 'w') as f: + f.write(cmakeConfig) + + def installQt(self): + if not os.path.isdir(self.fullPath): + print ('Downloading Qt from AWS') + print('Extracting ' + self.qtUrl + ' to ' + self.path) + hifi_utils.downloadAndExtract(self.qtUrl, self.path) + else: + print ('Qt has already been downloaded') diff --git a/hifi_utils.py b/hifi_utils.py index d117011c99..3a49f6d52b 100644 --- a/hifi_utils.py +++ b/hifi_utils.py @@ -113,7 +113,7 @@ def downloadFile(url, hash=None, hasher=hashlib.sha512(), retries=3): def downloadAndExtract(url, destPath, hash=None, hasher=hashlib.sha512(), isZip=False): tempFileName = downloadFile(url, hash, hasher) - if isZip: + if isZip or ".zip" in url: with zipfile.ZipFile(tempFileName) as zip: zip.extractall(destPath) else: diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py index e6ffeb8dbb..9846d386c4 100644 --- a/hifi_vcpkg.py +++ b/hifi_vcpkg.py @@ -9,7 +9,6 @@ import tempfile import json import xml.etree.ElementTree as ET import functools -import distro from os import path print = functools.partial(print, flush=True) @@ -22,7 +21,6 @@ get_filename_component(CMAKE_TOOLCHAIN_FILE "{}" ABSOLUTE CACHE) get_filename_component(CMAKE_TOOLCHAIN_FILE_UNCACHED "{}" ABSOLUTE) set(VCPKG_INSTALL_ROOT "{}") set(VCPKG_TOOLS_DIR "{}") -set(VCPKG_QT_CMAKE_PREFIX_PATH "{}") """ CMAKE_TEMPLATE_NON_ANDROID = """ @@ -40,19 +38,24 @@ endif() self.configFilePath = os.path.join(args.build_root, 'vcpkg.cmake') self.assets_url = self.readVar('EXTERNAL_BUILD_ASSETS') + # The noClean flag indicates we're doing weird dependency maintenance stuff + # i.e. we've got an explicit checkout of vcpkg and we don't want the script to + # do stuff it might otherwise do. It typically indicates that we're using our + # own git checkout of vcpkg and manually managing it + self.noClean = False + # OS dependent information system = platform.system() - if self.args.vcpkg_root is not None: + if 'HIFI_VCPKG_PATH' in os.environ: + self.path = os.environ['HIFI_VCPKG_PATH'] + self.noClean = True + elif self.args.vcpkg_root is not None: self.path = args.vcpkg_root + self.noClean = True else: - if 'Darwin' == system: - defaultBasePath = os.path.expanduser('~/hifi/vcpkg') - else: - defaultBasePath = os.path.join(tempfile.gettempdir(), 'hifi', 'vcpkg') + defaultBasePath = os.path.expanduser('~/hifi/vcpkg') self.basePath = os.getenv('HIFI_VCPKG_BASE', defaultBasePath) - if self.basePath == defaultBasePath: - print("Warning: Environment variable HIFI_VCPKG_BASE not set, using {}".format(defaultBasePath)) if self.args.android: self.basePath = os.path.join(self.basePath, 'android') if (not os.path.isdir(self.basePath)): @@ -67,27 +70,36 @@ endif() self.lockFile = os.path.join(lockDir, lockName) self.tagFile = os.path.join(self.path, '.id') + self.prebuildTagFile = os.path.join(self.path, '.prebuild') # A format version attached to the tag file... increment when you want to force the build systems to rebuild # without the contents of the ports changing self.version = 1 self.tagContents = "{}_{}".format(self.id, self.version) + self.bootstrapEnv = os.environ.copy() + self.buildEnv = os.environ.copy() + self.prebuiltArchive = None + usePrebuilt = ('CI_BUILD' in os.environ) and os.environ["CI_BUILD"] == "Github" and (not self.noClean) if 'Windows' == system: self.exe = os.path.join(self.path, 'vcpkg.exe') - self.bootstrapCmd = 'bootstrap-vcpkg.bat' - self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-win32.tar.gz%3FversionId=YZYkDejDRk7L_hrK_WVFthWvisAhbDzZ' - self.vcpkgHash = '3e0ff829a74956491d57666109b3e6b5ce4ed0735c24093884317102387b2cb1b2cd1ff38af9ed9173501f6e32ffa05cc6fe6d470b77a71ca1ffc3e0aa46ab9e' + self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat') ] + self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-win32-client.zip%3FversionId=tSFzbw01VkkVFeRQ6YuAY4dro2HxJR9U' + self.vcpkgHash = 'a650db47a63ccdc9904b68ddd16af74772e7e78170b513ea8de5a3b47d032751a3b73dcc7526d88bcb500753ea3dd9880639ca842bb176e2bddb1710f9a58cd3' self.hostTriplet = 'x64-windows' + if usePrebuilt: + self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx" elif 'Darwin' == system: self.exe = os.path.join(self.path, 'vcpkg') - self.bootstrapCmd = 'bootstrap-vcpkg.sh' - self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-osx.tar.gz%3FversionId=_fhqSxjfrtDJBvEsQ8L_ODcdUjlpX9cc' + self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.sh'), '--allowAppleClang' ] + self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-osx-client.tgz%3FversionId=j0b4azo_zTlH_Q9DElEWOz1UMYZ2nqQw' self.vcpkgHash = '519d666d02ef22b87c793f016ca412e70f92e1d55953c8f9bd4ee40f6d9f78c1df01a6ee293907718f3bbf24075cc35492fb216326dfc50712a95858e9cbcb4d' self.hostTriplet = 'x64-osx' + if usePrebuilt: + self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-osx.tgz%3FversionId=6JrIMTdvpBF3MAsjA92BMkO79Psjzs6Z" else: self.exe = os.path.join(self.path, 'vcpkg') - self.bootstrapCmd = 'bootstrap-vcpkg.sh' - self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-linux.tar.gz%3FversionId=97Nazh24etEVKWz33XwgLY0bvxEfZgMU' + self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.sh') ] + self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-linux-client.tgz%3FversionId=y7mct0gFicEXz5hJy3KROBugcLR56YWf' self.vcpkgHash = '6a1ce47ef6621e699a4627e8821ad32528c82fce62a6939d35b205da2d299aaa405b5f392df4a9e5343dd6a296516e341105fbb2dd8b48864781d129d7fba10d' self.hostTriplet = 'x64-linux' @@ -101,9 +113,13 @@ endif() with open(os.path.join(self.args.build_root, '_env', var + ".txt")) as fp: return fp.read() + def writeVar(self, var, value): + with open(os.path.join(self.args.build_root, '_env', var + ".txt"), 'w') as fp: + fp.write(value) + def upToDate(self): # Prevent doing a clean if we've explcitly set a directory for vcpkg - if self.args.vcpkg_root is not None: + if self.noClean: return True if self.args.force_build: @@ -145,8 +161,10 @@ endif() self.copyEnv() return - self.clean() + if self.prebuiltArchive is not None: + return + self.clean() downloadVcpkg = False if self.args.force_bootstrap: print("Forcing bootstrap") @@ -167,10 +185,10 @@ endif() print("Cloning vcpkg from github to {}".format(self.path)) hifi_utils.executeSubprocess(['git', 'clone', 'https://github.com/microsoft/vcpkg', self.path]) print("Bootstrapping vcpkg") - hifi_utils.executeSubprocess([self.bootstrapCmd], folder=self.path) + hifi_utils.executeSubprocess(self.bootstrapCmds, folder=self.path, env=self.bootstrapEnv) else: print("Fetching vcpkg from {} to {}".format(self.vcpkgUrl, self.path)) - hifi_utils.downloadAndExtract(self.vcpkgUrl, self.path, self.vcpkgHash) + hifi_utils.downloadAndExtract(self.vcpkgUrl, self.path) print("Replacing port files") portsPath = os.path.join(self.path, 'ports') @@ -186,9 +204,19 @@ endif() actualCommands.extend(commands) print("Running command") print(actualCommands) - hifi_utils.executeSubprocess(actualCommands, folder=self.path) + hifi_utils.executeSubprocess(actualCommands, folder=self.path, env=self.buildEnv) + + def setupDependencies(self, qt=None): + if self.prebuiltArchive: + if not os.path.isfile(self.prebuildTagFile): + print('Extracting ' + self.prebuiltArchive + ' to ' + self.path) + hifi_utils.downloadAndExtract(self.prebuiltArchive, self.path) + self.writePrebuildTag() + return + + if qt is not None: + self.buildEnv['QT_CMAKE_PREFIX_PATH'] = qt - def setupDependencies(self): # Special case for android, grab a bunch of binaries # FIXME remove special casing for android builds eventually if self.args.android: @@ -202,13 +230,10 @@ endif() if not self.args.android: print("Installing build dependencies") self.run(['install', '--triplet', self.triplet, 'hifi-client-deps']) - - # If not android, install our Qt build - if not self.args.android: - print("Installing Qt") - self.installQt() def cleanBuilds(self): + if self.noClean: + return # Remove temporary build artifacts builddir = os.path.join(self.path, 'buildtrees') if os.path.isdir(builddir): @@ -238,16 +263,18 @@ endif() hifi_utils.downloadAndExtract(url, dest, isZip=zipFile, hash=package['checksum'], hasher=hashlib.md5()) def writeTag(self): + if self.noClean: + return print("Writing tag {} to {}".format(self.tagContents, self.tagFile)) + if not os.path.isdir(self.path): + os.makedirs(self.path) with open(self.tagFile, 'w') as f: f.write(self.tagContents) - def getQt5InstallPath(self): - qt5InstallPath = os.path.join(self.path, 'installed', 'qt5-install') - if self.args.android: - precompiled = os.path.realpath(self.androidPackagePath) - qt5InstallPath = os.path.realpath(os.path.join(precompiled, 'qt')) - return qt5InstallPath + def writePrebuildTag(self): + print("Writing tag {} to {}".format(self.tagContents, self.tagFile)) + with open(self.prebuildTagFile, 'w') as f: + f.write(self.tagContents) def writeConfig(self): print("Writing cmake config to {}".format(self.configFilePath)) @@ -262,9 +289,7 @@ endif() cmakeTemplate += 'set(HIFI_ANDROID_PRECOMPILED "{}")\n'.format(precompiled) else: cmakeTemplate += VcpkgRepo.CMAKE_TEMPLATE_NON_ANDROID - - qtCmakePrefixPath = os.path.join(self.getQt5InstallPath(), "lib/cmake") - cmakeConfig = cmakeTemplate.format(cmakeScript, cmakeScript, installPath, toolsPath, qtCmakePrefixPath).replace('\\', '/') + cmakeConfig = cmakeTemplate.format(cmakeScript, cmakeScript, installPath, toolsPath).replace('\\', '/') with open(self.configFilePath, 'w') as f: f.write(cmakeConfig) @@ -273,49 +298,3 @@ endif() # update the tag file on every run, we can scan the base dir for sub directories containing # a tag file that is older than N days, and if found, delete the directory, recovering space print("Not implemented") - - - def installQt(self): - qt5InstallPath = self.getQt5InstallPath() - if not os.path.isdir(qt5InstallPath): - print ('Downloading Qt from AWS') - dest, tail = os.path.split(qt5InstallPath) - - url = 'NOT DEFINED' - if platform.system() == 'Windows': - url = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-windows3.tar.gz' - elif platform.system() == 'Darwin': - url = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-macos.tar.gz%3FversionId=bLAgnoJ8IMKpqv8NFDcAu8hsyQy3Rwwz' - elif platform.system() == 'Linux': - dist = distro.linux_distribution() - - if distro.id() == 'ubuntu': - u_major = int( distro.major_version() ) - u_minor = int( distro.minor_version() ) - - if u_major == 16: - url = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-16.04-with-symbols.tar.gz' - elif u_major == 18: - url = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz' - elif u_major == 19 and u_minor == 10: - url = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.6-ubuntu-19.10.tar.xz' - elif u_major > 18 and ( u_major != 19 and u_minor != 4): - print("We don't support " + distro.name(pretty=True) + " yet. Perhaps consider helping us out?") - else: - print("Sorry, " + distro.name(pretty=True) + " is old and won't be officially supported. Please consider upgrading."); - else: - print("Sorry, " + distro.name(pretty=True) + " is not supported. Please consider helping us out.") - print("It's also possible to build Qt for your distribution, please see the documentation at:") - print("https://github.com/kasenvr/project-athena/tree/kasen/core/tools/qt-builder") - return; - else: - print('UNKNOWN OPERATING SYSTEM!!!') - print("System : " + platform.system()) - print("Architecture: " + platform.architecture()) - print("Machine : " + platform.machine()) - return; - - print('Extracting ' + url + ' to ' + dest) - hifi_utils.downloadAndExtract(url, dest) - else: - print ('Qt has already been downloaded') diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 062a5538c7..dc9e5254df 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -190,7 +190,10 @@ if (BUILD_TOOLS AND NPM_EXECUTABLE) add_dependencies(resources jsdoc) endif() -add_dependencies(${TARGET_NAME} resources) +if (WIN32 OR APPLE) + add_dependencies(${TARGET_NAME} resources screenshare) +endif() + if (WIN32) # These are external plugins, but we need to do the 'add dependency' here so that their @@ -328,6 +331,10 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "${RESOURCES_DEV_DIR}/scripts" + # copy screenshare app to the resource folder + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_BINARY_DIR}/../screenshare/hifi-screenshare-darwin-x64/hifi-screenshare.app" + "${RESOURCES_DEV_DIR}/hifi-screenshare.app" # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" @@ -352,7 +359,7 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" "${RESOURCES_DEV_DIR}/serverless/redirect.json" - ) + ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() diff --git a/interface/resources/avatar/animations/afk_texting.fbx b/interface/resources/avatar/animations/afk_texting.fbx new file mode 100644 index 0000000000..b4ac55c01c Binary files /dev/null and b/interface/resources/avatar/animations/afk_texting.fbx differ diff --git a/interface/resources/avatar/animations/settle_sitturnleft_to_sitidle.fbx b/interface/resources/avatar/animations/settle_sitturnleft_to_sitidle.fbx new file mode 100644 index 0000000000..8e6c3fc007 Binary files /dev/null and b/interface/resources/avatar/animations/settle_sitturnleft_to_sitidle.fbx differ diff --git a/interface/resources/avatar/animations/settle_sitturnright_to_sitidle.fbx b/interface/resources/avatar/animations/settle_sitturnright_to_sitidle.fbx new file mode 100644 index 0000000000..e997a913da Binary files /dev/null and b/interface/resources/avatar/animations/settle_sitturnright_to_sitidle.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle.fbx b/interface/resources/avatar/animations/settle_to_idle.fbx new file mode 100644 index 0000000000..7b801d2c54 Binary files /dev/null and b/interface/resources/avatar/animations/settle_to_idle.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle02.fbx b/interface/resources/avatar/animations/settle_to_idle02.fbx new file mode 100644 index 0000000000..be7429091d Binary files /dev/null and b/interface/resources/avatar/animations/settle_to_idle02.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle03.fbx b/interface/resources/avatar/animations/settle_to_idle03.fbx new file mode 100644 index 0000000000..e7b48309bb Binary files /dev/null and b/interface/resources/avatar/animations/settle_to_idle03.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle04.fbx b/interface/resources/avatar/animations/settle_to_idle04.fbx new file mode 100644 index 0000000000..cc31a240cb Binary files /dev/null and b/interface/resources/avatar/animations/settle_to_idle04.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle_small.fbx b/interface/resources/avatar/animations/settle_to_idle_small.fbx index 9161a95d95..21e2ee8478 100644 Binary files a/interface/resources/avatar/animations/settle_to_idle_small.fbx and b/interface/resources/avatar/animations/settle_to_idle_small.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle04.fbx b/interface/resources/avatar/animations/sitting_idle04.fbx index 7012481b2a..7967627daf 100644 Binary files a/interface/resources/avatar/animations/sitting_idle04.fbx and b/interface/resources/avatar/animations/sitting_idle04.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle05.fbx b/interface/resources/avatar/animations/sitting_idle05.fbx index 1b89d26f15..bef1e3f73b 100644 Binary files a/interface/resources/avatar/animations/sitting_idle05.fbx and b/interface/resources/avatar/animations/sitting_idle05.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle_once_leanforward.fbx b/interface/resources/avatar/animations/sitting_idle_once_leanforward.fbx index 75a4603335..66be22f4c4 100644 Binary files a/interface/resources/avatar/animations/sitting_idle_once_leanforward.fbx and b/interface/resources/avatar/animations/sitting_idle_once_leanforward.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle_once_lookfidget.fbx b/interface/resources/avatar/animations/sitting_idle_once_lookfidget.fbx index b3ab378c26..ce0f4861cc 100644 Binary files a/interface/resources/avatar/animations/sitting_idle_once_lookfidget.fbx and b/interface/resources/avatar/animations/sitting_idle_once_lookfidget.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle_once_shakelegs.fbx b/interface/resources/avatar/animations/sitting_idle_once_shakelegs.fbx index a020e20044..2a6db1cf3f 100644 Binary files a/interface/resources/avatar/animations/sitting_idle_once_shakelegs.fbx and b/interface/resources/avatar/animations/sitting_idle_once_shakelegs.fbx differ diff --git a/interface/resources/avatar/animations/sitting_idle_once_shiftweight.fbx b/interface/resources/avatar/animations/sitting_idle_once_shiftweight.fbx index 90d2bd220b..9016403b96 100644 Binary files a/interface/resources/avatar/animations/sitting_idle_once_shiftweight.fbx and b/interface/resources/avatar/animations/sitting_idle_once_shiftweight.fbx differ diff --git a/interface/resources/avatar/animations/sitting_turn_left.fbx b/interface/resources/avatar/animations/sitting_turn_left.fbx new file mode 100644 index 0000000000..e0863327f4 Binary files /dev/null and b/interface/resources/avatar/animations/sitting_turn_left.fbx differ diff --git a/interface/resources/avatar/animations/sitting_turn_right.fbx b/interface/resources/avatar/animations/sitting_turn_right.fbx new file mode 100644 index 0000000000..54b1d9608e Binary files /dev/null and b/interface/resources/avatar/animations/sitting_turn_right.fbx differ diff --git a/interface/resources/avatar/avatar-animation-optimized-ik.json b/interface/resources/avatar/avatar-animation-optimized-ik.json new file mode 100644 index 0000000000..2aad89228a --- /dev/null +++ b/interface/resources/avatar/avatar-animation-optimized-ik.json @@ -0,0 +1,6864 @@ +{ + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + }, + "id": "defaultPose", + "type": "defaultPose" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 0, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx" + }, + "id": "rightHandGraspOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 0, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx" + }, + "id": "rightHandGraspClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "rightHandGraspAlpha" + }, + "id": "rightHandGrasp", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_point_open_right.fbx" + }, + "id": "rightIndexPointOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx" + }, + "id": "rightIndexPointClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "rightHandGraspAlpha" + }, + "id": "rightIndexPoint", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx" + }, + "id": "rightThumbRaiseOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx" + }, + "id": "rightThumbRaiseClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "rightHandGraspAlpha" + }, + "id": "rightThumbRaise", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx" + }, + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx" + }, + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "rightHandGraspAlpha" + }, + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear" + } + ], + "data": { + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpDuration": 3, + "interpTarget": 3, + "transitions": [ + { + "state": "rightIndexPoint", + "var": "isRightIndexPoint" + }, + { + "state": "rightThumbRaise", + "var": "isRightThumbRaise" + }, + { + "state": "rightIndexPointAndThumbRaise", + "var": "isRightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPoint", + "interpDuration": 3, + "interpTarget": 15, + "transitions": [ + { + "state": "rightHandGrasp", + "var": "isRightHandGrasp" + }, + { + "state": "rightThumbRaise", + "var": "isRightThumbRaise" + }, + { + "state": "rightIndexPointAndThumbRaise", + "var": "isRightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightThumbRaise", + "interpDuration": 3, + "interpTarget": 15, + "transitions": [ + { + "state": "rightHandGrasp", + "var": "isRightHandGrasp" + }, + { + "state": "rightIndexPoint", + "var": "isRightIndexPoint" + }, + { + "state": "rightIndexPointAndThumbRaise", + "var": "isRightIndexPointAndThumbRaise" + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpDuration": 3, + "interpTarget": 15, + "transitions": [ + { + "state": "rightHandGrasp", + "var": "isRightHandGrasp" + }, + { + "state": "rightIndexPoint", + "var": "isRightIndexPoint" + }, + { + "state": "rightThumbRaise", + "var": "isRightThumbRaise" + } + ] + } + ] + }, + "id": "rightHandStateMachine", + "type": "stateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 0, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx" + }, + "id": "leftHandGraspOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 10, + "loopFlag": true, + "startFrame": 10, + "timeScale": 1, + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx" + }, + "id": "leftHandGraspClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "leftHandGraspAlpha" + }, + "id": "leftHandGrasp", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_point_open_left.fbx" + }, + "id": "leftIndexPointOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx" + }, + "id": "leftIndexPointClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "leftHandGraspAlpha" + }, + "id": "leftIndexPoint", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx" + }, + "id": "leftThumbRaiseOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx" + }, + "id": "leftThumbRaiseClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "leftHandGraspAlpha" + }, + "id": "leftThumbRaise", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx" + }, + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": true, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx" + }, + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "leftHandGraspAlpha" + }, + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear" + } + ], + "data": { + "currentState": "leftHandGrasp", + "states": [ + { + "id": "leftHandGrasp", + "interpDuration": 3, + "interpTarget": 3, + "transitions": [ + { + "state": "leftIndexPoint", + "var": "isLeftIndexPoint" + }, + { + "state": "leftThumbRaise", + "var": "isLeftThumbRaise" + }, + { + "state": "leftIndexPointAndThumbRaise", + "var": "isLeftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPoint", + "interpDuration": 3, + "interpTarget": 15, + "transitions": [ + { + "state": "leftHandGrasp", + "var": "isLeftHandGrasp" + }, + { + "state": "leftThumbRaise", + "var": "isLeftThumbRaise" + }, + { + "state": "leftIndexPointAndThumbRaise", + "var": "isLeftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftThumbRaise", + "interpDuration": 3, + "interpTarget": 15, + "transitions": [ + { + "state": "leftHandGrasp", + "var": "isLeftHandGrasp" + }, + { + "state": "leftIndexPoint", + "var": "isLeftIndexPoint" + }, + { + "state": "leftIndexPointAndThumbRaise", + "var": "isLeftIndexPointAndThumbRaise" + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpDuration": 3, + "interpTarget": 15, + "transitions": [ + { + "state": "leftHandGrasp", + "var": "isLeftHandGrasp" + }, + { + "state": "leftIndexPoint", + "var": "isLeftIndexPoint" + }, + { + "state": "leftThumbRaise", + "var": "isLeftThumbRaise" + } + ] + } + ] + }, + "id": "leftHandStateMachine", + "type": "stateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 271, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_talk02.fbx" + }, + "id": "seatedTalk02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 252, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_talk03.fbx" + }, + "id": "seatedTalk03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 442, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_talk04.fbx" + }, + "id": "seatedTalk04", + "type": "clip" + } + ], + "data": { + "currentState": "seatedTalk02", + "randomSwitchTimeMax": 12, + "randomSwitchTimeMin": 7, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedTalk02", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedTalk03", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedTalk04", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + } + ], + "triggerRandomSwitch": "seatedTalkSwitch" + }, + "id": "seatedTalk", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle.fbx" + }, + "id": "seatedIdle01", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle02.fbx" + }, + "id": "seatedIdle02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle03.fbx" + }, + "id": "seatedIdle03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle04.fbx" + }, + "id": "seatedIdle04", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 332, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle05.fbx" + }, + "id": "seatedIdle05", + "type": "clip" + } + ], + "data": { + "currentState": "seatedIdle01", + "endFrame": 30, + "randomSwitchTimeMax": 40, + "randomSwitchTimeMin": 10, + "startFrame": 10, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedIdle01", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle02", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle03", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle04", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle05", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + } + ], + "timeScale": 1, + "triggerRandomSwitch": "seatedIdleSwitch", + "triggerTimeMax": 10 + }, + "id": "masterSeatedIdle", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 744, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_shifting.fbx" + }, + "id": "seatedFidgetShifting", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 420, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_lookfidget.fbx" + }, + "id": "seatedFidgetLookFidget", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 282, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_shiftweight.fbx" + }, + "id": "seatedFidgetShiftWeight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 428, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_fidget.fbx" + }, + "id": "seatedFidgeting", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 324, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_lookaround.fbx" + }, + "id": "seatedFidgetLookAround", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 120, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_lookleftright.fbx" + }, + "id": "seatedFidgetLookLeftRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 178, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_leanforward.fbx" + }, + "id": "seatedFidgetLeanForward", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 140, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_once_shakelegs.fbx" + }, + "id": "seatedFidgetShakeLegs", + "type": "clip" + } + ], + "data": { + "currentState": "seatedFidgetShifting", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetShifting", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLookFidget", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetShiftWeight", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgeting", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLookAround", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLookLeftRight", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetLeanForward", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidgetShakeLegs", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "seatedFidget", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "currentState": "masterSeatedIdle", + "randomSwitchTimeMax": 20, + "randomSwitchTimeMin": 10, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "masterSeatedIdle", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedFidget", + "var": "timeToSeatedFidget" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedFidget", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetShiftingOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLookFidgetOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetShiftWeightOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetingOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLookAroundOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLookLeftRightOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetLeanForwardOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "seatedFidgetShakeLegsOnDone" + } + ] + } + ], + "transitionVar": "timeToSeatedFidget", + "triggerRandomSwitch": "", + "triggerTimeMax": 45, + "triggerTimeMin": 10 + }, + "id": "seatedIdle", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "alpha": 1, + "alphaVar": "talkOverlayAlpha", + "boneSet": "upperBody" + }, + "id": "seatedTalkOverlay", + "type": "overlay" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 44, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_headnod.fbx" + }, + "id": "seatedReactionPositiveHeadNod", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 78, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_headnodyes.fbx" + }, + "id": "seatedReactionPositiveHeadNodYes", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 65, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_longheadnod.fbx" + }, + "id": "seatedReactionPositiveLongHeadNod", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 78, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_cheer.fbx" + }, + "id": "seatedReactionPositiveCheer", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 64, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_agree_acknowledge.fbx" + }, + "id": "seatedReactionPositiveAcknowledge", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionPositiveHeadNod", + "endFrame": 30, + "loopFlag": false, + "randomSwitchTimeMax": 12, + "randomSwitchTimeMin": 7, + "startFrame": 0, + "states": [ + { + "id": "seatedReactionPositiveHeadNod", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "id": "seatedReactionPositiveHeadNodYes", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "id": "seatedReactionPositiveLongHeadNod", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "id": "seatedReactionPositiveCheer", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "id": "seatedReactionPositiveAcknowledge", + "interpDuration": 1, + "interpTarget": 1, + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ], + "timeScale": 1, + "triggerRandomSwitch": "", + "url": "qrc:///avatar/animations/sitting_idle.fbx" + }, + "id": "seatedReactionPositive", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 64, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_headshake.fbx" + }, + "id": "seatedReactionNegativeDisagreeHeadshake", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 99, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_drophead.fbx" + }, + "id": "seatedReactionNegativeDisagreeDropHead", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 124, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_disbelief.fbx" + }, + "id": "seatedReactionNegativeDisagreeDisbelief", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 70, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_disagree_dismiss.fbx" + }, + "id": "seatedReactionNegativeDisagreeDismiss", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionNegativeDisagreeHeadshake", + "endFrame": 30, + "loopFlag": false, + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "startFrame": 0, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeHeadshake", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeDropHead", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeDisbelief", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegativeDisagreeDismiss", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ], + "timeScale": 1, + "triggerRandomSwitch": "", + "url": "qrc:///avatar/animations/sitting_idle.fbx" + }, + "id": "seatedReactionNegative", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 32, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" + }, + "id": "seatedReactionRaiseHandIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 345, + "loopFlag": true, + "startFrame": 32, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" + }, + "id": "seatedReactionRaiseHandLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 400, + "loopFlag": false, + "startFrame": 345, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand_all.fbx" + }, + "id": "seatedReactionRaiseHandOutro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 18, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" + }, + "id": "seatedReactionRaiseHand02Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 378, + "loopFlag": true, + "startFrame": 18, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" + }, + "id": "seatedReactionRaiseHand02Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 435, + "loopFlag": false, + "startFrame": 378, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand02_all.fbx" + }, + "id": "seatedReactionRaiseHand02Outro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" + }, + "id": "seatedReactionRaiseHand03Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 233, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 15, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" + }, + "id": "seatedReactionRaiseHand03Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 296, + "loopFlag": false, + "mirrorFlag": false, + "startFrame": 233, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_raisehand03_all.fbx" + }, + "id": "seatedReactionRaiseHand03Outro", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionRaiseHandIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHandIntro", + "interpDuration": 8, + "interpTarget": 9, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHandLoop", + "var": "seatedReactionRaiseHandIntroOnDone" + } + ] + }, + { + "id": "seatedReactionRaiseHandLoop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHandOutro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHandOutro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHandLoop", + "var": "reactionRaiseHandEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand02Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand02Loop", + "var": "seatedReactionRaiseHand02IntroOnDone" + } + ] + }, + { + "id": "seatedReactionRaiseHand02Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand02Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand02Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand02Loop", + "var": "reactionRaiseHandEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand03Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand03Loop", + "var": "seatedReactionRaiseHand03IntroOnDone" + } + ] + }, + { + "id": "seatedReactionRaiseHand03Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand03Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand03Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionRaiseHand03Loop", + "var": "reactionRaiseHandEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "seatedReactionRaiseHand", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 12, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" + }, + "id": "seatedReactionApplaudIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 76, + "loopFlag": true, + "startFrame": 12, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" + }, + "id": "seatedReactionApplaudLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 99, + "loopFlag": false, + "startFrame": 76, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap_all.fbx" + }, + "id": "seatedReactionApplaudOutro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 12, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" + }, + "id": "seatedReactionApplaud02Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 112, + "loopFlag": true, + "startFrame": 12, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" + }, + "id": "seatedReactionApplaud02Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 132, + "loopFlag": false, + "startFrame": 112, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap02_all.fbx" + }, + "id": "seatedReactionApplaud02Outro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 17, + "loopFlag": false, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" + }, + "id": "seatedReactionApplaud03Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 111, + "loopFlag": true, + "startFrame": 17, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" + }, + "id": "seatedReactionApplaud03Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 136, + "loopFlag": false, + "startFrame": 111, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_clap03_all.fbx" + }, + "id": "seatedReactionApplaud03Outro", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionApplaudIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaudIntro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaudLoop", + "var": "seatedReactionApplaudIntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaudLoop", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaudOutro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaudOutro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaudLoop", + "var": "reactionApplaudEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud02Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud02Loop", + "var": "seatedReactionApplaud02IntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud02Loop", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud02Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud02Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud02Loop", + "var": "reactionApplaudEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud03Intro", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud03Loop", + "var": "seatedReactionApplaud03IntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud03Loop", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud03Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud03Outro", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionApplaud03Loop", + "var": "reactionApplaudEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "seatedReactionApplaud", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 21, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + }, + "id": "seatedReactionPointIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 100, + "loopFlag": true, + "startFrame": 21, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + }, + "id": "seatedReactionPointLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 134, + "loopFlag": false, + "mirrorFlag": false, + "startFrame": 100, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_all.fbx" + }, + "id": "seatedReactionPointOutro", + "type": "clip" + } + ], + "data": { + "currentState": "seatedReactionPointIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPointIntro", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionPointLoop", + "var": "seatedReactionPointIntroOnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPointLoop", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionPointOutro", + "var": "reactionPointDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPointOutro", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "seatedReactionPointLoop", + "var": "reactionPointEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "seatedReactionPoint", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "alpha": 0, + "alphaVar": "seatedPointBlendAlpha", + "blendType": "addAbsolute" + }, + "id": "seatedReactionPointBase", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 11, + "loopFlag": true, + "startFrame": 11, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 30, + "loopFlag": true, + "startFrame": 30, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 50, + "loopFlag": true, + "startFrame": 50, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointUp", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 70, + "loopFlag": true, + "startFrame": 70, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointDown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 90, + "loopFlag": true, + "startFrame": 90, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointUpLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 110, + "loopFlag": true, + "startFrame": 110, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointUpRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 130, + "loopFlag": true, + "startFrame": 130, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointDownLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 150, + "loopFlag": true, + "startFrame": 150, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointDownRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 3, + "loopFlag": true, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_emote_point_aimoffsets.fbx" + }, + "id": "seatedPointCenter", + "type": "clip" + } + ], + "data": { + "alpha": [ + 0, + 0, + 0 + ], + "alphaVar": "pointAroundAlpha", + "centerId": "seatedPointCenter", + "downId": "seatedPointDown", + "downLeftId": "seatedPointDownLeft", + "downRightId": "seatedPointDownRight", + "leftId": "seatedPointLeft", + "rightId": "seatedPointRight", + "upId": "seatedPointUp", + "upLeftId": "seatedPointUpLeft", + "upRightId": "seatedPointUpRight" + }, + "id": "seatedPointAround", + "type": "blendDirectional" + } + ], + "data": { + "alpha": 0, + "alphaVar": "pointBlendAlpha", + "blendType": "addAbsolute" + }, + "id": "seatedReactionPoint", + "type": "blendLinear" + } + ], + "data": { + "currentState": "seatedTalkOverlay", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedTalkOverlay", + "interpDuration": 25, + "interpTarget": 25, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPositive", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveHeadNodOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveHeadNodYesOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveLongHeadNodOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveCheerOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionPositiveAcknowledgeOnDone" + }, + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionNegative", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeHeadshakeOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeDropHeadOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeDisbeliefOnDone" + }, + { + "state": "seatedTalkOverlay", + "var": "seatedReactionNegativeDisagreeDismissOnDone" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionRaiseHand", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedTalkOverlay", + "var": "reactionRaiseHandDisabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionApplaud", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedTalkOverlay", + "var": "reactionApplaudDisabled" + }, + { + "state": "seatedReactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedReactionPoint", + "interpDuration": 12, + "interpTarget": 12, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "seatedReactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "seatedReactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "seatedReactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "seatedReactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "seatedTalkOverlay", + "var": "reactionPointDisabled" + } + ] + } + ] + }, + "id": "seatedSM", + "type": "stateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 11, + "loopFlag": true, + "startFrame": 11, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 30, + "loopFlag": true, + "startFrame": 30, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 50, + "loopFlag": true, + "startFrame": 50, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookUp", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 70, + "loopFlag": true, + "startFrame": 70, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookDown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 97, + "loopFlag": true, + "startFrame": 97, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookUpLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 110, + "loopFlag": true, + "startFrame": 110, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookUpRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 130, + "loopFlag": true, + "startFrame": 130, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookDownLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 150, + "loopFlag": true, + "startFrame": 150, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookDownRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 3, + "loopFlag": true, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx" + }, + "id": "seatedLookCenter", + "type": "clip" + } + ], + "data": { + "alpha": [ + 0, + 0, + 0 + ], + "alphaVar": "lookAroundAlpha", + "centerId": "seatedLookCenter", + "downId": "seatedLookDown", + "downLeftId": "seatedLookDownLeft", + "downRightId": "seatedLookDownRight", + "leftId": "seatedLookLeft", + "rightId": "seatedLookRight", + "upId": "seatedLookUp", + "upLeftId": "seatedLookUpLeft", + "upRightId": "seatedLookUpRight" + }, + "id": "seatedLookAroundBlend", + "type": "blendDirectional" + } + ], + "data": { + "alpha": 0, + "alphaVar": "seatedLookBlendAlpha", + "blendType": "addAbsolute" + }, + "id": "seated", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 500, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk.fbx" + }, + "id": "talk", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 325, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk02.fbx" + }, + "id": "talk02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 300, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk03.fbx" + }, + "id": "talk03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 500, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk04.fbx" + }, + "id": "talk04", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 215, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk_armsdown.fbx" + }, + "id": "talk_armsdown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 500, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk_lefthand.fbx" + }, + "id": "talk_lefthand", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 502, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/talk_righthand.fbx" + }, + "id": "talk_righthand", + "type": "clip" + } + ], + "data": { + "currentState": "talk", + "randomSwitchTimeMax": 12, + "randomSwitchTimeMin": 7, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "talk", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "talk02", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "talk03", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "talk04", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "talk_armsdown", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "talk_lefthand", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "talk_righthand", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": true, + "transitions": [ + ] + } + ], + "triggerRandomSwitch": "idleTalkSwitch" + }, + "id": "idleTalk", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 300, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle.fbx" + }, + "id": "masterIdle1", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 400, + "loopFlag": true, + "startFrame": 1, + "timeScale": 0.75, + "url": "qrc:///avatar/animations/idle02.fbx" + }, + "id": "masterIdle2", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle03.fbx" + }, + "id": "masterIdle3", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 902, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle04.fbx" + }, + "id": "masterIdle4", + "type": "clip" + } + ], + "data": { + "currentState": "masterIdle1", + "randomSwitchTimeMax": 30, + "randomSwitchTimeMin": 10, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "masterIdle1", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 0.25, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "masterIdle2", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 0.25, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "masterIdle3", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 0.25, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "masterIdle4", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 0.25, + "resume": true, + "transitions": [ + ] + } + ], + "triggerRandomSwitch": "masterIdleSwitch" + }, + "id": "masterIdle", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 91, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_once_slownod.fbx" + }, + "id": "idle_once_slownod", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 154, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_once_headtilt.fbx" + }, + "id": "idle_once_headtilt", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 491, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_once_shiftheelpivot.fbx" + }, + "id": "idle_once_shiftheelpivot", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 1620, + "loopFlag": false, + "startFrame": 1, + "timeScale": 0.7, + "url": "qrc:///avatar/animations/idleWS_all.fbx" + }, + "id": "idleWS_all", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 324, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_once_lookaround.fbx" + }, + "id": "idle_once_lookaround", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 169, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_once_neckstretch.fbx" + }, + "id": "idle_once_neckstretch", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 375, + "loopFlag": false, + "startFrame": 1, + "timeScale": 0.7, + "url": "qrc:///avatar/animations/idle_once_lookleftright.fbx" + }, + "id": "idle_once_lookleftright", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 429, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_once_fidget.fbx" + }, + "id": "idle_once_fidget", + "type": "clip" + } + ], + "data": { + "currentState": "idle_once_slownod", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "idle_once_slownod", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle_once_headtilt", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle_once_shiftheelpivot", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleWS_all", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle_once_lookaround", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle_once_neckstretch", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle_once_lookleftright", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle_once_fidget", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 0.2, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "movement", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 80, + "loopFlag": false, + "startFrame": 1, + "startFrameVar": "", + "timeScale": 0.65, + "url": "qrc:///avatar/animations/idle_LFF_all.fbx" + }, + "id": "transitionToAltIdle1", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 80, + "loopFlag": false, + "startFrame": 1, + "timeScale": 0.65, + "url": "qrc:///avatar/animations/idle_RFF_all.fbx" + }, + "id": "transitionToAltIdle2", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 388, + "loopFlag": true, + "startFrame": 80, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_LFF_all.fbx" + }, + "id": "altIdle1", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 388, + "loopFlag": true, + "startFrame": 80, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_RFF_all.fbx" + }, + "id": "altIdle2", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 472, + "loopFlag": false, + "startFrame": 388, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_LFF_all.fbx" + }, + "id": "alt1ToMasterIdle", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 453, + "loopFlag": false, + "startFrame": 388, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_RFF_all.fbx" + }, + "id": "alt2ToMasterIdle", + "type": "clip" + } + ], + "data": { + "currentState": "transitionToAltIdle1", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "transitionToAltIdle1", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.5, + "resume": false, + "transitions": [ + { + "randomSwitchState": "altIdle1", + "var": "transitionToAltIdle1OnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "transitionToAltIdle2", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": 0.5, + "resume": false, + "transitions": [ + { + "randomSwitchState": "altIdle2", + "var": "transitionToAltIdle2OnDone" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "altIdle1", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "alt1ToMasterIdle", + "var": "finishAltIdle2" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "altIdle2", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "alt2ToMasterIdle", + "var": "finishAltIdle2" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "alt1ToMasterIdle", + "interpDuration": 24, + "interpTarget": 24, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "alt2ToMasterIdle", + "interpDuration": 24, + "interpTarget": 24, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + ] + } + ], + "transitionVar": "finishAltIdle2", + "triggerTimeMax": 60, + "triggerTimeMin": 10 + }, + "id": "alternateIdle", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "currentState": "movement", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "movement", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 0.6, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "alternateIdle", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 0.4, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "fidget", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "currentState": "masterIdle", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "masterIdle", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "fidget", + "var": "timeToFidget" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "fidget", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "priority": -1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "masterIdle", + "var": "idle_once_slownodOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idle_once_headtiltOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idle_once_shiftheelpivotOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idleWS_allOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idle_once_lookaroundOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idle_once_neckstretchOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idle_once_lookleftrightOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "idle_once_fidgetOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "alt1ToMasterIdleOnDone" + }, + { + "randomSwitchState": "masterIdle", + "var": "alt2ToMasterIdleOnDone" + } + ] + } + ], + "transitionVar": "timeToFidget", + "triggerTimeMax": 50, + "triggerTimeMin": 10 + }, + "id": "idleStand", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "alpha": 1, + "alphaVar": "talkOverlayAlpha", + "boneSet": "upperBody" + }, + "id": "idleTalkOverlay", + "type": "overlay" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 64, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_agree_acknowledge.fbx" + }, + "id": "positiveAcknowledge", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 55, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_agree_headnod.fbx" + }, + "id": "positiveHeadNod", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 94, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_agree_headnodyes.fbx" + }, + "id": "positiveHeadNodYes", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 68, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_agree_longheadnod.fbx" + }, + "id": "positiveLongHeadNod", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 84, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_agree_thoughtfulheadnod.fbx" + }, + "id": "positiveThoughtfulHeadNod", + "type": "clip" + } + ], + "data": { + "currentState": "positiveAcknowledge", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "positiveAcknowledge", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "positiveHeadNod", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "positiveHeadNodYes", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "positiveLongHeadNod", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "positiveThoughtfulHeadNod", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 0.33, + "resume": false, + "transitions": [ + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "reactionPositive", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 72, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_disagree_annoyedheadshake.fbx" + }, + "id": "negativeAnnoyedHeadshake", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 84, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_disagree_thoughtfulheadshake.fbx" + }, + "id": "negativeThoughtfulHeadshake", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 100, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_disagree_drophead.fbx" + }, + "id": "negativeDropHead", + "type": "clip" + } + ], + "data": { + "currentState": "negativeAnnoyedHeadshake", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "negativeAnnoyedHeadshake", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "negativeThoughtfulHeadshake", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "negativeDropHead", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "reactionNegative", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 18, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand01_all.fbx" + }, + "id": "raiseHand01Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 378, + "loopFlag": true, + "startFrame": 18, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand01_all.fbx" + }, + "id": "raiseHand01Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 435, + "loopFlag": false, + "startFrame": 378, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand01_all.fbx" + }, + "id": "raiseHand01Outro", + "type": "clip" + } + ], + "data": { + "currentState": "raiseHand01Intro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "raiseHand01Intro", + "interpDuration": 10, + "interpTarget": 10, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand01Loop", + "var": "raiseHand01IntroOnDone" + } + ] + }, + { + "id": "raiseHand01Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand01Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "id": "raiseHand01Outro", + "interpDuration": 6, + "interpTarget": 6, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand01Loop", + "var": "reactionRaiseHandEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "raiseHand01", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 19, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand03_all.fbx" + }, + "id": "raiseHand03Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 237, + "loopFlag": true, + "startFrame": 19, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand03_all.fbx" + }, + "id": "raiseHand03Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 300, + "loopFlag": false, + "startFrame": 237, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand03_all.fbx" + }, + "id": "raiseHand03Outro", + "type": "clip" + } + ], + "data": { + "currentState": "raiseHand03Intro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "raiseHand03Intro", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand03Loop", + "var": "raiseHand03IntroOnDone" + } + ] + }, + { + "id": "raiseHand03Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand03Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "id": "raiseHand03Outro", + "interpDuration": 6, + "interpTarget": 6, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand03Loop", + "var": "reactionRaiseHandEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "raiseHand03", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 32, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand04_all.fbx" + }, + "id": "raiseHand04Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 345, + "loopFlag": true, + "startFrame": 32, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand04_all.fbx" + }, + "id": "raiseHand04Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 400, + "loopFlag": false, + "startFrame": 345, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_raisehand04_all.fbx" + }, + "id": "raiseHand04Outro", + "type": "clip" + } + ], + "data": { + "currentState": "raiseHand04Intro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "raiseHand04Intro", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand04Loop", + "var": "raiseHand04IntroOnDone" + } + ] + }, + { + "id": "raiseHand04Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand04Outro", + "var": "reactionRaiseHandDisabled" + } + ] + }, + { + "id": "raiseHand04Outro", + "interpDuration": 6, + "interpTarget": 6, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "raiseHand04Loop", + "var": "reactionRaiseHandEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "raiseHand04", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "currentState": "raiseHand01", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "raiseHand01", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "raiseHand03", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "raiseHand04", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "reactionRaiseHand", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 17, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap01_all.fbx" + }, + "id": "applaudClap01Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 111, + "loopFlag": true, + "startFrame": 17, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap01_all.fbx" + }, + "id": "applaudClap01Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 160, + "loopFlag": false, + "startFrame": 111, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap01_all.fbx" + }, + "id": "applaudClap01Outro", + "type": "clip" + } + ], + "data": { + "currentState": "applaudClap01Intro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "applaudClap01Intro", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap01Loop", + "var": "applaudClap01IntroOnDone" + } + ] + }, + { + "id": "applaudClap01Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap01Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "id": "applaudClap01Outro", + "interpDuration": 6, + "interpTarget": 6, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap01Loop", + "var": "reactionApplaudEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "applaudClap01", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 14, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap02_all.fbx" + }, + "id": "applaudClap02Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 78, + "loopFlag": true, + "startFrame": 14, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap02_all.fbx" + }, + "id": "applaudClap02Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 115, + "loopFlag": false, + "startFrame": 78, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap02_all.fbx" + }, + "id": "applaudClap02Outro", + "type": "clip" + } + ], + "data": { + "currentState": "applaudClap02Intro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "applaudClap02Intro", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap02Loop", + "var": "applaudClap02IntroOnDone" + } + ] + }, + { + "id": "applaudClap02Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap02Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "id": "applaudClap02Outro", + "interpDuration": 6, + "interpTarget": 6, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap02Loop", + "var": "reactionApplaudEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "applaudClap02", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 14, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap03_all.fbx" + }, + "id": "applaudClap03Intro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 100, + "loopFlag": true, + "startFrame": 14, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap03_all.fbx" + }, + "id": "applaudClap03Loop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 149, + "loopFlag": false, + "startFrame": 100, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_clap03_all.fbx" + }, + "id": "applaudClap03Outro", + "type": "clip" + } + ], + "data": { + "currentState": "applaudClap03Intro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "applaudClap03Intro", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap03Loop", + "var": "applaudClap03IntroOnDone" + } + ] + }, + { + "id": "applaudClap03Loop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap03Outro", + "var": "reactionApplaudDisabled" + } + ] + }, + { + "id": "applaudClap03Outro", + "interpDuration": 6, + "interpTarget": 6, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "applaudClap03Loop", + "var": "reactionApplaudEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "applaudClap03", + "type": "randomSwitchStateMachine" + } + ], + "data": { + "currentState": "applaudClap01", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "applaudClap01", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "applaudClap02", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "applaudClap03", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ] + }, + "id": "reactionApplaud", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 21, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_all.fbx" + }, + "id": "reactionPointIntro", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 100, + "loopFlag": true, + "startFrame": 21, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_all.fbx" + }, + "id": "reactionPointLoop", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 134, + "loopFlag": false, + "startFrame": 100, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_all.fbx" + }, + "id": "reactionPointOutro", + "type": "clip" + } + ], + "data": { + "currentState": "reactionPointIntro", + "randomSwitchTimeMax": 10, + "randomSwitchTimeMin": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "reactionPointIntro", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + { + "randomSwitchState": "reactionPointLoop", + "var": "reactionPointIntroOnDone" + } + ] + }, + { + "id": "reactionPointLoop", + "interpDuration": 1, + "interpTarget": 1, + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "reactionPointOutro", + "var": "reactionPointDisabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "reactionPointOutro", + "interpDuration": 6, + "interpTarget": 6, + "interpType": "evaluateBoth", + "priority": 0, + "resume": false, + "transitions": [ + { + "randomSwitchState": "reactionPointLoop", + "var": "reactionPointEnabled" + } + ] + } + ], + "triggerRandomSwitch": "" + }, + "id": "reactionPoint", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 11, + "loopFlag": true, + "startFrame": 11, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 30, + "loopFlag": true, + "startFrame": 30, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 50, + "loopFlag": true, + "startFrame": 50, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointUp", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 70, + "loopFlag": true, + "startFrame": 70, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointDown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 90, + "loopFlag": true, + "startFrame": 90, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointUpLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 110, + "loopFlag": true, + "startFrame": 110, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointUpRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 130, + "loopFlag": true, + "startFrame": 130, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointDownLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 150, + "loopFlag": true, + "startFrame": 150, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointDownRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 3, + "loopFlag": true, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/emote_point01_aimoffsets.fbx" + }, + "id": "idlePointCenter", + "type": "clip" + } + ], + "data": { + "alpha": [ + 0, + 0, + 0 + ], + "alphaVar": "pointAroundAlpha", + "centerId": "idlePointCenter", + "downId": "idlePointDown", + "downLeftId": "idlePointDownLeft", + "downRightId": "idlePointDownRight", + "leftId": "idlePointLeft", + "rightId": "idlePointRight", + "upId": "idlePointUp", + "upLeftId": "idlePointUpLeft", + "upRightId": "idlePointUpRight" + }, + "id": "idlePointAround", + "type": "blendDirectional" + } + ], + "data": { + "alpha": 0, + "alphaVar": "pointBlendAlpha", + "blendType": "addAbsolute" + }, + "id": "reactionPoint", + "type": "blendLinear" + } + ], + "data": { + "currentState": "idleTalkOverlay", + "states": [ + { + "easingType": "easeInOutQuad", + "id": "idleTalkOverlay", + "interpDuration": 25, + "interpTarget": 25, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "reactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "reactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "reactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "reactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "reactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "reactionPositive", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "idleTalkOverlay", + "var": "positiveAcknowledgeOnDone" + }, + { + "state": "idleTalkOverlay", + "var": "positiveHeadNodOnDone" + }, + { + "state": "idleTalkOverlay", + "var": "positiveHeadNodYesOnDone" + }, + { + "state": "idleTalkOverlay", + "var": "positiveLongHeadNodOnDone" + }, + { + "state": "idleTalkOverlay", + "var": "positiveThoughtfulHeadNodOnDone" + }, + { + "state": "reactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "reactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "reactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "reactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "reactionNegative", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "reactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "idleTalkOverlay", + "var": "negativeAnnoyedHeadshakeOnDone" + }, + { + "state": "idleTalkOverlay", + "var": "negativeThoughtfulHeadshakeOnDone" + }, + { + "state": "idleTalkOverlay", + "var": "negativeDropHeadOnDone" + }, + { + "state": "reactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "reactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "reactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "reactionRaiseHand", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "reactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "reactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "idleTalkOverlay", + "var": "reactionRaiseHandDisabled" + }, + { + "state": "reactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "reactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "reactionApplaud", + "interpDuration": 18, + "interpTarget": 18, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "reactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "reactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "reactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "idleTalkOverlay", + "var": "reactionApplaudDisabled" + }, + { + "state": "reactionPoint", + "var": "reactionPointEnabled" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "reactionPoint", + "interpDuration": 10, + "interpTarget": 10, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "reactionNegative", + "var": "reactionNegativeTrigger" + }, + { + "state": "reactionPositive", + "var": "reactionPositiveTrigger" + }, + { + "state": "reactionRaiseHand", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "reactionApplaud", + "var": "reactionApplaudEnabled" + }, + { + "state": "idleTalkOverlay", + "var": "reactionPointDisabled" + } + ] + } + ] + }, + "id": "idle", + "type": "stateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 40, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_short_fwd.fbx" + }, + "id": "walkFwdShort_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 30, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_fwd.fbx" + }, + "id": "walkFwdNormal_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 26, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx" + }, + "id": "walkFwdFast_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 18, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jog_fwd.fbx" + }, + "id": "walkFwdJog_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 19, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/run_fast_fwd.fbx" + }, + "id": "walkFwdRun_c", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "moveForwardAlpha", + "characteristicSpeeds": [ + 0.5, + 1.8, + 2.5, + 3.55, + 5.675 + ], + "desiredSpeed": 1.4, + "desiredSpeedVar": "moveForwardSpeed" + }, + "id": "WALKFWD", + "type": "blendLinearMove" + }, + { + "children": [ + ], + "data": { + "endFrame": 13, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_to_walk.fbx" + }, + "id": "idleToWalkFwd", + "type": "clip" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 59, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle.fbx" + }, + "id": "idleSettle01", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "blendType": "", + "endFrame": 40, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle02.fbx" + }, + "id": "idleSettle02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 60, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle03.fbx" + }, + "id": "idleSettle03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 82, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle04.fbx" + }, + "id": "idleSettle04", + "type": "clip" + } + ], + "data": { + "currentState": "idleSettle01", + "endFrame": 59, + "loopFlag": false, + "randomSwitchTimeMin": 1, + "startFrame": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "idleSettle01", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle02", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle03", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle04", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ], + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle.fbx" + }, + "id": "idleSettle", + "type": "randomSwitchStateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 37, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_bwd.fbx" + }, + "id": "walkBwdShort_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 28, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx" + }, + "id": "walkBwdFast_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 20, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jog_bwd.fbx" + }, + "id": "jogBwd_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 14, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/run_bwd.fbx" + }, + "id": "runBwd_c", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "moveBackwardAlpha", + "characteristicSpeeds": [ + 0.6, + 1.6, + 2.8, + 4.5 + ], + "desiredSpeed": 1.4, + "desiredSpeedVar": "moveBackwardSpeed" + }, + "id": "WALKBWD", + "type": "blendLinearMove" + }, + { + "children": [ + ], + "data": { + "endFrame": 33, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/turn_left.fbx" + }, + "id": "turnLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 31, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/turn_right.fbx" + }, + "id": "turnRight", + "type": "clip" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 35, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_left.fbx" + }, + "id": "strafeLeftWalk_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 21, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_left_fast.fbx" + }, + "id": "strafeLeftWalkFast_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 20, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jog_left.fbx" + }, + "id": "strafeLeftJog_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 19, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/run_fast_left.fbx" + }, + "id": "strafeLeftRun_c", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "moveLateralAlpha", + "characteristicSpeeds": [ + 1, + 2.55, + 3.35, + 5.25 + ], + "desiredSpeed": 1.4, + "desiredSpeedVar": "moveLateralSpeed" + }, + "id": "STRAFELEFT", + "type": "blendLinearMove" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 35, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_right.fbx" + }, + "id": "strafeRightWalk_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 21, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/walk_right_fast.fbx" + }, + "id": "strafeRightFast_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 20, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jog_right.fbx" + }, + "id": "strafeRightJog_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 19, + "loopFlag": true, + "mirrorFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/run_fast_right.fbx" + }, + "id": "strafeRightRun_c", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "moveLateralAlpha", + "characteristicSpeeds": [ + 1, + 2.55, + 3.4, + 5.25 + ], + "desiredSpeed": 1.4, + "desiredSpeedVar": "moveLateralSpeed" + }, + "id": "STRAFERIGHT", + "type": "blendLinearMove" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 30, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/side_step_short_left.fbx" + }, + "id": "stepLeftShort_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 20, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/side_step_left.fbx" + }, + "id": "stepLeft_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 16, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/side_step_left_fast.fbx" + }, + "id": "strafeLeftAnim_c", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "moveLateralAlpha", + "characteristicSpeeds": [ + 0, + 0.5, + 2.5 + ], + "desiredSpeed": 1.4, + "desiredSpeedVar": "moveLateralSpeed" + }, + "id": "strafeLeftHmd", + "type": "blendLinearMove" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 30, + "loopFlag": true, + "mirrorFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/side_step_short_left.fbx" + }, + "id": "stepRightShort_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 20, + "loopFlag": true, + "mirrorFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/side_step_left.fbx" + }, + "id": "stepRight_c", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 16, + "loopFlag": true, + "mirrorFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/side_step_left_fast.fbx" + }, + "id": "strafeRightAnim_c", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "moveLateralAlpha", + "characteristicSpeeds": [ + 0, + 0.5, + 2.5 + ], + "desiredSpeed": 1.4, + "desiredSpeedVar": "moveLateralSpeed" + }, + "id": "strafeRightHmd", + "type": "blendLinearMove" + }, + { + "children": [ + ], + "data": { + "endFrame": 79, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/fly.fbx" + }, + "id": "fly", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 16, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_standing_launch_all.fbx" + }, + "id": "takeoffStand", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 15, + "loopFlag": false, + "startFrame": 4, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx" + }, + "id": "TAKEOFFRUN", + "type": "clip" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 1, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_standing_apex_all.fbx" + }, + "id": "inAirStandPreApex", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 2, + "loopFlag": false, + "startFrame": 2, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_standing_apex_all.fbx" + }, + "id": "inAirStandApex", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 3, + "loopFlag": false, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_standing_apex_all.fbx" + }, + "id": "inAirStandPostApex", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "inAirAlpha" + }, + "id": "inAirStand", + "type": "blendLinear" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "endFrame": 16, + "loopFlag": false, + "startFrame": 16, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx" + }, + "id": "inAirRunPreApex", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 22, + "loopFlag": false, + "startFrame": 22, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx" + }, + "id": "inAirRunApex", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 33, + "loopFlag": false, + "startFrame": 33, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx" + }, + "id": "inAirRunPostApex", + "type": "clip" + } + ], + "data": { + "alpha": 0, + "alphaVar": "inAirAlpha" + }, + "id": "INAIRRUN", + "type": "blendLinear" + }, + { + "children": [ + ], + "data": { + "endFrame": 6, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_standing_land_settle_all.fbx" + }, + "id": "landStandImpact", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 68, + "loopFlag": false, + "startFrame": 6, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_standing_land_settle_all.fbx" + }, + "id": "landStand", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 40, + "loopFlag": false, + "startFrame": 29, + "timeScale": 1, + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx" + }, + "id": "LANDRUN", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 40, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle_small.fbx" + }, + "id": "idleSettleSmall", + "type": "clip" + } + ], + "data": { + "currentState": "idle", + "outputJoints": [ + "LeftFoot", + "RightFoot" + ], + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seated", + "interpDuration": 6, + "interpTarget": 6, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "idle", + "var": "isNotSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idle", + "interpDuration": 20, + "interpTarget": 20, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "idleToWalkFwd", + "interpDuration": 8, + "interpTarget": 12, + "transitions": [ + { + "state": "WALKFWD", + "var": "idleToWalkFwdOnDone" + }, + { + "state": "idle", + "var": "isNotInput" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idle", + "var": "idleSettle01OnDone" + }, + { + "state": "idle", + "var": "idleSettle02OnDone" + }, + { + "state": "idle", + "var": "idleSettle03OnDone" + }, + { + "state": "idle", + "var": "idleSettle04OnDone" + }, + { + "state": "idle", + "var": "reactionPositiveTrigger" + }, + { + "state": "idle", + "var": "reactionNegativeTrigger" + }, + { + "state": "idle", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "idle", + "var": "reactionApplaudEnabled" + }, + { + "state": "idle", + "var": "reactionPointEnabled" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettleSmall", + "interpDuration": 10, + "interpTarget": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idle", + "var": "idleSettleSmallOnDone" + }, + { + "state": "idle", + "var": "reactionPositiveTrigger" + }, + { + "state": "idle", + "var": "reactionNegativeTrigger" + }, + { + "state": "idle", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "idle", + "var": "reactionApplaudEnabled" + }, + { + "state": "idle", + "var": "reactionPointEnabled" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "WALKFWD", + "interpDuration": 15, + "interpTarget": 35, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, + { + "state": "idleSettle", + "var": "isNotInputSlow" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "WALKBWD", + "interpDuration": 15, + "interpTarget": 35, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, + { + "state": "idleSettle", + "var": "isNotInputSlow" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "STRAFERIGHT", + "interpDuration": 15, + "interpTarget": 25, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, + { + "state": "idleSettle", + "var": "isNotInputSlow" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFELEFT", + "var": "isMovingLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "STRAFELEFT", + "interpDuration": 15, + "interpTarget": 25, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, + { + "state": "idleSettle", + "var": "isNotInputSlow" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isMovingRight" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "turnRight", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "idle", + "var": "isNotTurning" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "turnLeft", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "idle", + "var": "isNotTurning" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "strafeRightHmd", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idleSettle", + "var": "isNotMoving" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "strafeLeftHmd", + "interpDuration": 8, + "interpTarget": 8, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idleSettle", + "var": "isNotMoving" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "fly", + "interpDuration": 24, + "interpTarget": 24, + "interpType": "evaluateBoth", + "transitions": [ + { + "state": "idleSettle", + "var": "isNotFlying" + } + ] + }, + { + "id": "takeoffStand", + "interpDuration": 2, + "interpTarget": 2, + "transitions": [ + { + "state": "inAirStand", + "var": "isNotTakeoff" + } + ] + }, + { + "id": "TAKEOFFRUN", + "interpDuration": 2, + "interpTarget": 2, + "transitions": [ + { + "state": "INAIRRUN", + "var": "isNotTakeoff" + } + ] + }, + { + "id": "inAirStand", + "interpDuration": 3, + "interpTarget": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "landStandImpact", + "var": "isNotInAir" + } + ] + }, + { + "id": "INAIRRUN", + "interpDuration": 3, + "interpTarget": 3, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "WALKFWD", + "var": "isNotInAir" + } + ] + }, + { + "id": "landStandImpact", + "interpDuration": 1, + "interpTarget": 1, + "transitions": [ + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "landStand", + "var": "landStandImpactOnDone" + } + ] + }, + { + "id": "landStand", + "interpDuration": 1, + "interpTarget": 1, + "transitions": [ + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "idle", + "var": "landStandOnDone" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "id": "LANDRUN", + "interpDuration": 2, + "interpTarget": 2, + "transitions": [ + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "WALKFWD", + "var": "landRunOnDone" + } + ] + } + ] + }, + "id": "mainStateMachine", + "type": "stateMachine" + }, + { + "children": [ + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 11, + "loopFlag": true, + "startFrame": 11, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 30, + "loopFlag": true, + "startFrame": 30, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 50, + "loopFlag": true, + "startFrame": 50, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookUp", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 70, + "loopFlag": true, + "startFrame": 70, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookDown", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 97, + "loopFlag": true, + "startFrame": 97, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookUpLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 110, + "loopFlag": true, + "startFrame": 110, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookUpRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 130, + "loopFlag": true, + "startFrame": 130, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookDownLeft", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 150, + "loopFlag": true, + "startFrame": 150, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookDownRight", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "baseFrame": 1, + "baseURL": "qrc:///avatar/animations/idle_aimoffsets.fbx", + "blendType": "addAbsolute", + "endFrame": 3, + "loopFlag": true, + "startFrame": 3, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle_aimoffsets.fbx" + }, + "id": "lookCenter", + "type": "clip" + } + ], + "data": { + "alpha": [ + 0, + 0, + 0 + ], + "alphaVar": "lookAroundAlpha", + "centerId": "lookCenter", + "downId": "lookDown", + "downLeftId": "lookDownLeft", + "downRightId": "lookDownRight", + "leftId": "lookLeft", + "rightId": "lookRight", + "upId": "lookUp", + "upLeftId": "lookUpLeft", + "upRightId": "lookUpRight" + }, + "id": "lookAroundBlend", + "type": "blendDirectional" + } + ], + "data": { + "alpha": 0, + "alphaVar": "lookBlendAlpha", + "blendType": "addAbsolute" + }, + "id": "lookAround", + "type": "blendLinear" + } + ], + "data": { + "alpha": 0, + "alphaVar": "leftHandOverlayAlpha", + "boneSet": "leftHand" + }, + "id": "leftHandOverlay", + "type": "overlay" + } + ], + "data": { + "alpha": 0, + "alphaVar": "rightHandOverlayAlpha", + "boneSet": "rightHand" + }, + "id": "rightHandOverlay", + "type": "overlay" + } + ], + "data": { + "alpha": 0, + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" + }, + "id": "defaultPoseOverlay", + "type": "overlay" + } + ], + "data": { + "alpha": 1, + "alphaVar": "splineIKAlpha", + "baseJointName": "Hips", + "basePositionVar": "hipsPosition", + "baseRotationVar": "hipsRotation", + "enabled": false, + "enabledVar": "splineIKEnabled", + "interpDuration": 15, + "midJointName": "Spine2", + "midPositionVar": "spine2Position", + "midRotationVar": "spine2Rotation", + "midTargetFlexCoefficients": [ + 1, + 1, + 1 + ], + "tipJointName": "Head", + "tipPositionVar": "headPosition", + "tipRotationVar": "headRotation", + "tipTargetFlexCoefficients": [ + 1, + 1, + 1, + 1, + 1 + ] + }, + "id": "userSplineIK", + "type": "splineIK" + } + ], + "data": { + "alpha": 1, + "alphaVar": "leftHandIKAlpha", + "baseJointName": "LeftArm", + "enabled": false, + "enabledVar": "leftHandIKEnabled", + "endEffectorPositionVarVar": "leftHandIKPositionVar", + "endEffectorRotationVarVar": "leftHandIKRotationVar", + "interpDuration": 15, + "midHingeAxis": [ + 0, + 0, + 1 + ], + "midJointName": "LeftForeArm", + "tipJointName": "LeftHand" + }, + "id": "leftHandIK", + "type": "twoBoneIK" + } + ], + "data": { + "baseJointName": "LeftArm", + "enabled": false, + "enabledVar": "leftHandPoleVectorEnabled", + "midJointName": "LeftForeArm", + "poleVectorVar": "leftHandPoleVector", + "referenceVector": [ + 1, + 0, + 0 + ], + "tipJointName": "LeftHand" + }, + "id": "leftHandPoleVector", + "type": "poleVectorConstraint" + } + ], + "data": { + "alpha": 1, + "alphaVar": "rightHandIKAlpha", + "baseJointName": "RightArm", + "enabled": false, + "enabledVar": "rightHandIKEnabled", + "endEffectorPositionVarVar": "rightHandIKPositionVar", + "endEffectorRotationVarVar": "rightHandIKRotationVar", + "interpDuration": 15, + "midHingeAxis": [ + 0, + 0, + -1 + ], + "midJointName": "RightForeArm", + "tipJointName": "RightHand" + }, + "id": "rightHandIK", + "type": "twoBoneIK" + } + ], + "data": { + "baseJointName": "RightArm", + "enabled": false, + "enabledVar": "rightHandPoleVectorEnabled", + "midJointName": "RightForeArm", + "poleVectorVar": "rightHandPoleVector", + "referenceVector": [ + -1, + 0, + 0 + ], + "tipJointName": "RightHand" + }, + "id": "rightHandPoleVector", + "type": "poleVectorConstraint" + } + ], + "data": { + "alpha": 1, + "alphaVar": "leftFootIKAlpha", + "baseJointName": "LeftUpLeg", + "enabled": false, + "enabledVar": "leftFootIKEnabled", + "endEffectorPositionVarVar": "leftFootIKPositionVar", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "interpDuration": 15, + "midHingeAxis": [ + -1, + 0, + 0 + ], + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot" + }, + "id": "leftFootIK", + "type": "twoBoneIK" + } + ], + "data": { + "baseJointName": "LeftUpLeg", + "enabled": false, + "enabledVar": "leftFootPoleVectorEnabled", + "midJointName": "LeftLeg", + "poleVectorVar": "leftFootPoleVector", + "referenceVector": [ + 0, + 0, + 1 + ], + "tipJointName": "LeftFoot" + }, + "id": "leftFootPoleVector", + "type": "poleVectorConstraint" + } + ], + "data": { + "alpha": 1, + "alphaVar": "rightFootIKAlpha", + "baseJointName": "RightUpLeg", + "enabled": false, + "enabledVar": "rightFootIKEnabled", + "endEffectorPositionVarVar": "rightFootIKPositionVar", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "interpDuration": 15, + "midHingeAxis": [ + -1, + 0, + 0 + ], + "midJointName": "RightLeg", + "tipJointName": "RightFoot" + }, + "id": "rightFootIK", + "type": "twoBoneIK" + } + ], + "data": { + "baseJointName": "RightUpLeg", + "enabled": false, + "enabledVar": "rightFootPoleVectorEnabled", + "midJointName": "RightLeg", + "poleVectorVar": "rightFootPoleVector", + "referenceVector": [ + 0, + 0, + 1 + ], + "tipJointName": "RightFoot" + }, + "id": "userAnimNone", + "type": "poleVectorConstraint" + }, + { + "children": [ + ], + "data": { + "endFrame": 90, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle.fbx" + }, + "id": "userAnimA", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 90, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/idle.fbx" + }, + "id": "userAnimB", + "type": "clip" + } + ], + "data": { + "currentState": "userAnimNone", + "states": [ + { + "id": "userAnimNone", + "interpDuration": 6, + "interpTarget": 6, + "transitions": [ + { + "state": "userAnimA", + "var": "userAnimA" + }, + { + "state": "userAnimB", + "var": "userAnimB" + } + ] + }, + { + "id": "userAnimA", + "interpDuration": 6, + "interpTarget": 6, + "transitions": [ + { + "state": "userAnimNone", + "var": "userAnimNone" + }, + { + "state": "userAnimB", + "var": "userAnimB" + } + ] + }, + { + "id": "userAnimB", + "interpDuration": 6, + "interpTarget": 6, + "transitions": [ + { + "state": "userAnimNone", + "var": "userAnimNone" + }, + { + "state": "userAnimA", + "var": "userAnimA" + } + ] + } + ] + }, + "id": "userAnimStateMachine", + "type": "stateMachine" + }, + "version": "1.1" +} diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index c7cb176920..3ee0f6f9c4 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -684,139 +684,295 @@ "children": [ { "children": [ + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle.fbx" + }, + "id": "seatedIdle01", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle02.fbx" + }, + "id": "seatedIdle02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 0, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle03.fbx" + }, + "id": "seatedIdle03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 800, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle04.fbx" + }, + "id": "seatedIdle04", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 332, + "loopFlag": true, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/sitting_idle05.fbx" + }, + "id": "seatedIdle05", + "type": "clip" + } ], "data": { - "endFrame": 800, - "loopFlag": true, - "startFrame": 0, + "currentState": "seatedIdle01", + "endFrame": 30, + "randomSwitchTimeMax": 40, + "randomSwitchTimeMin": 10, + "startFrame": 10, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "seatedIdle01", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle02", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle03", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle04", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "seatedIdle05", + "interpDuration": 30, + "interpTarget": 30, + "interpType": "evaluateBoth", + "priority": 1, + "resume": true, + "transitions": [ + ] + } + ], "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle.fbx" + "triggerRandomSwitch": "seatedIdleSwitch", + "triggerTimeMax": 10 }, - "id": "seatedIdle01", - "type": "clip" + "id": "masterSeatedIdleRand", + "type": "randomSwitchStateMachine" }, { "children": [ ], "data": { - "endFrame": 800, + "endFrame": 200, "loopFlag": true, "startFrame": 1, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle02.fbx" + "url": "qrc:///avatar/animations/sitting_turn_left.fbx" }, - "id": "seatedIdle02", + "id": "seatedTurnLeft", "type": "clip" }, { "children": [ ], "data": { - "endFrame": 800, - "loopFlag": true, - "startFrame": 0, - "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle03.fbx" - }, - "id": "seatedIdle03", - "type": "clip" - }, - { - "children": [ - ], - "data": { - "endFrame": 800, + "endFrame": 200, "loopFlag": true, "startFrame": 1, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle04.fbx" + "url": "qrc:///avatar/animations/sitting_turn_right.fbx" }, - "id": "seatedIdle04", + "id": "seatedTurnRight", "type": "clip" }, { "children": [ ], "data": { - "endFrame": 332, - "loopFlag": true, + "endFrame": 45, + "loopFlag": false, "startFrame": 1, "timeScale": 1, - "url": "qrc:///avatar/animations/sitting_idle05.fbx" + "url": "qrc:///avatar/animations/settle_sitturnright_to_sitidle.fbx" }, - "id": "seatedIdle05", + "id": "seatedTurnRight_to_Idle", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 45, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_sitturnleft_to_sitidle.fbx" + }, + "id": "seatedTurnLeft_to_Idle", "type": "clip" } ], "data": { - "currentState": "seatedIdle01", - "endFrame": 30, - "randomSwitchTimeMax": 40, - "randomSwitchTimeMin": 10, - "startFrame": 10, + "currentState": "masterSeatedIdleRand", "states": [ { "easingType": "easeInOutQuad", - "id": "seatedIdle01", - "interpDuration": 30, - "interpTarget": 30, - "interpType": "evaluateBoth", - "priority": 1, - "resume": true, + "id": "masterSeatedIdleRand", + "interpDuration": 15, + "interpTarget": 15, + "interpType": "snapshotPrev", "transitions": [ + { + "state": "seatedTurnLeft", + "var": "isSeatedTurningLeft" + }, + { + "state": "seatedTurnRight", + "var": "isSeatedTurningRight" + } ] }, { "easingType": "easeInOutQuad", - "id": "seatedIdle02", - "interpDuration": 30, - "interpTarget": 30, + "id": "seatedTurnLeft", + "interpDuration": 22, + "interpTarget": 22, "interpType": "evaluateBoth", - "priority": 1, - "resume": true, "transitions": [ + { + "state": "seatedTurnLeft_to_Idle", + "var": "isSeatedNotTurning" + }, + { + "state": "seatedTurnRight", + "var": "isSeatedTurningRight" + } ] }, { "easingType": "easeInOutQuad", - "id": "seatedIdle03", - "interpDuration": 30, - "interpTarget": 30, + "id": "seatedTurnRight", + "interpDuration": 22, + "interpTarget": 22, "interpType": "evaluateBoth", - "priority": 1, - "resume": true, "transitions": [ + { + "state": "seatedTurnLeft", + "var": "isSeatedTurningLeft" + }, + { + "state": "seatedTurnRight_to_Idle", + "var": "isSeatedNotTurning" + } ] }, { "easingType": "easeInOutQuad", - "id": "seatedIdle04", - "interpDuration": 30, - "interpTarget": 30, + "id": "seatedTurnRight_to_Idle", + "interpDuration": 22, + "interpTarget": 22, "interpType": "evaluateBoth", - "priority": 1, - "resume": true, "transitions": [ + { + "state": "seatedTurnLeft", + "var": "isSeatedTurningLeft" + }, + { + "state": "seatedTurnRight", + "var": "isSeatedTurningRight" + }, + { + "state": "masterSeatedIdleRand", + "var": "seatedTurnRight_to_IdleOnDone" + } ] }, { "easingType": "easeInOutQuad", - "id": "seatedIdle05", - "interpDuration": 30, - "interpTarget": 30, + "id": "seatedTurnLeft_to_Idle", + "interpDuration": 22, + "interpTarget": 22, "interpType": "evaluateBoth", - "priority": 1, - "resume": true, "transitions": [ + { + "state": "seatedTurnRight", + "var": "isSeatedTurningRight" + }, + { + "state": "seatedTurnLeft", + "var": "isSeatedTurningLeft" + }, + { + "state": "masterSeatedIdleRand", + "var": "seatedTurnLeft_to_IdleOnDone" + } ] } - ], - "timeScale": 1, - "triggerRandomSwitch": "seatedIdleSwitch", - "triggerTimeMax": 10 + ] }, "id": "masterSeatedIdle", - "type": "randomSwitchStateMachine" + "type": "stateMachine" }, { "children": [ @@ -1082,6 +1238,14 @@ { "randomSwitchState": "masterSeatedIdle", "var": "seatedFidgetShakeLegsOnDone" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "isSeatedTurningRight" + }, + { + "randomSwitchState": "masterSeatedIdle", + "var": "isSeatedTurningLeft" } ] } @@ -4804,16 +4968,117 @@ }, { "children": [ + { + "children": [ + ], + "data": { + "endFrame": 59, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle.fbx" + }, + "id": "idleSettle01", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "blendType": "", + "endFrame": 40, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle02.fbx" + }, + "id": "idleSettle02", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 60, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle03.fbx" + }, + "id": "idleSettle03", + "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 82, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle04.fbx" + }, + "id": "idleSettle04", + "type": "clip" + } ], "data": { + "currentState": "idleSettle01", "endFrame": 59, "loopFlag": false, + "randomSwitchTimeMin": 1, "startFrame": 1, + "states": [ + { + "easingType": "easeInOutQuad", + "id": "idleSettle01", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle02", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle03", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettle04", + "interpDuration": 1, + "interpTarget": 1, + "interpType": "evaluateBoth", + "priority": 1, + "resume": false, + "transitions": [ + ] + } + ], "timeScale": 1, - "url": "qrc:///avatar/animations/settle_to_idle_small.fbx" + "url": "qrc:///avatar/animations/settle_to_idle.fbx" }, "id": "idleSettle", - "type": "clip" + "type": "randomSwitchStateMachine" }, { "children": [ @@ -5346,6 +5611,19 @@ }, "id": "LANDRUN", "type": "clip" + }, + { + "children": [ + ], + "data": { + "endFrame": 40, + "loopFlag": false, + "startFrame": 1, + "timeScale": 1, + "url": "qrc:///avatar/animations/settle_to_idle_small.fbx" + }, + "id": "idleSettleSmall", + "type": "clip" } ], "data": { @@ -5371,7 +5649,7 @@ { "easingType": "easeInOutQuad", "id": "idle", - "interpDuration": 15, + "interpDuration": 20, "interpTarget": 20, "interpType": "evaluateBoth", "transitions": [ @@ -5503,13 +5781,114 @@ { "easingType": "easeInOutQuad", "id": "idleSettle", - "interpDuration": 13, - "interpTarget": 14, + "interpDuration": 15, + "interpTarget": 15, "interpType": "snapshotPrev", "transitions": [ { "state": "idle", - "var": "idleSettleOnDone" + "var": "idleSettle01OnDone" + }, + { + "state": "idle", + "var": "idleSettle02OnDone" + }, + { + "state": "idle", + "var": "idleSettle03OnDone" + }, + { + "state": "idle", + "var": "idleSettle04OnDone" + }, + { + "state": "idle", + "var": "reactionPositiveTrigger" + }, + { + "state": "idle", + "var": "reactionNegativeTrigger" + }, + { + "state": "idle", + "var": "reactionRaiseHandEnabled" + }, + { + "state": "idle", + "var": "reactionApplaudEnabled" + }, + { + "state": "idle", + "var": "reactionPointEnabled" + }, + { + "state": "WALKFWD", + "var": "isInputForward" + }, + { + "state": "WALKBWD", + "var": "isInputBackward" + }, + { + "state": "STRAFERIGHT", + "var": "isInputRight" + }, + { + "state": "STRAFELEFT", + "var": "isInputLeft" + }, + { + "state": "strafeRightHmd", + "var": "isMovingRightHmd" + }, + { + "state": "strafeLeftHmd", + "var": "isMovingLeftHmd" + }, + { + "state": "turnRight", + "var": "isTurningRight" + }, + { + "state": "turnLeft", + "var": "isTurningLeft" + }, + { + "state": "fly", + "var": "isFlying" + }, + { + "state": "takeoffStand", + "var": "isTakeoffStand" + }, + { + "state": "TAKEOFFRUN", + "var": "isTakeoffRun" + }, + { + "state": "inAirStand", + "var": "isInAirStand" + }, + { + "state": "INAIRRUN", + "var": "isInAirRun" + }, + { + "state": "seated", + "var": "isSeated" + } + ] + }, + { + "easingType": "easeInOutQuad", + "id": "idleSettleSmall", + "interpDuration": 10, + "interpTarget": 10, + "interpType": "snapshotPrev", + "transitions": [ + { + "state": "idle", + "var": "idleSettleSmallOnDone" }, { "state": "idle", @@ -5595,6 +5974,10 @@ "interpTarget": 35, "interpType": "snapshotPrev", "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, { "state": "idleSettle", "var": "isNotInputSlow" @@ -5659,6 +6042,10 @@ "interpTarget": 35, "interpType": "snapshotPrev", "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, { "state": "idleSettle", "var": "isNotInputSlow" @@ -5723,6 +6110,10 @@ "interpTarget": 25, "interpType": "snapshotPrev", "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, { "state": "idleSettle", "var": "isNotInputSlow" @@ -5779,6 +6170,10 @@ "interpTarget": 25, "interpType": "snapshotPrev", "transitions": [ + { + "state": "idleSettleSmall", + "var": "isNotInputNoMomentum" + }, { "state": "idleSettle", "var": "isNotInputSlow" diff --git a/interface/resources/avatar/avatar-animation_withSplineIKNode.json b/interface/resources/avatar/avatar-animation_withSplineIKNode.json deleted file mode 100644 index b1f198c52c..0000000000 --- a/interface/resources/avatar/avatar-animation_withSplineIKNode.json +++ /dev/null @@ -1,2229 +0,0 @@ -{ - "version": "1.1", - "root": { - "id": "userAnimStateMachine", - "type": "stateMachine", - "data": { - "currentState": "userAnimNone", - "states": [ - { - "id": "userAnimNone", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "userAnimA", - "state": "userAnimA" - }, - { - "var": "userAnimB", - "state": "userAnimB" - } - ] - }, - { - "id": "userAnimA", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "userAnimNone", - "state": "userAnimNone" - }, - { - "var": "userAnimB", - "state": "userAnimB" - } - ] - }, - { - "id": "userAnimB", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "userAnimNone", - "state": "userAnimNone" - }, - { - "var": "userAnimA", - "state": "userAnimA" - } - ] - } - ] - }, - "children": [ - { - "id": "userAnimNone", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 0, 0, 1 ], - "baseJointName": "RightUpLeg", - "midJointName": "RightLeg", - "tipJointName": "RightFoot", - "enabledVar": "rightFootPoleVectorEnabled", - "poleVectorVar": "rightFootPoleVector" - }, - "children": [ - { - "id": "rightFootIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "RightUpLeg", - "midJointName": "RightLeg", - "tipJointName": "RightFoot", - "midHingeAxis": [ -1, 0, 0 ], - "alphaVar": "rightFootIKAlpha", - "enabledVar": "rightFootIKEnabled", - "endEffectorRotationVarVar": "rightFootIKRotationVar", - "endEffectorPositionVarVar": "rightFootIKPositionVar" - }, - "children": [ - { - "id": "leftFootPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 0, 0, 1 ], - "baseJointName": "LeftUpLeg", - "midJointName": "LeftLeg", - "tipJointName": "LeftFoot", - "enabledVar": "leftFootPoleVectorEnabled", - "poleVectorVar": "leftFootPoleVector" - }, - "children": [ - { - "id": "leftFootIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "LeftUpLeg", - "midJointName": "LeftLeg", - "tipJointName": "LeftFoot", - "midHingeAxis": [ -1, 0, 0 ], - "alphaVar": "leftFootIKAlpha", - "enabledVar": "leftFootIKEnabled", - "endEffectorRotationVarVar": "leftFootIKRotationVar", - "endEffectorPositionVarVar": "leftFootIKPositionVar" - }, - "children": [ - { - "id": "rightHandPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ -1, 0, 0 ], - "baseJointName": "RightArm", - "midJointName": "RightForeArm", - "tipJointName": "RightHand", - "enabledVar": "rightHandPoleVectorEnabled", - "poleVectorVar": "rightHandPoleVector" - }, - "children": [ - { - "id": "rightHandIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "RightArm", - "midJointName": "RightForeArm", - "tipJointName": "RightHand", - "midHingeAxis": [ 0, 0, -1 ], - "alphaVar": "rightHandIKAlpha", - "enabledVar": "rightHandIKEnabled", - "endEffectorRotationVarVar": "rightHandIKRotationVar", - "endEffectorPositionVarVar": "rightHandIKPositionVar" - }, - "children": [ - { - "id": "leftHandPoleVector", - "type": "poleVectorConstraint", - "data": { - "enabled": false, - "referenceVector": [ 1, 0, 0 ], - "baseJointName": "LeftArm", - "midJointName": "LeftForeArm", - "tipJointName": "LeftHand", - "enabledVar": "leftHandPoleVectorEnabled", - "poleVectorVar": "leftHandPoleVector" - }, - "children": [ - { - "id": "leftHandIK", - "type": "twoBoneIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "LeftArm", - "midJointName": "LeftForeArm", - "tipJointName": "LeftHand", - "midHingeAxis": [ 0, 0, 1 ], - "alphaVar": "leftHandIKAlpha", - "enabledVar": "leftHandIKEnabled", - "endEffectorRotationVarVar": "leftHandIKRotationVar", - "endEffectorPositionVarVar": "leftHandIKPositionVar" - }, - "children": [ - { - "id": "userSplineIK", - "type": "splineIK", - "data": { - "alpha": 1.0, - "enabled": false, - "interpDuration": 15, - "baseJointName": "Hips", - "midJointName": "Spine2", - "tipJointName": "Head", - "basePositionVar": "hipsPosition", - "baseRotationVar": "hipsRotation", - "midPositionVar": "spine2Position", - "midRotationVar": "spine2Rotation", - "tipPositionVar": "headPosition", - "tipRotationVar": "headRotation", - "alphaVar": "splineIKAlpha", - "enabledVar": "splineIKEnabled", - "tipTargetFlexCoefficients": [ 1.0, 1.0, 1.0, 1.0, 1.0 ], - "midTargetFlexCoefficients": [ 1.0, 1.0, 1.0 ] - }, - "children": [ - { - "id": "defaultPoseOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" - }, - "children": [ - { - "id": "defaultPose", - "type": "defaultPose", - "data": { - }, - "children": [] - }, - { - "id": "rightHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" - }, - "children": [ - { - "id": "rightHandStateMachine", - "type": "stateMachine", - "data": { - "currentState": "rightHandGrasp", - "states": [ - { - "id": "rightHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightIndexPointAndThumbRaise", - "state": "rightIndexPointAndThumbRaise" - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isRightHandGrasp", - "state": "rightHandGrasp" - }, - { - "var": "isRightIndexPoint", - "state": "rightIndexPoint" - }, - { - "var": "isRightThumbRaise", - "state": "rightThumbRaise" - } - ] - } - ] - }, - "children": [ - { - "id": "rightHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "leftHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" - }, - "children": [ - { - "id": "leftHandStateMachine", - "type": "stateMachine", - "data": { - "currentState": "leftHandGrasp", - "states": [ - { - "id": "leftHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftIndexPointAndThumbRaise", - "state": "leftIndexPointAndThumbRaise" - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { - "var": "isLeftHandGrasp", - "state": "leftHandGrasp" - }, - { - "var": "isLeftIndexPoint", - "state": "leftIndexPoint" - }, - { - "var": "isLeftThumbRaise", - "state": "leftThumbRaise" - } - ] - } - ] - }, - "children": [ - { - "id": "leftHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftHandGraspOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandGraspClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "mainStateMachine", - "type": "stateMachine", - "data": { - "outputJoints": [ "LeftFoot", "RightFoot" ], - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 20, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 12, - "interpDuration": 8, - "transitions": [ - { - "var": "idleToWalkFwdOnDone", - "state": "WALKFWD" - }, - { - "var": "isNotMoving", - "state": "idle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "idleSettle", - "interpTarget": 15, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "idleSettleOnDone", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "WALKFWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "WALKBWD", - "interpTarget": 35, - "interpDuration": 10, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFERIGHT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "STRAFELEFT", - "interpTarget": 25, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "transitions": [ - { - "var": "isNotTurning", - "state": "idle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "strafeRightHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "strafeLeftHmd", - "interpTarget": 5, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotMoving", - "state": "idleSettle" - }, - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { - "var": "isNotFlying", - "state": "idleSettle" - } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "inAirStand" - } - ] - }, - { - "id": "TAKEOFFRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isNotTakeoff", - "state": "INAIRRUN" - } - ] - }, - { - "id": "inAirStand", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "landStandImpact" - } - ] - }, - { - "id": "INAIRRUN", - "interpTarget": 3, - "interpDuration": 3, - "interpType": "snapshotPrev", - "transitions": [ - { - "var": "isNotInAir", - "state": "WALKFWD" - } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landStandImpactOnDone", - "state": "landStand" - } - ] - }, - { - "id": "landStand", - "interpTarget": 1, - "interpDuration": 1, - "transitions": [ - { - "var": "isMovingForward", - "state": "WALKFWD" - }, - { - "var": "isMovingBackward", - "state": "WALKBWD" - }, - { - "var": "isMovingRight", - "state": "STRAFERIGHT" - }, - { - "var": "isMovingLeft", - "state": "STRAFELEFT" - }, - { - "var": "isTurningRight", - "state": "turnRight" - }, - { - "var": "isTurningLeft", - "state": "turnLeft" - }, - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "isInAirStand", - "state": "inAirStand" - }, - { - "var": "isInAirRun", - "state": "INAIRRUN" - }, - { - "var": "landStandOnDone", - "state": "idle" - }, - { - "var": "isMovingRightHmd", - "state": "strafeRightHmd" - }, - { - "var": "isMovingLeftHmd", - "state": "strafeLeftHmd" - } - ] - }, - { - "id": "LANDRUN", - "interpTarget": 2, - "interpDuration": 2, - "transitions": [ - { - "var": "isFlying", - "state": "fly" - }, - { - "var": "isTakeoffStand", - "state": "takeoffStand" - }, - { - "var": "isTakeoffRun", - "state": "TAKEOFFRUN" - }, - { - "var": "landRunOnDone", - "state": "WALKFWD" - } - ] - } - ] - }, - "children": [ - { - "id": "idle", - "type": "stateMachine", - "data": { - "currentState": "idleStand", - "states": [ - { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "isTalking", - "state": "idleTalk" - } - ] - }, - { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 10, - "transitions": [ - { - "var": "notIsTalking", - "state": "idleStand" - } - ] - } - ] - }, - "children": [ - { - "id": "idleStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "idleTalk", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "WALKFWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.5, 1.8, 2.3, 3.2, 4.5 ], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" - }, - "children": [ - { - "id": "walkFwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdNormal_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkFwdRun_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "idleToWalkFwd", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "idleSettle", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", - "startFrame": 1.0, - "endFrame": 59.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "WALKBWD", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.6, 1.6, 2.3, 3.1 ], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "jogBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "runBwd_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "STRAFELEFT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftWalkFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "STRAFERIGHT", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0.1, 0.5, 1.0, 2.6, 3.0 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeRightShortStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightStep_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightWalk_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightFast_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightJog_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeLeftHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepLeftShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "stepLeft_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRightHmd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [ 0, 0.5, 2.5 ], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "stepRightShort_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "stepRight_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeRightAnim_c", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "takeoffStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", - "startFrame": 2.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "TAKEOFFRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 4.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 1.0, - "endFrame": 1.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "INAIRRUN", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirRunPreApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 16.0, - "endFrame": 16.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 22.0, - "endFrame": 22.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunPostApex", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 33.0, - "endFrame": 33.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", - "startFrame": 6.0, - "endFrame": 68.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "LANDRUN", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", - "startFrame": 29.0, - "endFrame": 40.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "id": "userAnimA", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "userAnimB", - "type": "clip", - "data": { - "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } -} diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index d6ecc540c2..11c80a4dd2 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -269,6 +269,8 @@ { "from": "Keyboard.MouseWheelDown", "to": "Actions.LATERAL_LEFT" }, { "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.GesturePinchOut", "to": "Actions.BOOM_OUT"}, + { "from": "Keyboard.GesturePinchIn", "to": "Actions.BOOM_IN"}, { "from": "Keyboard.Space", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" }, diff --git a/interface/resources/controllers/osc.json b/interface/resources/controllers/osc.json new file mode 100644 index 0000000000..e5c639f8b7 --- /dev/null +++ b/interface/resources/controllers/osc.json @@ -0,0 +1,71 @@ +{ + "name": "OSC to Standard", + "channels": [ + { "from": "OSC.Head", "to" : "Standard.Head" }, + { "from": "OSC.LeftEye", "to" : "Standard.LeftEye" }, + { "from": "OSC.RightEye", "to" : "Standard.RightEye" }, + + { "from": "OSC.EyeBlink_L", "to": "Standard.EyeBlink_L" }, + { "from": "OSC.EyeBlink_R", "to": "Standard.EyeBlink_R" }, + { "from": "OSC.EyeSquint_L", "to": "Standard.EyeSquint_L" }, + { "from": "OSC.EyeSquint_R", "to": "Standard.EyeSquint_R" }, + { "from": "OSC.EyeDown_L", "to": "Standard.EyeDown_L" }, + { "from": "OSC.EyeDown_R", "to": "Standard.EyeDown_R" }, + { "from": "OSC.EyeIn_L", "to": "Standard.EyeIn_L" }, + { "from": "OSC.EyeIn_R", "to": "Standard.EyeIn_R" }, + { "from": "OSC.EyeOpen_L", "to": "Standard.EyeOpen_L" }, + { "from": "OSC.EyeOpen_R", "to": "Standard.EyeOpen_R" }, + { "from": "OSC.EyeOut_L", "to": "Standard.EyeOut_L" }, + { "from": "OSC.EyeOut_R", "to": "Standard.EyeOut_R" }, + { "from": "OSC.EyeUp_L", "to": "Standard.EyeUp_L" }, + { "from": "OSC.EyeUp_R", "to": "Standard.EyeUp_R" }, + { "from": "OSC.BrowsD_L", "to": "Standard.BrowsD_L" }, + { "from": "OSC.BrowsD_R", "to": "Standard.BrowsD_R" }, + { "from": "OSC.BrowsU_C", "to": "Standard.BrowsU_C" }, + { "from": "OSC.BrowsU_L", "to": "Standard.BrowsU_L" }, + { "from": "OSC.BrowsU_R", "to": "Standard.BrowsU_R" }, + { "from": "OSC.JawFwd", "to": "Standard.JawFwd" }, + { "from": "OSC.JawLeft", "to": "Standard.JawLeft" }, + { "from": "OSC.JawOpen", "to": "Standard.JawOpen" }, + { "from": "OSC.JawRight", "to": "Standard.JawRight" }, + { "from": "OSC.MouthLeft", "to": "Standard.MouthLeft" }, + { "from": "OSC.MouthRight", "to": "Standard.MouthRight" }, + { "from": "OSC.MouthFrown_L", "to": "Standard.MouthFrown_L" }, + { "from": "OSC.MouthFrown_R", "to": "Standard.MouthFrown_R" }, + { "from": "OSC.MouthSmile_L", "to": "Standard.MouthSmile_L" }, + { "from": "OSC.MouthSmile_R", "to": "Standard.MouthSmile_R" }, + { "from": "OSC.MouthDimple_L", "to": "Standard.MouthDimple_L" }, + { "from": "OSC.MouthDimple_R", "to": "Standard.MouthDimple_R" }, + { "from": "OSC.LipsStretch_L", "to": "Standard.LipsStretch_L" }, + { "from": "OSC.LipsStretch_R", "to": "Standard.LipsStretch_R" }, + { "from": "OSC.LipsUpperClose", "to": "Standard.LipsUpperClose" }, + { "from": "OSC.LipsLowerClose", "to": "Standard.LipsLowerClose" }, + { "from": "OSC.LipsFunnel", "to": "Standard.LipsFunnel" }, + { "from": "OSC.LipsPucker", "to": "Standard.LipsPucker" }, + { "from": "OSC.Puff", "to": "Standard.Puff" }, + { "from": "OSC.CheekSquint_L", "to": "Standard.CheekSquint_L" }, + { "from": "OSC.CheekSquint_R", "to": "Standard.CheekSquint_R" }, + { "from": "OSC.MouthClose", "to": "Standard.MouthClose" }, + { "from": "OSC.MouthUpperUp_L", "to": "Standard.MouthUpperUp_L" }, + { "from": "OSC.MouthUpperUp_R", "to": "Standard.MouthUpperUp_R" }, + { "from": "OSC.MouthLowerDown_L", "to": "Standard.MouthLowerDown_L" }, + { "from": "OSC.MouthLowerDown_R", "to": "Standard.MouthLowerDown_R" }, + { "from": "OSC.MouthPress_L", "to": "Standard.MouthPress_L" }, + { "from": "OSC.MouthPress_R", "to": "Standard.MouthPress_R" }, + { "from": "OSC.MouthShrugLower", "to": "Standard.MouthShrugLower" }, + { "from": "OSC.MouthShrugUpper", "to": "Standard.MouthShrugUpper" }, + { "from": "OSC.NoseSneer_L", "to": "Standard.NoseSneer_L" }, + { "from": "OSC.NoseSneer_R", "to": "Standard.NoseSneer_R" }, + { "from": "OSC.TongueOut", "to": "Standard.TongueOut" }, + { "from": "OSC.UserBlendshape0", "to": "Standard.UserBlendshape0" }, + { "from": "OSC.UserBlendshape1", "to": "Standard.UserBlendshape1" }, + { "from": "OSC.UserBlendshape2", "to": "Standard.UserBlendshape2" }, + { "from": "OSC.UserBlendshape3", "to": "Standard.UserBlendshape3" }, + { "from": "OSC.UserBlendshape4", "to": "Standard.UserBlendshape4" }, + { "from": "OSC.UserBlendshape5", "to": "Standard.UserBlendshape5" }, + { "from": "OSC.UserBlendshape6", "to": "Standard.UserBlendshape6" }, + { "from": "OSC.UserBlendshape7", "to": "Standard.UserBlendshape7" }, + { "from": "OSC.UserBlendshape8", "to": "Standard.UserBlendshape8" }, + { "from": "OSC.UserBlendshape9", "to": "Standard.UserBlendshape9" } + ] +} diff --git a/interface/resources/controllers/xbox.json b/interface/resources/controllers/xbox.json index 08088f50d9..cca92e1353 100644 --- a/interface/resources/controllers/xbox.json +++ b/interface/resources/controllers/xbox.json @@ -52,7 +52,6 @@ { "from": "GamePad.DL", "to": "Standard.DL" }, { "from": "GamePad.DR", "to": "Standard.DR" }, - { "from": [ "GamePad.Y" ], "to": "Standard.RightPrimaryThumb", "peek": true }, { "from": "GamePad.A", "to": "Standard.A" }, { "from": "GamePad.B", "to": "Standard.B" }, { "from": "GamePad.X", "to": "Standard.X" }, diff --git a/interface/resources/images/avatarapp/AvatarIsland.jpg b/interface/resources/images/avatarapp/AvatarIsland.jpg deleted file mode 100644 index f5f649abea..0000000000 Binary files a/interface/resources/images/avatarapp/AvatarIsland.jpg and /dev/null differ diff --git a/interface/resources/images/avatarapp/BodyMart.PNG b/interface/resources/images/avatarapp/BodyMart.PNG deleted file mode 100644 index 669a02c8fe..0000000000 Binary files a/interface/resources/images/avatarapp/BodyMart.PNG and /dev/null differ diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml index b44b95c5ca..913a817cd9 100644 --- a/interface/resources/qml/+android_interface/Stats.qml +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -201,7 +201,7 @@ Item { } StatText { visible: root.expanded; - text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " + + text: "Audio In Audio: " + root.audioInboundPPS + " pps, " + "Silent: " + root.audioSilentInboundPPS + " pps"; } StatText { diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 6de8676fd0..526d1da665 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -85,27 +85,31 @@ Item { UserActivityLogger.logAction("encourageLoginDialog", data); } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, - "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus }); + "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus, + "displayName":displayNameField.text }); } function init() { // going to/from sign in/up dialog. loginErrorMessage.text = linkAccountBody.errorString; loginErrorMessage.visible = (linkAccountBody.errorString !== ""); - if (loginErrorMessageTextMetrics.width > emailField.width) { + if (loginErrorMessageTextMetrics.width > displayNameField.width) { loginErrorMessage.wrapMode = Text.WordWrap; - errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height; + errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; } loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.color = hifi.buttons.blue; + displayNameField.placeholderText = "Display Name (optional)"; + var savedDisplayName = Settings.getValue("Avatar/displayName", ""); + displayNameField.text = savedDisplayName; emailField.placeholderText = "Username or Email"; var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2; - loginButton.anchors.right = emailField.right; + loginButton.anchors.right = displayNameField.right; } else { - loginButton.anchors.left = emailField.left; + loginButton.anchors.left = displayNameField.left; } loginContainer.visible = true; } @@ -125,14 +129,14 @@ Item { Item { id: loginContainer - width: emailField.width - height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + + width: displayNameField.width + height: errorContainer.height + displayNameField.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height anchors { top: parent.top topMargin: root.bannerHeight + 0.25 * parent.height left: parent.left - leftMargin: (parent.width - emailField.width) / 2 + leftMargin: (parent.width - displayNameField.width) / 2 } Item { @@ -140,9 +144,9 @@ Item { width: parent.width height: loginErrorMessageTextMetrics.height anchors { - bottom: emailField.top; + bottom: displayNameField.top; bottomMargin: hifi.dimensions.contentSpacing.y; - left: emailField.left; + left: displayNameField.left; } TextMetrics { id: loginErrorMessageTextMetrics @@ -163,7 +167,7 @@ Item { } HifiControlsUit.TextField { - id: emailField + id: displayNameField width: root.bannerWidth height: linkAccountBody.textFieldHeight font.pixelSize: linkAccountBody.textFieldFontSize @@ -172,6 +176,45 @@ Item { top: parent.top topMargin: errorContainer.height } + placeholderText: "Display Name (optional)" + activeFocusOnPress: true + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + emailField.focus = true; + break; + case Qt.Key_Backtab: + event.accepted = true; + passwordField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + if (keepMeLoggedInCheckbox.checked) { + Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + } + linkAccountBody.login(); + break; + } + } + onFocusChanged: { + root.text = ""; + if (focus) { + root.isPassword = false; + } + } + } + HifiControlsUit.TextField { + id: emailField + width: root.bannerWidth + height: linkAccountBody.textFieldHeight + font.pixelSize: linkAccountBody.textFieldFontSize + styleRenderType: Text.QtRendering + anchors { + top: displayNameField.bottom + topMargin: 1.5 * hifi.dimensions.contentSpacing.y + } placeholderText: "Username or Email" activeFocusOnPress: true Keys.onPressed: { @@ -182,7 +225,7 @@ Item { break; case Qt.Key_Backtab: event.accepted = true; - passwordField.focus = true; + displayNameField.focus = true; break; case Qt.Key_Enter: case Qt.Key_Return: @@ -257,6 +300,9 @@ Item { Keys.onPressed: { switch (event.key) { case Qt.Key_Tab: + event.accepted = true; + displayNameField.focus = true; + break; case Qt.Key_Backtab: event.accepted = true; emailField.focus = true; @@ -350,7 +396,7 @@ Item { anchors { top: loginButton.bottom topMargin: hifi.dimensions.contentSpacing.y - left: emailField.left + left: displayNameField.left } font.family: linkAccountBody.fontFamily font.pixelSize: linkAccountBody.textFieldFontSize @@ -381,13 +427,13 @@ Item { } HifiControlsUit.Button { id: continueButton; - width: emailField.width; + width: displayNameField.width; height: d.minHeightButton color: hifi.buttons.none; anchors { top: cantAccessText.bottom topMargin: hifi.dimensions.contentSpacing.y - left: emailField.left + left: displayNameField.left } text: qsTr("CONTINUE WITH STEAM") fontFamily: linkAccountBody.fontFamily @@ -420,7 +466,8 @@ Item { } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, - "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus }); + "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus, + "displayName":displayNameField.text}); } Component.onCompleted: { if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { @@ -535,7 +582,7 @@ Item { onFocusEnabled: { if (!linkAccountBody.lostFocus) { Qt.callLater(function() { - emailField.forceActiveFocus(); + displayNameField.forceActiveFocus(); }); } } @@ -543,6 +590,7 @@ Item { linkAccountBody.lostFocus = !root.isTablet && !root.isOverlay; if (linkAccountBody.lostFocus) { Qt.callLater(function() { + displayNameField.focus = false; emailField.focus = false; passwordField.focus = false; }); @@ -558,7 +606,7 @@ Item { d.resize(); init(); Qt.callLater(function() { - emailField.forceActiveFocus(); + displayNameField.forceActiveFocus(); }); } diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 583f00583b..a0029dc40b 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -31,6 +31,7 @@ Item { property bool linkSteam: linkSteam property bool linkOculus: linkOculus property bool createOculus: createOculus + property string displayName: "" readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -161,6 +162,7 @@ Item { } } + MyAvatar.displayName = displayName; successTimer.start(); } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 9e4fdffaee..f18fb06dc3 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -238,7 +238,7 @@ Item { } StatText { visible: root.expanded; - text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " + + text: "Audio In Audio: " + root.audioInboundPPS + " pps, " + "Silent: " + root.audioSilentInboundPPS + " pps"; } StatText { diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 3340226761..d58bcd2eba 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -49,4 +49,6 @@ Item { Component.onCompleted: { load(root.url, root.scriptUrl); } + + signal sendToScript(var message); } diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 997407885b..cfc1121af9 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -777,9 +777,7 @@ Rectangle { hoverEnabled: true onClicked: { - popup.showBuyAvatars(function() { - emitSendToScript({'method' : 'navigate', 'url' : 'hifi://BodyMart'}) - }, function(link) { + popup.showBuyAvatars(null, function(link) { emitSendToScript({'method' : 'navigate', 'url' : link}) }); } diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 391e4fab37..15a20b491a 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -245,9 +245,7 @@ Rectangle { linkColor: hifi.colors.blueHighlight Layout.alignment: Qt.AlignVCenter onLinkActivated: { - popup.showGetWearables(function() { - emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'}) - }, function(link) { + popup.showGetWearables(null, function(link) { emitSendToScript({'method' : 'navigate', 'url' : link}) }); } diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml index 691fbedac3..ac09f14d9e 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -7,7 +7,7 @@ MessageBox { popup.onButton2Clicked = callback; popup.titleText = 'Specify Avatar URL' popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.
' + - '' + + '' + 'Learn to make a custom avatar by opening this link on your desktop.' + '' popup.inputText.visible = true; @@ -56,25 +56,12 @@ MessageBox { popup.inputText.forceActiveFocus(); } - property url getWearablesUrl: '../../../images/avatarapp/AvatarIsland.jpg' - function showGetWearables(callback, linkCallback) { - popup.button2text = 'AvatarIsland' - popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; + popup.dialogButtons.yesButton.visible = false; popup.button1text = 'CANCEL' popup.titleText = 'Get Wearables' popup.bodyText = 'Get wearables from Marketplace.' + '
' + - 'Wear wearable from Inventory.' + '
' + '
' + - 'Visit “AvatarIsland” to get wearables' - - popup.imageSource = getWearablesUrl; - popup.onButton2Clicked = function() { - popup.close(); - - if (callback) { - callback(); - } - } + 'Wear wearable from Inventory.' popup.onLinkClicked = function(link) { popup.close(); @@ -120,26 +107,13 @@ MessageBox { popup.open(); } - property url getAvatarsUrl: '../../../images/avatarapp/BodyMart.PNG' - function showBuyAvatars(callback, linkCallback) { - popup.button2text = 'BodyMart' - popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; + popup.dialogButtons.yesButton.visible = false; popup.button1text = 'CANCEL' popup.titleText = 'Get Avatars' popup.bodyText = 'Get avatars from Marketplace.' + '
' + - 'Wear avatars in Inventory.' + '
' + '
' + - 'Visit “BodyMart” to get free avatars.' - - popup.imageSource = getAvatarsUrl; - popup.onButton2Clicked = function() { - popup.close(); - - if (callback) { - callback(); - } - } + 'Wear avatars in Inventory.' popup.onLinkClicked = function(link) { popup.close(); diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 6727047eb0..5db784789e 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -332,7 +332,7 @@ Item { anchors.fill: stackView id: controllerPrefereneces objectName: "TabletControllerPreferences" - showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] + showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion", "Open Sound Control (OSC)"] categoryProperties: { "VR Movement" : { "User real-world height (meters)" : { "anchors.right" : "undefined" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a73281e569..1b22dbd329 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -185,6 +185,7 @@ #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" #include "scripting/DesktopScriptingInterface.h" +#include "scripting/ScreenshareScriptingInterface.h" #include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" @@ -664,10 +665,17 @@ private: * * * CameraFirstPersonnumbernumberThe camera is in first-person mode. - * + * Legacy first person camera mode. + * CameraFirstPersonLookAtnumbernumberThe camera is in first-person mode. + * Default first person camera mode. * CameraThirdPersonnumbernumberThe camera is in third-person mode. - * - * CameraFSMnumbernumberThe camera is in full screen mirror mode. + * Legacy third person camera mode. + * CameraLookAtnumbernumberThe camera is in third-person mode. + * Default third person camera mode. + * CameraFSMnumbernumberThe camera is in full screen mirror mode. + * Legacy "look at myself" behavior. + * CameraSelfienumbernumberThe camera is in selfie mode. + * Default "look at myself" camera mode. * CameraIndependentnumbernumberThe camera is in independent mode. * CameraEntitynumbernumberThe camera is in entity mode. * InHMDnumbernumberThe user is in HMD mode. @@ -939,6 +947,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); PlatformHelper::setup(); QObject::connect(PlatformHelper::instance(), &PlatformHelper::systemWillWake, [] { @@ -2908,6 +2917,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::get()->cleanup(); @@ -3401,6 +3411,10 @@ void Application::initializeUi() { setIsInterstitialMode(true); + +#if defined(DISABLE_QML) && defined(Q_OS_LINUX) + resumeAfterLoginDialogActionTaken(); +#endif } @@ -3439,7 +3453,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("Users", DependencyManager::get().data()); surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get().data()); - + surfaceContext->setContextProperty("Screenshare", DependencyManager::get().data()); surfaceContext->setContextProperty("Camera", &_myCamera); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) @@ -3545,6 +3559,7 @@ void Application::userKickConfirmation(const QUuid& nodeID) { } void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) { + surfaceContext->setContextProperty("Screenshare", DependencyManager::get().data()); surfaceContext->setContextProperty("Users", DependencyManager::get().data()); surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get().data()); @@ -4880,6 +4895,9 @@ void Application::touchEndEvent(QTouchEvent* event) { } void Application::touchGestureEvent(QGestureEvent* event) { + if (_keyboardMouseDevice->isActive()) { + _keyboardMouseDevice->touchGestureEvent(event); + } if (_touchscreenDevice && _touchscreenDevice->isActive()) { _touchscreenDevice->touchGestureEvent(event); } @@ -5670,6 +5688,7 @@ void Application::resumeAfterLoginDialogActionTaken() { return; } +#if !defined(DISABLE_QML) if (!isHMDMode() && getDesktopTabletBecomesToolbarSetting()) { auto toolbar = DependencyManager::get()->getToolbar("com.highfidelity.interface.toolbar.system"); toolbar->writeProperty("visible", true); @@ -5679,6 +5698,7 @@ void Application::resumeAfterLoginDialogActionTaken() { } updateSystemTabletMode(); +#endif { auto userInputMapper = DependencyManager::get(); @@ -5829,12 +5849,7 @@ void Application::centerUI() { void Application::cycleCamera() { auto menu = Menu::getInstance(); - if (menu->isOptionChecked(MenuOption::FullscreenMirror)) { - - menu->setIsOptionChecked(MenuOption::FullscreenMirror, false); - menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, true); - - } else if (menu->isOptionChecked(MenuOption::FirstPersonLookAt)) { + if (menu->isOptionChecked(MenuOption::FirstPersonLookAt)) { menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, false); menu->setIsOptionChecked(MenuOption::LookAtCamera, true); @@ -5842,12 +5857,16 @@ void Application::cycleCamera() { } else if (menu->isOptionChecked(MenuOption::LookAtCamera)) { menu->setIsOptionChecked(MenuOption::LookAtCamera, false); - menu->setIsOptionChecked(MenuOption::SelfieCamera, true); + if (menu->getActionForOption(MenuOption::SelfieCamera)->isVisible()) { + menu->setIsOptionChecked(MenuOption::SelfieCamera, true); + } else { + menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, true); + } } else if (menu->isOptionChecked(MenuOption::SelfieCamera)) { menu->setIsOptionChecked(MenuOption::SelfieCamera, false); - menu->setIsOptionChecked(MenuOption::FullscreenMirror, true); + menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, true); } cameraMenuChanged(); // handle the menu change @@ -6273,7 +6292,7 @@ void Application::update(float deltaTime) { 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)); - if (deltaTime > FLT_EPSILON) { + if (deltaTime > FLT_EPSILON && userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z) == 0.0f) { myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH)); myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW)); myAvatar->setDriveKey(MyAvatar::DELTA_PITCH, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_PITCH)); @@ -7329,6 +7348,7 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Camera", &_myCamera); + scriptEngine->registerGlobalObject("Screenshare", DependencyManager::get().data()); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) scriptEngine->registerGlobalObject("SpeechRecognizer", DependencyManager::get().data()); diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 6f6a93ff19..9204cd7514 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -154,7 +154,7 @@ void AvatarBookmarks::deleteBookmark() { void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto currentAvatarEntities = myAvatar->getAvatarEntityData(); + auto currentAvatarEntities = myAvatar->getAvatarEntityDataNonDefault(); std::set newAvatarEntities; // Update or add all the new avatar entities @@ -187,7 +187,7 @@ void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { * @property {number} avatarScale - The target scale of the avatar. * @property {Array>} [avatarEntites] - The avatar entities included with the * bookmark. - * @property {MyAvatar.AttachmentData[]} [attachments] - The attachments included with the bookmark. + * @property {AttachmentData[]} [attachments] - The attachments included with the bookmark. *

Deprecated: Use avatar entities instead. */ @@ -296,7 +296,7 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { if (entityTree) { QScriptEngine scriptEngine; - auto avatarEntities = myAvatar->getAvatarEntityData(); + auto avatarEntities = myAvatar->getAvatarEntityDataNonDefault(); for (auto entityID : avatarEntities.keys()) { auto entity = entityTree->findEntityByID(entityID); if (!entity || !isWearableEntity(entity)) { diff --git a/interface/src/InterfaceParentFinder.cpp b/interface/src/InterfaceParentFinder.cpp index 33328f54cc..0f1c8876a9 100644 --- a/interface/src/InterfaceParentFinder.cpp +++ b/interface/src/InterfaceParentFinder.cpp @@ -45,10 +45,6 @@ SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& s success = true; return parent; } - if (parentID == AVATAR_SELF_ID) { - success = true; - return avatarManager->getMyAvatar(); - } success = false; return parent; diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 4708deb61b..16b0529b05 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -66,16 +66,62 @@ class AABox; /**jsdoc - * The LOD class manages your Level of Detail functions within Interface. + * The LODManager API manages the Level of Detail displayed in Interface. If the LOD is being automatically + * adjusted, the LOD is decreased if the measured frame rate is lower than the target FPS, and increased if the measured frame + * rate is greater than the target FPS. + * * @namespace LODManager * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {number} presentTime Read-only. - * @property {number} engineRunTime Read-only. - * @property {number} gpuTime Read-only. + * @property {LODManager.WorldDetailQuality} worldDetailQuality - The quality of the rendered world detail. + *

Setting this value updates the current desktop or HMD target LOD FPS.

+ * @property {number} lodQualityLevel - Not used. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} automaticLODAdjust - true to automatically adjust the LOD, false to manually + * adjust it. + * + * @property {number} engineRunTime - The time spent in the "render" thread to produce the most recent frame, in ms. + * Read-only. + * @property {number} batchTime - The time spent in the "present" thread processing the batches of the most recent frame, in ms. + * Read-only. + * @property {number} presentTime - The time spent in the "present" thread between the last buffer swap, i.e., the total time + * to submit the most recent frame, in ms. + * Read-only. + * @property {number} gpuTime - The time spent in the GPU executing the most recent frame, in ms. + * Read-only. + * + * @property {number} nowRenderTime - The current, instantaneous time spend to produce frames, in ms. This is the worst of + * engineRunTime, batchTime, presentTime, and gpuTime. + * Read-only. + * @property {number} nowRenderFPS - The current, instantaneous frame rate, in Hz. + * Read-only. + * + * @property {number} smoothScale - The amount of smoothing applied to calculate smoothRenderTime and + * smoothRenderFPS. + * @property {number} smoothRenderTime - The average time spend to produce frames, in ms. + * Read-only. + * @property {number} smoothRenderFPS - The average frame rate, in Hz. + * Read-only. + * + * @property {number} lodTargetFPS - The target LOD FPS per the current desktop or HMD display mode, capped by the target + * refresh rate set by the {@link Performance} API. + * Read-only. + + * @property {number} lodAngleDeg - The minimum angular dimension (relative to the camera position) of an entity in order for + * it to be rendered, in degrees. The angular dimension is calculated as a sphere of radius half the diagonal of the + * entity's AA box. + * + * @property {number} pidKp - Not used. + * @property {number} pidKi - Not used. + * @property {number} pidKd - Not used. + * @property {number} pidKv - Not used. + * @property {number} pidOp - Not used. Read-only. + * @property {number} pidOi - Not used. Read-only. + * @property {number} pidOd - Not used. Read-only. + * @property {number} pidO - Not used. Read-only. */ class LODManager : public QObject, public Dependency { Q_OBJECT @@ -117,83 +163,96 @@ class LODManager : public QObject, public Dependency { public: /**jsdoc + * Sets whether the LOD should be automatically adjusted. * @function LODManager.setAutomaticLODAdjust - * @param {boolean} value + * @param {boolean} value - true to automatically adjust the LOD, false to manually adjust it. */ Q_INVOKABLE void setAutomaticLODAdjust(bool value); /**jsdoc + * Gets whether the LOD is being automatically adjusted. * @function LODManager.getAutomaticLODAdjust - * @returns {boolean} + * @returns {boolean} true if the LOD is being automatically adjusted, false if it is being + * manually adjusted. */ Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } /**jsdoc + * Sets the target desktop LOD FPS. * @function LODManager.setDesktopLODTargetFPS - * @param {number} value + * @param {number} value - The target desktop LOD FPS, in Hz. */ Q_INVOKABLE void setDesktopLODTargetFPS(float value); /**jsdoc + * Gets the target desktop LOD FPS. * @function LODManager.getDesktopLODTargetFPS - * @returns {number} + * @returns {number} The target desktop LOD FPS, in Hz. */ Q_INVOKABLE float getDesktopLODTargetFPS() const; /**jsdoc + * Sets the target HMD LOD FPS. * @function LODManager.setHMDLODTargetFPS - * @param {number} value + * @param {number} value - The target HMD LOD FPS, in Hz. */ Q_INVOKABLE void setHMDLODTargetFPS(float value); /**jsdoc + * Gets the target HMD LOD FPS. + * The target FPS in HMD mode. The LOD is adjusted to ... * @function LODManager.getHMDLODTargetFPS - * @returns {number} + * @returns {number} The target HMD LOD FPS, in Hz. */ Q_INVOKABLE float getHMDLODTargetFPS() const; // User Tweakable LOD Items + /**jsdoc + * Gets a text description of the current level of detail rendered. * @function LODManager.getLODFeedbackText - * @returns {string} + * @returns {string} A text description of the current level of detail rendered. + * @example Report the current level of detail rendered. + * print("You can currently see: " + LODManager.getLODFeedbackText()); */ Q_INVOKABLE QString getLODFeedbackText(); /**jsdoc * @function LODManager.setOctreeSizeScale - * @param {number} sizeScale - * @deprecated This function is deprecated and will be removed. Use the {@link LODManager.lodAngleDeg} property instead. + * @param {number} sizeScale - The octree size scale. + * @deprecated This function is deprecated and will be removed. Use the lodAngleDeg property instead. */ Q_INVOKABLE void setOctreeSizeScale(float sizeScale); /**jsdoc * @function LODManager.getOctreeSizeScale - * @returns {number} - * @deprecated This function is deprecated and will be removed. Use the {@link LODManager.lodAngleDeg} property instead. + * @returns {number} The octree size scale. + * @deprecated This function is deprecated and will be removed. Use the lodAngleDeg property instead. */ Q_INVOKABLE float getOctreeSizeScale() const; /**jsdoc * @function LODManager.setBoundaryLevelAdjust - * @param {number} boundaryLevelAdjust + * @param {number} boundaryLevelAdjust - The boundary level adjust factor. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust); /**jsdoc * @function LODManager.getBoundaryLevelAdjust - * @returns {number} + * @returns {number} The boundary level adjust factor. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } /**jsdoc - * @function LODManager.getLODTargetFPS - * @returns {number} - */ + * The target LOD FPS per the current desktop or HMD display mode, capped by the target refresh rate. + * @function LODManager.getLODTargetFPS + * @returns {number} The target LOD FPS, in Hz. + */ Q_INVOKABLE float getLODTargetFPS() const; @@ -249,19 +308,41 @@ public: signals: /**jsdoc + * Not triggered. * @function LODManager.LODIncreased * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void LODIncreased(); /**jsdoc + * Not triggered. * @function LODManager.LODDecreased * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void LODDecreased(); + /**jsdoc + * Triggered when whether or not the LOD is being automatically adjusted changes. + * @function LODManager.autoLODChanged + * @returns {Signal} + */ void autoLODChanged(); + + /**jsdoc + * Triggered when the lodQualityLevel property value changes. + * @function LODManager.lodQualityLevelChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ void lodQualityLevelChanged(); + + /**jsdoc + * Triggered when the world detail quality changes. + * @function LODManager.worldDetailQualityChanged + * @returns {Signal} + */ void worldDetailQualityChanged(); private: diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index fbb69842bd..b5cacd662b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include "Application.h" #include "AccountManager.h" @@ -595,8 +597,20 @@ Menu::Menu() { QString("hifi/tablet/TabletNetworkingPreferences.qml"), "NetworkingPreferencesDialog"); }); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); - addActionToQMenuAndActionHash(networkMenu, MenuOption::ClearDiskCache, 0, - DependencyManager::get().data(), SLOT(clearCache())); + + action = addActionToQMenuAndActionHash(networkMenu, MenuOption::ClearDiskCaches); + connect(action, &QAction::triggered, [] { + // The following caches are cleared immediately + DependencyManager::get()->clearCache(); +#ifndef Q_OS_ANDROID + FileTypeProfile::clearCache(); + HFWebEngineProfile::clearCache(); +#endif + + // Clear the KTX cache on the next restart. It can't be cleared immediately because its files might be in use. + Setting::Handle(KTXCache::SETTING_VERSION_NAME, KTXCache::INVALID_VERSION).set(KTXCache::INVALID_VERSION); + }); + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, 0, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 1d6c010a05..3fcb261705 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -56,7 +56,7 @@ namespace MenuOption { const QString CalibrateCamera = "Calibrate Camera"; const QString CenterPlayerInView = "Center Player In View"; const QString Chat = "Chat..."; - const QString ClearDiskCache = "Clear Disk Cache"; + const QString ClearDiskCaches = "Clear Disk Caches (requires restart)"; const QString Collisions = "Collisions"; const QString Connexion = "Activate 3D Connexion Devices"; const QString Console = "Console..."; @@ -118,7 +118,6 @@ namespace MenuOption { const QString FixGaze = "Fix Gaze (no saccade)"; const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; - const QString FullscreenMirror = "Mirror"; const QString Help = "Help..."; const QString HomeLocation = "Home "; const QString IncreaseAvatarSize = "Increase Avatar Size"; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index d67341990d..bf7fd26b08 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -80,7 +80,7 @@ QVariantHash ModelPropertiesDialog::getMapping() const { // update the joint indices QVariantHash jointIndices; - for (int i = 0; i < _hfmModel.joints.size(); i++) { + for (size_t i = 0; i < _hfmModel.joints.size(); i++) { jointIndices.insert(_hfmModel.joints.at(i).name, QString::number(i)); } mapping.insert(JOINT_INDEX_FIELD, jointIndices); diff --git a/interface/src/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp index da6c5cbd37..bb4ec42e41 100644 --- a/interface/src/RefreshRateManager.cpp +++ b/interface/src/RefreshRateManager.cpp @@ -17,12 +17,58 @@ static const int VR_TARGET_RATE = 90; +/**jsdoc + *

Refresh rate profile.

+ * + * + * + * + * + * + * + * + *
ValueDescription
"Eco"Low refresh rate, which is reduced when Interface doesn't have focus or is + * minimized.
"Interactive"Medium refresh rate, which is reduced when Interface doesn't have focus or is + * minimized.
"Realtime"High refresh rate, even when Interface doesn't have focus or is minimized. + *
+ * @typedef {string} RefreshRateProfileName + */ static const std::array REFRESH_RATE_PROFILE_TO_STRING = { { "Eco", "Interactive", "Realtime" } }; +/**jsdoc + *

Interface states that affect the refresh rate.

+ * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"FocusActive"Interface has focus and the user is active or is in VR.
"FocusInactive"Interface has focus and the user is inactive.
"Unfocus"Interface doesn't have focus.
"Minimized"Interface is minimized.
"StartUp"Interface is starting up.
"ShutDown"Interface is shutting down.
+ * @typedef {string} RefreshRateRegimeName + */ static const std::array REFRESH_RATE_REGIME_TO_STRING = { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } }; +/**jsdoc + *

User experience (UX) modes.

+ * + * + * + * + * + * + * + * + *
ValueDescription
"Desktop"Desktop user experience.
"VR"VR user experience.
+ * @typedef {string} UXModeName + */ static const std::array UX_MODE_TO_STRING = { { "Desktop", "VR" } }; @@ -42,7 +88,7 @@ static const std::array { { 30, 20, 10, 2, 30, 30 } }; static const std::array REALTIME_PROFILE = - { { 60, 60, 10, 2, 30, 30} }; + { { 60, 60, 60, 2, 30, 30} }; static const std::array, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILES = { { ECO_PROFILE, INTERACTIVE_PROFILE, REALTIME_PROFILE } }; diff --git a/interface/src/RefreshRateManager.h b/interface/src/RefreshRateManager.h index 567a515898..18fad392c9 100644 --- a/interface/src/RefreshRateManager.h +++ b/interface/src/RefreshRateManager.h @@ -31,6 +31,23 @@ public: }; static bool isValidRefreshRateProfile(RefreshRateProfile value) { return (value >= RefreshRateProfile::ECO && value <= RefreshRateProfile::REALTIME); } + /**jsdoc + *

Interface states that affect the refresh rate.

+ * + * + * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0FOCUS_ACTIVEInterface has focus and the user is active or is in VR.
1FOCUS_INACTIVEInterface has focus and the user is inactive.
2UNFOCUSInterface doesn't have focus.
3MINIMIZEDInterface is minimized.
4STARTUPInterface is starting up.
5SHUTDOWNInterface is shutting down.
+ * @typedef {number} RefreshRateRegime + */ enum RefreshRateRegime { FOCUS_ACTIVE = 0, FOCUS_INACTIVE, @@ -42,6 +59,19 @@ public: }; static bool isValidRefreshRateRegime(RefreshRateRegime value) { return (value >= RefreshRateRegime::FOCUS_ACTIVE && value <= RefreshRateRegime::SHUTDOWN); } + /**jsdoc + *

User experience (UX) modes.

+ * + * + * + * + * + * + * + * + *
ValueNameDescription
0DESKTOPDesktop user experience.
1VRVR use experience.
+ * @typedef {number} UXMode + */ enum UXMode { DESKTOP = 0, VR, diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index b0c82f1afc..3c56170825 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -79,7 +79,7 @@ void AvatarDoctor::startDiagnosing() { _missingTextureCount = 0; _unsupportedTextureCount = 0; - const auto resource = DependencyManager::get()->getGeometryResource(_avatarFSTFileUrl); + const auto resource = DependencyManager::get()->getModelResource(_avatarFSTFileUrl); resource->refresh(); const auto resourceLoaded = [this, resource](bool success) { @@ -99,12 +99,12 @@ void AvatarDoctor::startDiagnosing() { } // RIG - if (avatarModel.joints.isEmpty()) { + if (avatarModel.joints.empty()) { addError("Avatar has no rig.", "no-rig"); } else { auto jointNames = avatarModel.getJointNames(); - if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { + if (avatarModel.joints.size() > NETWORKED_JOINTS_LIMIT) { addError(tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), "maximum-bone-limit"); } // Avatar does not have Hips bone mapped @@ -297,7 +297,7 @@ void AvatarDoctor::startDiagnosing() { if (resource->isLoaded()) { resourceLoaded(!resource->isFailed()); } else { - connect(resource.data(), &GeometryResource::finished, this, resourceLoaded); + connect(resource.data(), &ModelResource::finished, this, resourceLoaded); } } else { addError("Model file cannot be opened", "missing-file"); diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index 1465a5defc..1e3c84e02f 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -53,7 +53,7 @@ private: int _materialMappingCount = 0; int _materialMappingLoadedCount = 0; - GeometryResource::Pointer _model; + ModelResource::Pointer _model; bool _isDiagnosing = false; }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 553033f394..32e725388c 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -543,26 +543,8 @@ void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities) for (auto entity : deadEntities) { QUuid entityOwnerID = entity->getOwningAvatarID(); AvatarSharedPointer avatar = getAvatarBySessionID(entityOwnerID); - const bool REQUIRES_REMOVAL_FROM_TREE = false; if (avatar) { - avatar->clearAvatarEntity(entity->getID(), REQUIRES_REMOVAL_FROM_TREE); - } - if (entityTree && entity->isMyAvatarEntity()) { - entityTree->withWriteLock([&] { - // We only need to delete the direct children (rather than the descendants) because - // when the child is deleted, it will take care of its own children. If the child - // is also an avatar-entity, we'll end up back here. If it's not, the entity-server - // will take care of it in the usual way. - entity->forEachChild([&](SpatiallyNestablePointer child) { - EntityItemPointer childEntity = std::dynamic_pointer_cast(child); - if (childEntity) { - entityTree->deleteEntity(childEntity->getID(), true, true); - if (avatar) { - avatar->clearAvatarEntity(childEntity->getID(), REQUIRES_REMOVAL_FROM_TREE); - } - } - }); - }); + avatar->clearAvatarEntity(entity->getID()); } } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 76575ab8ef..4736362fcb 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1518,7 +1518,8 @@ void MyAvatar::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteAr } void MyAvatar::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { - AvatarData::clearAvatarEntity(entityID, requiresRemovalFromTree); + // NOTE: the requiresRemovalFromTree argument is unused + AvatarData::clearAvatarEntity(entityID); _avatarEntitiesLock.withWriteLock([&] { _cachedAvatarEntityBlobsToDelete.push_back(entityID); }); @@ -1526,7 +1527,12 @@ void MyAvatar::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFrom void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties) const { properties.setEntityHostType(entity::HostType::AVATAR); - properties.setOwningAvatarID(getID()); + + // Note: we store AVATAR_SELF_ID in EntityItem::_owningAvatarID and we usually + // store the actual sessionUUID in EntityItemProperties::_owningAvatarID (for JS + // consumption, for example). However at this context we are preparing properties + // for outgoing packet, in which case we use AVATAR_SELF_ID. + properties.setOwningAvatarID(AVATAR_SELF_ID); // there's no entity-server to tell us we're the simulation owner, so always set the // simulationOwner to the owningAvatarID and a high priority. @@ -1581,20 +1587,20 @@ void MyAvatar::handleChangedAvatarEntityData() { // AvatarData::_packedAvatarEntityData via deeper logic. // move the lists to minimize lock time - std::vector cachedBlobsToDelete; - std::vector cachedBlobsToUpdate; - std::vector entitiesToDelete; - std::vector entitiesToAdd; - std::vector entitiesToUpdate; + std::vector cachedBlobsToDelete; + std::vector cachedBlobsToUpdate; + std::vector entitiesToDelete; + std::vector entitiesToAdd; + std::vector entitiesToUpdate; _avatarEntitiesLock.withWriteLock([&] { - cachedBlobsToDelete = std::move(_cachedAvatarEntityBlobsToDelete); - cachedBlobsToUpdate = std::move(_cachedAvatarEntityBlobsToAddOrUpdate); - entitiesToDelete = std::move(_entitiesToDelete); - entitiesToAdd = std::move(_entitiesToAdd); - entitiesToUpdate = std::move(_entitiesToUpdate); + cachedBlobsToDelete.swap(_cachedAvatarEntityBlobsToDelete); + cachedBlobsToUpdate.swap(_cachedAvatarEntityBlobsToAddOrUpdate); + entitiesToDelete.swap(_entitiesToDelete); + entitiesToAdd.swap(_entitiesToAdd); + entitiesToUpdate.swap(_entitiesToUpdate); }); - auto removeAllInstancesHelper = [] (const QUuid& id, std::vector& v) { + auto removeAllInstancesHelper = [] (const EntityItemID& id, std::vector& v) { uint32_t i = 0; while (i < v.size()) { if (id == v[i]) { @@ -1621,11 +1627,7 @@ void MyAvatar::handleChangedAvatarEntityData() { } // DELETE real entities - for (const auto& id : entitiesToDelete) { - entityTree->withWriteLock([&] { - entityTree->deleteEntity(id); - }); - } + entityTree->deleteEntitiesByID(entitiesToDelete); // ADD real entities EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); @@ -1696,7 +1698,7 @@ void MyAvatar::handleChangedAvatarEntityData() { _needToSaveAvatarEntitySettings = true; } // also remove from list of stale blobs to avoid failed entity lookup later - std::set::iterator blobItr = _staleCachedAvatarEntityBlobs.find(id); + std::set::iterator blobItr = _staleCachedAvatarEntityBlobs.find(id); if (blobItr != _staleCachedAvatarEntityBlobs.end()) { _staleCachedAvatarEntityBlobs.erase(blobItr); } @@ -1764,9 +1766,9 @@ bool MyAvatar::updateStaleAvatarEntityBlobs() const { return false; } - std::set staleBlobs = std::move(_staleCachedAvatarEntityBlobs); + std::set staleIDs = std::move(_staleCachedAvatarEntityBlobs); int32_t numFound = 0; - for (const auto& id : staleBlobs) { + for (const auto& id : staleIDs) { bool found = false; EntityItemProperties properties; entityTree->withReadLock([&] { @@ -1809,6 +1811,46 @@ void MyAvatar::prepareAvatarEntityDataForReload() { } AvatarEntityMap MyAvatar::getAvatarEntityData() const { + // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs + AvatarEntityMap data; + + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return data; + } + + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { + auto entity = entityTree->findEntityByID(entityID); + if (!entity) { + continue; + } + + EncodeBitstreamParams params; + auto desiredProperties = entity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + desiredProperties += PROP_LOCAL_VELOCITY; + desiredProperties += PROP_LOCAL_ANGULAR_VELOCITY; + desiredProperties += PROP_LOCAL_DIMENSIONS; + EntityItemProperties properties = entity->getProperties(desiredProperties); + + QByteArray blob; + { + std::lock_guard guard(_scriptEngineLock); + EntityItemProperties::propertiesToBlob(*_scriptEngine, getID(), properties, blob, true); + } + + data[entityID] = blob; + } + return data; +} + +AvatarEntityMap MyAvatar::getAvatarEntityDataNonDefault() const { // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs updateStaleAvatarEntityBlobs(); AvatarEntityMap result; @@ -1851,7 +1893,7 @@ void MyAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { ++constItr; } // find and erase deleted IDs from _cachedAvatarEntityBlobs - std::vector deletedIDs; + std::vector deletedIDs; AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.begin(); while (itr != _cachedAvatarEntityBlobs.end()) { QUuid id = itr.key(); @@ -2441,7 +2483,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (_fullAvatarModelName.isEmpty()) { // Store the FST file name into preferences - const auto& mapping = _skeletonModel->getGeometry()->getMapping(); + const auto& mapping = _skeletonModel->getNetworkModel()->getMapping(); if (mapping.value("name").isValid()) { _fullAvatarModelName = mapping.value("name").toString(); } @@ -2449,7 +2491,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); - _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); + _fstAnimGraphOverrideUrl = _skeletonModel->getNetworkModel()->getAnimGraphOverrideUrl(); initAnimGraph(); initFlowFromFST(); } @@ -2469,18 +2511,11 @@ bool isWearableEntity(const EntityItemPointer& entity) { void MyAvatar::removeWornAvatarEntity(const EntityItemID& entityID) { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { auto entity = entityTree->findEntityByID(entityID); if (entity && isWearableEntity(entity)) { - entityTree->withWriteLock([&entityID, &entityTree] { - // remove this entity first from the entity tree - entityTree->deleteEntity(entityID, true, true); - }); - - // remove the avatar entity from our internal list - // (but indicate it doesn't need to be pulled from the tree) - clearAvatarEntity(entityID, false); + treeRenderer->deleteEntity(entityID); + clearAvatarEntity(entityID); } } } @@ -2527,6 +2562,9 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { auto desiredProperties = entity->getEntityProperties(params); desiredProperties += PROP_LOCAL_POSITION; desiredProperties += PROP_LOCAL_ROTATION; + desiredProperties += PROP_LOCAL_VELOCITY; + desiredProperties += PROP_LOCAL_ANGULAR_VELOCITY; + desiredProperties += PROP_LOCAL_DIMENSIONS; QVariantMap avatarEntityData; avatarEntityData["id"] = entityID; EntityItemProperties entityProperties = entity->getProperties(desiredProperties); @@ -3098,7 +3136,7 @@ void MyAvatar::initAnimGraph() { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); #if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK) - graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json"); + graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation-optimized-ik.json"); #endif } @@ -3513,8 +3551,46 @@ void MyAvatar::updateOrientation(float deltaTime) { } setWorldOrientation(glm::slerp(getWorldOrientation(), faceRotation, blend)); } else if (isRotatingWhileSeated) { - float rotatingWhileSeatedYaw = -getDriveKey(TRANSLATE_X) * _yawSpeed * deltaTime; - setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, rotatingWhileSeatedYaw, 0.0f)))); + float direction = -getDriveKey(TRANSLATE_X); + float seatedTargetSpeed = direction * _yawSpeed * deltaTime; //deg/renderframe + + const float SEATED_ROTATION_ACCEL_SCALE = 3.5; + + float blend = deltaTime * SEATED_ROTATION_ACCEL_SCALE; + if (blend > 1.0f) { + blend = 1.0f; + } + + //init, accelerate or clamp rotation at target speed + if (fabsf(_seatedBodyYawDelta) > 0.0f) { + if (fabsf(_seatedBodyYawDelta) >= fabsf(seatedTargetSpeed)) { + _seatedBodyYawDelta = seatedTargetSpeed; + } else { + _seatedBodyYawDelta += blend * direction; + } + } else { + _seatedBodyYawDelta = blend * direction; + } + + setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, _seatedBodyYawDelta, 0.0f)))); + + } else if (_seatedBodyYawDelta != 0.0f) { + //decelerate from seated rotation + const float ROTATION_DECAY_TIMESCALE = 0.25f; + float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE; + if (attenuation < 0.0f) { + attenuation = 0.0f; + } + _seatedBodyYawDelta *= attenuation; + + float MINIMUM_ROTATION_RATE = 2.0f; + if (fabsf(_seatedBodyYawDelta) < MINIMUM_ROTATION_RATE * deltaTime) { + _seatedBodyYawDelta = 0.0f; + } + + setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, _seatedBodyYawDelta, 0.0f)))); + } else { + _seatedBodyYawDelta = 0.0f; } } @@ -3573,24 +3649,28 @@ void MyAvatar::updateOrientation(float deltaTime) { const float DEFAULT_REORIENT_ANGLE = 65.0f; const float FIRST_PERSON_REORIENT_ANGLE = 95.0f; - const float TRIGGER_REORIENT_ANGLE = 45.0f; + const float TRIGGER_REORIENT_ANGLE = 135.0f; const float FIRST_PERSON_TRIGGER_REORIENT_ANGLE = 65.0f; glm::vec3 ajustedYawVector = cameraYawVector; - float limitAngle = 0.0f; - float triggerAngle = -glm::sin(glm::radians(TRIGGER_REORIENT_ANGLE)); + float triggerAngle = glm::cos(glm::radians(TRIGGER_REORIENT_ANGLE)); + float limitAngle = triggerAngle; if (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT) { - limitAngle = glm::sin(glm::radians(90.0f - FIRST_PERSON_TRIGGER_REORIENT_ANGLE)); + limitAngle = glm::cos(glm::radians(FIRST_PERSON_TRIGGER_REORIENT_ANGLE)); triggerAngle = limitAngle; } float reorientAngle = mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT ? FIRST_PERSON_REORIENT_ANGLE : DEFAULT_REORIENT_ANGLE; + if (frontBackDot < 0.0f) { + ajustedYawVector = (leftRightDot < 0.0f ? -avatarVectorRight : avatarVectorRight); + } if (frontBackDot < limitAngle) { - if (frontBackDot < 0.0f) { - ajustedYawVector = (leftRightDot < 0.0f ? -avatarVectorRight : avatarVectorRight); - } + if (!isRotatingWhileSeated) { - if (frontBackDot < triggerAngle) { + if (frontBackDot < triggerAngle && _seatedBodyYawDelta == 0.0f) { _shouldTurnToFaceCamera = true; _firstPersonSteadyHeadTimer = 0.0f; + } else { + setWorldOrientation(previousOrientation); + _seatedBodyYawDelta = 0.0f; } } else { setWorldOrientation(previousOrientation); @@ -3934,6 +4014,10 @@ float MyAvatar::getGravity() { void MyAvatar::setSessionUUID(const QUuid& sessionUUID) { QUuid oldSessionID = getSessionUUID(); Avatar::setSessionUUID(sessionUUID); + bool sendPackets = !DependencyManager::get()->getSessionUUID().isNull(); + if (!sendPackets) { + return; + } QUuid newSessionID = getSessionUUID(); if (newSessionID != oldSessionID) { auto treeRenderer = DependencyManager::get(); @@ -3943,7 +4027,6 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) { _avatarEntitiesLock.withReadLock([&] { avatarEntityIDs = _packedAvatarEntityData.keys(); }); - bool sendPackets = !DependencyManager::get()->getSessionUUID().isNull(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); entityTree->withWriteLock([&] { for (const auto& entityID : avatarEntityIDs) { @@ -3951,11 +4034,9 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) { if (!entity) { continue; } - // update OwningAvatarID so entity can be identified as "ours" later - entity->setOwningAvatarID(newSessionID); // NOTE: each attached AvatarEntity already have the correct updated parentID // via magic in SpatiallyNestable, hence we check against newSessionID - if (sendPackets && entity->getParentID() == newSessionID) { + if (entity->getParentID() == newSessionID) { // but when we have a real session and the AvatarEntity is parented to MyAvatar // we need to update the "packedAvatarEntityData" sent to the avatar-mixer // because it contains a stale parentID somewhere deep inside @@ -6554,22 +6635,23 @@ void MyAvatar::updateEyesLookAtPosition(float deltaTime) { int rightEyeJointIndex = getJointIndex("RightEye"); bool eyesAreOverridden = getIsJointOverridden(leftEyeJointIndex) || getIsJointOverridden(rightEyeJointIndex); + const float DEFAULT_GAZE_DISTANCE = 20.0f; // meters if (eyesAreOverridden) { // A script has set the eye rotations, so use these to set lookAtSpot glm::quat leftEyeRotation = getAbsoluteJointRotationInObjectFrame(leftEyeJointIndex); glm::quat rightEyeRotation = getAbsoluteJointRotationInObjectFrame(rightEyeJointIndex); - glm::vec3 leftVec = getWorldOrientation() * leftEyeRotation * IDENTITY_FORWARD; - glm::vec3 rightVec = getWorldOrientation() * rightEyeRotation * IDENTITY_FORWARD; + glm::vec3 leftVec = getWorldOrientation() * leftEyeRotation * Vectors::UNIT_Z; + glm::vec3 rightVec = getWorldOrientation() * rightEyeRotation * Vectors::UNIT_Z; glm::vec3 leftEyePosition = myHead->getLeftEyePosition(); glm::vec3 rightEyePosition = myHead->getRightEyePosition(); float t1, t2; bool success = findClosestApproachOfLines(leftEyePosition, leftVec, rightEyePosition, rightVec, t1, t2); - if (success) { + if (success && t1 > 0 && t2 > 0) { glm::vec3 leftFocus = leftEyePosition + leftVec * t1; glm::vec3 rightFocus = rightEyePosition + rightVec * t2; lookAtSpot = (leftFocus + rightFocus) / 2.0f; // average } else { - lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f; + lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * DEFAULT_GAZE_DISTANCE; } } else if (_scriptControlsEyesLookAt) { if (_scriptEyesControlTimer < MAX_LOOK_AT_TIME_SCRIPT_CONTROL) { @@ -6583,19 +6665,18 @@ void MyAvatar::updateEyesLookAtPosition(float deltaTime) { controller::Pose rightEyePose = getControllerPoseInAvatarFrame(controller::Action::RIGHT_EYE); if (leftEyePose.isValid() && rightEyePose.isValid()) { // an eye tracker is in use, set lookAtSpot from this - glm::vec3 leftVec = getWorldOrientation() * leftEyePose.rotation * glm::vec3(0.0f, 0.0f, -1.0f); - glm::vec3 rightVec = getWorldOrientation() * rightEyePose.rotation * glm::vec3(0.0f, 0.0f, -1.0f); - + glm::vec3 leftVec = getWorldOrientation() * leftEyePose.rotation * Vectors::UNIT_Z; + glm::vec3 rightVec = getWorldOrientation() * rightEyePose.rotation * Vectors::UNIT_Z; glm::vec3 leftEyePosition = myHead->getLeftEyePosition(); glm::vec3 rightEyePosition = myHead->getRightEyePosition(); float t1, t2; bool success = findClosestApproachOfLines(leftEyePosition, leftVec, rightEyePosition, rightVec, t1, t2); - if (success) { + if (success && t1 > 0 && t2 > 0) { glm::vec3 leftFocus = leftEyePosition + leftVec * t1; glm::vec3 rightFocus = rightEyePosition + rightVec * t2; lookAtSpot = (leftFocus + rightFocus) / 2.0f; // average } else { - lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f; + lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * DEFAULT_GAZE_DISTANCE; } } else { // no script override, no eye tracker, so do procedural eye motion diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 95256500e1..2a83ab69c1 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -148,6 +148,22 @@ class MyAvatar : public Avatar { * size in the virtual world. Read-only. * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. * Read-only. + * @property {boolean} hasScriptedBlendshapes=false - true if blend shapes are controlled by scripted actions, + * otherwise false. Set this to true before using the {@link MyAvatar.setBlendshape} method, + * and set back to false after you no longer want scripted control over the blend shapes. + *

Note: This property will automatically be set to true if the controller system has + * valid facial blend shape actions.

+ * @property {boolean} hasProceduralBlinkFaceMovement=true - true if avatars blink automatically by animating + * facial blend shapes, false if automatic blinking is disabled. Set to false to fully control + * the blink facial blend shapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasProceduralEyeFaceMovement=true - true if the facial blend shapes for an avatar's eyes + * adjust automatically as the eyes move, false if this automatic movement is disabled. Set this property + * to true to prevent the iris from being obscured by the upper or lower lids. Set to false to + * fully control the eye blend shapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasAudioEnabledFaceMovement=true - true if the avatar's mouth blend shapes animate + * automatically based on detected microphone input, false if this automatic movement is disabled. Set + * this property to false to fully control the mouth facial blend shapes via the + * {@link MyAvatar.setBlendshape} method. * * @comment IMPORTANT: This group of properties is copied from Avatar.h; they should NOT be edited here. * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the @@ -270,9 +286,9 @@ class MyAvatar : public Avatar { * @property {number} isInSittingState - true if the user wearing the HMD is determined to be sitting * (avatar leaning is disabled, recentering is enabled), false if the user wearing the HMD is * determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). - * If userRecenterModel == 2 (i.e., auto) the property value automatically updates as the user sits + * If userRecenterModel == 2 (i.e., "auto") the property value automatically updates as the user sits * or stands, unless isSitStandStateLocked == true. Setting the property value overrides the current - * siting / standing state, which is updated when the user next sits or stands unless + * sitting / standing state, which is updated when the user next sits or stands unless * isSitStandStateLocked == true. * @property {boolean} isSitStandStateLocked - true to lock the avatar sitting/standing state, i.e., use this * to disable automatically changing state. @@ -305,10 +321,7 @@ class MyAvatar : public Avatar { * @borrows Avatar.setAttachmentsVariant as setAttachmentsVariant * @borrows Avatar.updateAvatarEntity as updateAvatarEntity * @borrows Avatar.clearAvatarEntity as clearAvatarEntity - * @borrows Avatar.hasScriptedBlendshapes as hasScriptedBlendshapes - * @borrows Avatar.hasProceduralBlinkFaceMovement as hasProceduralBlinkFaceMovement - * @borrows Avatar.hasProceduralEyeFaceMovement as hasProceduralEyeFaceMovement - * @borrows Avatar.hasAudioEnabledFaceMovement as hasAudioEnabledFaceMovement + * @borrows Avatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected * @borrows Avatar.setSkeletonModelURL as setSkeletonModelURL * @borrows Avatar.getAttachmentData as getAttachmentData * @borrows Avatar.setAttachmentData as setAttachmentData @@ -420,38 +433,72 @@ class MyAvatar : public Avatar { const bool DEFAULT_STRAFE_ENABLED = true; public: + /**jsdoc + * The DriveKeys API provides constant numeric values that represent different logical keys that drive your + * avatar and camera. + * + * @namespace DriveKeys + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {number} TRANSLATE_X - Move the user's avatar in the direction of its x-axis, if the camera isn't in + * independent or mirror modes. + * @property {number} TRANSLATE_Y - Move the user's avatar in the direction of its y-axis, if the camera isn't in + * independent or mirror modes. + * @property {number} TRANSLATE_Z - Move the user's avatar in the direction of its z-axis, if the camera isn't in + * independent or mirror modes. + * @property {number} YAW - Rotate the user's avatar about its y-axis at a rate proportional to the control value, if the + * camera isn't in independent or mirror modes. + * @property {number} STEP_TRANSLATE_X - No action. + * @property {number} STEP_TRANSLATE_Y - No action. + * @property {number} STEP_TRANSLATE_Z - No action. + * @property {number} STEP_YAW - Rotate the user's avatar about its y-axis in a step increment, if the camera isn't in + * independent or mirror modes. + * @property {number} PITCH - Rotate the user's avatar head and attached camera about its negative x-axis (i.e., positive + * values pitch down) at a rate proportional to the control value, if the camera isn't in HMD, independent, or mirror + * modes. + * @property {number} ZOOM - Zoom the camera in or out. + * @property {number} DELTA_YAW - Rotate the user's avatar about its y-axis by an amount proportional to the control value, + * if the camera isn't in independent or mirror modes. + * @property {number} DELTA_PITCH - Rotate the user's avatar head and attached camera about its negative x-axis (i.e., + * positive values pitch down) by an amount proportional to the control value, if the camera isn't in HMD, independent, + * or mirror modes. + */ + /**jsdoc *

Logical keys that drive your avatar and camera.

* * - * + * * * - * - * - * - * - * - * - * - * - * - * - * - * + * + * + * + * + * + * + * + * + * + * + * + * * *
ValueNameDescription
ValueDescription
0TRANSLATE_XMove the user's avatar in the direction of its x-axis, if the - * camera isn't in independent or mirror modes.
1TRANSLATE_YMove the user's avatar in the direction of its y-axis, if the - * camera isn't in independent or mirror modes.
2TRANSLATE_ZMove the user's avatar in the direction of its z-axis, if the - * camera isn't in independent or mirror modes
3YAWRotate the user's avatar about its y-axis at a rate proportional to the - * control value, if the camera isn't in independent or mirror modes.
4STEP_TRANSLATE_XNo action.
5STEP_TRANSLATE_YNo action.
6STEP_TRANSLATE_ZNo action.
7STEP_YAWRotate the user's avatar about its y-axis in a step increment, if - * the camera isn't in independent or mirror modes.
8PITCHRotate the user's avatar head and attached camera about its negative - * x-axis (i.e., positive values pitch down) at a rate proportional to the control value, if the camera isn't in HMD, - * independent, or mirror modes.
9ZOOMZoom the camera in or out.
10DELTA_YAWRotate the user's avatar about its y-axis by an amount proportional - * to the control value, if the camera isn't in independent or mirror modes.
11DELTA_PITCHRotate the user's avatar head and attached camera about its - * negative x-axis (i.e., positive values pitch down) by an amount proportional to the control value, if the camera - * isn't in HMD, independent, or mirror modes.
{@link DriveKeys|DriveKeys.TRANSLATE_X}Move the user's avatar in the direction of its + * x-axis, if the camera isn't in independent or mirror modes.
{@link DriveKeys|DriveKeys.TRANSLATE_Y}Move the user's avatar in the direction of its + * -axis, if the camera isn't in independent or mirror modes.
{@link DriveKeys|DriveKeys.TRANSLATE_Z}Move the user's avatar in the direction of its + * z-axis, if the camera isn't in independent or mirror modes.
{@link DriveKeys|DriveKeys.YAW}Rotate the user's avatar about its y-axis at a rate + * proportional to the control value, if the camera isn't in independent or mirror modes.
{@link DriveKeys|DriveKeys.STEP_TRANSLATE_X}No action.
{@link DriveKeys|DriveKeys.STEP_TRANSLATE_Y}No action.
{@link DriveKeys|DriveKeys.STEP_TRANSLATE_Z}No action.
{@link DriveKeys|DriveKeys.STEP_YAW}Rotate the user's avatar about its y-axis in a + * step increment, if the camera isn't in independent or mirror modes.
{@link DriveKeys|DriveKeys.PITCH}Rotate the user's avatar head and attached camera + * about its negative x-axis (i.e., positive values pitch down) at a rate proportional to the control value, if the + * camera isn't in HMD, independent, or mirror modes.
{@link DriveKeys|DriveKeys.ZOOM}Zoom the camera in or out.
{@link DriveKeys|DriveKeys.DELTA_YAW}Rotate the user's avatar about its y-axis by an + * amount proportional to the control value, if the camera isn't in independent or mirror modes.
{@link DriveKeys|DriveKeys.DELTA_PITCH}Rotate the user's avatar head and attached + * camera about its negative x-axis (i.e., positive values pitch down) by an amount proportional to the control + * value, if the camera isn't in HMD, independent, or mirror modes.
- * @typedef {number} MyAvatar.DriveKeys + * @typedef {number} DriveKey */ enum DriveKeys { TRANSLATE_X = 0, @@ -480,7 +527,7 @@ public: * 0ForceSitAssumes the user is seated in the real world. Disables avatar * leaning regardless of what the avatar is doing in the virtual world (i.e., avatar always recenters). * 1ForceStandAssumes the user is standing in the real world. Enables avatar - * leaning regardless of what the avatar is doing in the virtual world (i.e. avatar leans, then if leans too far it + * leaning regardless of what the avatar is doing in the virtual world (i.e., avatar leans, then if leans too far it * recenters). * 2AutoInterface detects when the user is standing or seated in the real world. * Avatar leaning is disabled when the user is sitting (i.e., avatar always recenters), and avatar leaning is enabled @@ -607,7 +654,7 @@ public: *

Note: When using pre-built animation data, it's critical that the joint orientation of the source animation and target * rig are equivalent, since the animation data applies absolute values onto the joints. If the orientations are different, * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see - * Avatar Standards.

+ * Avatar Standards.

* @function MyAvatar.overrideAnimation * @param {string} url - The URL to the animation file. Animation files may be in glTF or FBX format, but only need to * contain the avatar skeleton and animation data. glTF models may be in JSON or binary format (".gltf" or ".glb" URLs @@ -618,7 +665,7 @@ public: * @param {number} firstFrame - The frame to start the animation at. * @param {number} lastFrame - The frame to end the animation at. * @example Play a clapping animation on your avatar for three seconds. - * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx"; * MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreAnimation(); @@ -641,7 +688,7 @@ public: * @param {number} firstFrame - The frame to start the animation at. * @param {number} lastFrame - The frame to end the animation at. * @example Override left hand animation for three seconds. - * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx"; * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreHandAnimation(); @@ -658,7 +705,7 @@ public: * animation, this function has no effect.

* @function MyAvatar.restoreAnimation * @example Play a clapping animation on your avatar for three seconds. - * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx"; * MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreAnimation(); @@ -675,7 +722,7 @@ public: * @function MyAvatar.restoreHandAnimation * @param isLeft {boolean} Set to true if using the left hand * @example Override left hand animation for three seconds. - * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * var ANIM_URL = "https://apidocs.projectathena.dev/models/ClapHands_Standing.fbx"; * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreHandAnimation(); @@ -733,7 +780,7 @@ public: * hanging at its sides when it is not moving, the avatar will stand and clap its hands. Note that just as it did before, as soon as the avatar * starts to move, the animation will smoothly blend into the walk animation used by the "walkFwd" animation role. * // An animation of the avatar clapping its hands while standing. Restore default after 30s. - * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; + * var ANIM_URL = "https://apidocs.projectathena.dev/models/ClapHands_Standing.fbx"; * MyAvatar.overrideRoleAnimation("idleStand", ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreRoleAnimation(); @@ -1003,7 +1050,7 @@ public: /**jsdoc * Gets the value of a drive key, regardless of whether it is disabled. * @function MyAvatar.getRawDriveKey - * @param {MyAvatar.DriveKeys} key - The drive key. + * @param {DriveKey} key - The drive key. * @returns {number} The value of the drive key. */ Q_INVOKABLE float getRawDriveKey(DriveKeys key) const; @@ -1013,11 +1060,10 @@ public: /**jsdoc * Disables the action associated with a drive key. * @function MyAvatar.disableDriveKey - * @param {MyAvatar.DriveKeys} key - The drive key to disable. + * @param {DriveKey} key - The drive key to disable. * @example Disable rotating your avatar using the keyboard for a couple of seconds. - * var YAW = 3; * print("Disable"); - * MyAvatar.disableDriveKey(YAW); + * MyAvatar.disableDriveKey(DriveKeys.YAW); * Script.setTimeout(function () { * print("Enable"); * MyAvatar.enableDriveKey(YAW); @@ -1029,14 +1075,14 @@ public: * Enables the action associated with a drive key. The action may have been disabled with * {@link MyAvatar.disableDriveKey|disableDriveKey}. * @function MyAvatar.enableDriveKey - * @param {MyAvatar.DriveKeys} key - The drive key to enable. + * @param {DriveKey} key - The drive key to enable. */ Q_INVOKABLE void enableDriveKey(DriveKeys key); /**jsdoc * Checks whether a drive key is disabled. * @function MyAvatar.isDriveKeyDisabled - * @param {DriveKeys} key - The drive key to check. + * @param {DriveKey} key - The drive key to check. * @returns {boolean} true if the drive key is disabled, false if it isn't. */ Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const; @@ -1137,7 +1183,7 @@ public: /**jsdoc * Gets information on the avatar your avatar is currently looking at. * @function MyAvatar.getTargetAvatar - * @returns {AvatarData} Information on the avatar being looked at. + * @returns {ScriptAvatar} Information on the avatar being looked at, null if no avatar is being looked at. */ // FIXME: The return type doesn't have a conversion to a script value so the function always returns undefined in // JavaScript. Note: When fixed, JSDoc is needed for the return type. @@ -1746,57 +1792,57 @@ public: void prepareAvatarEntityDataForReload(); /**jsdoc - * Turn the avatar's head until it faces the target point within the 90/-90 degree range. - * Once this method is called, API calls will have full control of the head for a limited time. - * If this method is not called for two seconds, the engine will regain control of the head. - * @function MyAvatar.setHeadLookAt - * @param {Vec3} lookAtTarget - The target point in world coordinates. - */ + * Turns the avatar's head until it faces the target point within a +90/-90 degree range. + * Once this method is called, API calls have full control of the head for a limited time. + * If this method is not called for 2 seconds, the engine regains control of the head. + * @function MyAvatar.setHeadLookAt + * @param {Vec3} lookAtTarget - The target point in world coordinates. + */ Q_INVOKABLE void setHeadLookAt(const glm::vec3& lookAtTarget); /**jsdoc - * Returns the current head look at target point in world coordinates. - * @function MyAvatar.getHeadLookAt - * @returns {Vec3} The head's look at target in world coordinates. - */ + * Gets the current target point of the head's look direction in world coordinates. + * @function MyAvatar.getHeadLookAt + * @returns {Vec3} The head's look-at target in world coordinates. + */ Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; } /**jsdoc - * When this function is called the engine regains control of the head immediately. - * @function MyAvatar.releaseHeadLookAtControl - */ + * Returns control of the avatar's head to the engine, and releases control from API calls. + * @function MyAvatar.releaseHeadLookAtControl + */ Q_INVOKABLE void releaseHeadLookAtControl(); /**jsdoc - * Force the avatar's eyes to look to the specified location. - * Once this method is called, API calls will have full control of the eyes for a limited time. - * If this method is not called for two seconds, the engine will regain control of the eyes. - * @function MyAvatar.setEyesLookAt - * @param {Vec3} lookAtTarget - The target point in world coordinates. - */ + * Forces the avatar's eyes to look at a specified location. Once this method is called, API calls + * full control of the eyes for a limited time. If this method is not called for 2 seconds, + * the engine regains control of the eyes. + * @function MyAvatar.setEyesLookAt + * @param {Vec3} lookAtTarget - The target point in world coordinates. + */ Q_INVOKABLE void setEyesLookAt(const glm::vec3& lookAtTarget); /**jsdoc - * Returns the current eyes look at target point in world coordinates. - * @function MyAvatar.getEyesLookAt - * @returns {Vec3} The eyes's look at target in world coordinates. - */ + * Gets the current target point of the eyes look direction in world coordinates. + * @function MyAvatar.getEyesLookAt + * @returns {Vec3} The eyes' look-at target in world coordinates. + */ Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget.get(); } /**jsdoc - * When this function is called the engine regains control of the eyes immediately. - * @function MyAvatar.releaseEyesLookAtControl - */ + * Returns control of the avatar's eyes to the engine, and releases control from API calls. + * @function MyAvatar.releaseEyesLookAtControl + */ Q_INVOKABLE void releaseEyesLookAtControl(); /**jsdoc - * Aims the pointing directional blending towards the provided target point. - * The "point" reaction should be triggered before using this method. - * MyAvatar.beginReaction("point") - * Returns true if the target point lays in front of the avatar. - * @function MyAvatar.setPointAt - * @param {Vec3} pointAtTarget - The target point in world coordinates. - */ + * Sets the point-at target for the "point" reaction that may be started with {@link MyAvatar.beginReaction}. + * The point-at target is set only if it is in front of the avatar. + *

Note: The "point" reaction should be started before calling this method.

+ * @function MyAvatar.setPointAt + * @param {Vec3} pointAtTarget - The target to point at, in world coordinates. + * @returns {boolean} true if the target point was set, false if it wasn't. + */ Q_INVOKABLE bool setPointAt(const glm::vec3& pointAtTarget); glm::quat getLookAtRotation() { return _lookAtYaw * _lookAtPitch; } @@ -1840,14 +1886,17 @@ public: /**jsdoc * Gets details of all avatar entities. + *

Warning: Potentially an expensive call. Do not use if possible.

* @function MyAvatar.getAvatarEntityData - * @returns {AvatarEntityMap} Details of the avatar entities. + * @returns {AvatarEntityMap} Details of all avatar entities. * @example Report the current avatar entities. * var avatarEntityData = MyAvatar.getAvatarEntityData(); * print("Avatar entities: " + JSON.stringify(avatarEntityData)); */ AvatarEntityMap getAvatarEntityData() const override; + AvatarEntityMap getAvatarEntityDataNonDefault() const override; + /**jsdoc * Sets all avatar entities from an object. * @function MyAvatar.setAvatarEntityData @@ -1877,7 +1926,7 @@ public: * @param {boolean} isActive - true if flow simulation is enabled on the joint, false if it isn't. * @param {boolean} isCollidable - true to enable collisions in the flow simulation, false to * disable. - * @param {Object} [physicsConfig>] - Physics configurations for particular entity + * @param {Object} [physicsConfig] - Physics configurations for particular entity * and avatar joints. * @param {Object} [collisionsConfig] - Collision configurations for particular * entity and avatar joints. @@ -1899,26 +1948,25 @@ public: Q_INVOKABLE QVariantList getCollidingFlowJoints(); /**jsdoc - * Starts a sitting action for the avatar + * Starts a sitting action for the avatar. * @function MyAvatar.beginSit - * @param {Vec3} position - The point in space where the avatar will sit. - * @param {Quat} rotation - Initial absolute orientation of the avatar once is seated. + * @param {Vec3} position - The position where the avatar should sit. + * @param {Quat} rotation - The initial orientation of the seated avatar. */ Q_INVOKABLE void beginSit(const glm::vec3& position, const glm::quat& rotation); /**jsdoc - * Ends a sitting action for the avatar + * Ends a sitting action for the avatar. * @function MyAvatar.endSit * @param {Vec3} position - The position of the avatar when standing up. - * @param {Quat} rotation - The absolute rotation of the avatar once the sitting action ends. + * @param {Quat} rotation - The orientation of the avatar when standing up. */ Q_INVOKABLE void endSit(const glm::vec3& position, const glm::quat& rotation); /**jsdoc - * Gets whether the avatar is in a seated pose. The seated pose is set by calling the - * MyAvatar::beginSit method. + * Gets whether the avatar is in a seated pose. The seated pose is set by calling {@link MyAvatar.beginSit}. * @function MyAvatar.isSeated - * @returns {boolean} true if the avatar is in a seated pose. + * @returns {boolean} true if the avatar is in a seated pose, false if it isn't. */ Q_INVOKABLE bool isSeated() { return _characterController.getSeated(); } @@ -2003,7 +2051,8 @@ public slots: float getGravity(); /**jsdoc - * Moves the avatar to a new position and/or orientation in the domain, while taking into account Avatar leg-length. + * Moves the avatar to a new position and/or orientation in the domain, with safe landing, while taking into account avatar + * leg length. * @function MyAvatar.goToFeetLocation * @param {Vec3} position - The new position for the avatar, in world coordinates. * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. @@ -2021,21 +2070,21 @@ public slots: * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. * @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar. * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from - * @param {boolean} [withSafeLanding=true] - Set to false MyAvatar::safeLanding will not be called (used when teleporting). * the new position and orientate the avatar to face the position. + * @param {boolean} [withSafeLanding=true] - Set to false to disable safe landing when teleporting. */ void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false, bool withSafeLanding = true); /**jsdoc - * Moves the avatar to a new position and (optional) orientation in the domain. + * Moves the avatar to a new position and (optional) orientation in the domain, with safe landing. * @function MyAvatar.goToLocation * @param {MyAvatar.GoToProperties} target - The goto target. */ void goToLocation(const QVariant& properties); /**jsdoc - * Moves the avatar to a new position and then enables collisions. + * Moves the avatar to a new position, with safe landing, and enables collisions. * @function MyAvatar.goToLocationAndEnableCollisions * @param {Vec3} position - The new position for the avatar, in world coordinates. */ @@ -2297,43 +2346,52 @@ public slots: virtual void setModelScale(float scale) override; /**jsdoc - * MyAvatar.getTriggerReactions - * Returns a list of reactions names that can be triggered using MyAvatar.triggerReaction(). - * @returns {string[]} Array of reaction names. + * Gets the list of reactions names that can be triggered using {@link MyAvatar.triggerReaction}. + *

See also: {@link MyAvatar.getBeginEndReactions}. + * @function MyAvatar.getTriggerReactions + * @returns {string[]} List of reaction names that can be triggered using {@link MyAvatar.triggerReaction}. + * @example List the available trigger reactions. + * print("Trigger reactions:", JSON.stringify(MyAvatar.getTriggerReactions())); */ QStringList getTriggerReactions() const; /**jsdoc - * MyAvatar.getBeginReactions - * Returns a list of reactions names that can be enabled using MyAvatar.beginReaction() and MyAvatar.endReaction(). - * @returns {string[]} Array of reaction names. + * Gets the list of reactions names that can be enabled using {@link MyAvatar.beginReaction} and + * {@link MyAvatar.endReaction}. + *

See also: {@link MyAvatar.getTriggerReactions}. + * @function MyAvatar.getBeginEndReactions + * @returns {string[]} List of reaction names that can be enabled using {@link MyAvatar.beginReaction} and + * {@link MyAvatar.endReaction}. + * @example List the available begin-end reactions. + * print("Begin-end reactions:", JSON.stringify(MyAvatar.getBeginEndReactions())); */ QStringList getBeginEndReactions() const; /**jsdoc - * MyAvatar.triggerReaction - * Plays the given reaction on the avatar, once the reaction is complete it will automatically complete. Only reaction names returned from MyAvatar.getTriggerReactions() are available. - * @param {string} reactionName - reaction name - * @returns {bool} false if the given reaction is not supported. + * Plays a reaction on the avatar. Once the reaction is complete it will stop playing. + *

Only reaction names returned by {@link MyAvatar.getTriggerReactions} are available.

+ * @function MyAvatar.triggerReaction + * @param {string} reactionName - The reaction to trigger. + * @returns {boolean} true if the reaction was played, false if the reaction is not supported. */ bool triggerReaction(QString reactionName); /**jsdoc - * MyAvatar.beginReaction - * Plays the given reaction on the avatar. The avatar will continue to play the reaction until stopped via the MyAvatar.endReaction() call or superseeded by another reaction. - * Only reaction names returned from MyAvatar.getBeginEndReactions() are available. - * NOTE: the caller is responsible for calling the corresponding MyAvatar.endReaction(), otherwise the avatar might become stuck in the reaction forever. - * @param {string} reactionName - reaction name - * @returns {bool} false if the given reaction is not supported. + * Starts playing a reaction on the avatar. The reaction will continue to play until stopped using + * {@link MyAvatar.endReaction} or superseded by another reaction. + *

Only reactions returned by {@link MyAvatar.getBeginEndReactions} are available.

+ * @function MyAvatar.beginReaction + * @param {string} reactionName - The reaction to start playing. + * @returns {boolean} true if the reaction was started, false if the reaction is not supported. */ bool beginReaction(QString reactionName); /**jsdoc - * MyAvatar.endReaction - * Used to stop a given reaction that was started via MyAvatar.beginReaction(). - * @param {string} reactionName - reaction name - * @returns {bool} false if the given reaction is not supported. + * Stops playing a reaction that was started using {@link MyAvatar.beginReaction}. + * @function MyAvatar.endReaction + * @param {string} reactionName - The reaction to stop playing. + * @returns {boolean} true if the reaction was stopped, false if the reaction is not supported. */ bool endReaction(QString reactionName); @@ -2970,19 +3028,19 @@ private: // correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and // setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to // real EntityItems. - std::vector _entitiesToDelete; - std::vector _entitiesToAdd; - std::vector _entitiesToUpdate; + std::vector _entitiesToDelete; + std::vector _entitiesToAdd; + std::vector _entitiesToUpdate; // // The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are // already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs // and eventually to settings. - std::vector _cachedAvatarEntityBlobsToDelete; - std::vector _cachedAvatarEntityBlobsToAddOrUpdate; - std::vector _cachedAvatarEntityBlobUpdatesToSkip; + std::vector _cachedAvatarEntityBlobsToDelete; + std::vector _cachedAvatarEntityBlobsToAddOrUpdate; + std::vector _cachedAvatarEntityBlobUpdatesToSkip; // // Also these lists for tracking delayed changes to blobs and Settings - mutable std::set _staleCachedAvatarEntityBlobs; + mutable std::set _staleCachedAvatarEntityBlobs; // // keep a ScriptEngine around so we don't have to instantiate on the fly (these are very slow to create/delete) mutable std::mutex _scriptEngineLock; diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 1b88a518c8..4b6f85de1c 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -134,11 +134,11 @@ void MyHead::simulate(float deltaTime) { userInputMapper->getActionStateValid(controller::Action::MOUTHSMILE_L) || userInputMapper->getActionStateValid(controller::Action::MOUTHSMILE_R); - bool eyesTracked = - userInputMapper->getPoseState(controller::Action::LEFT_EYE).valid && - userInputMapper->getPoseState(controller::Action::RIGHT_EYE).valid; - MyAvatar* myAvatar = static_cast(_owningAvatar); + bool eyesTracked = + myAvatar->getControllerPoseInSensorFrame(controller::Action::LEFT_EYE).valid && + myAvatar->getControllerPoseInSensorFrame(controller::Action::RIGHT_EYE).valid; + int leftEyeJointIndex = myAvatar->getJointIndex("LeftEye"); int rightEyeJointIndex = myAvatar->getJointIndex("RightEye"); bool eyeJointsOverridden = myAvatar->getIsJointOverridden(leftEyeJointIndex) || myAvatar->getIsJointOverridden(rightEyeJointIndex); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 50f6369dbe..aa6c074d08 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -561,9 +561,18 @@ void OtherAvatar::handleChangedAvatarEntityData() { _avatarEntitiesLock.withReadLock([&] { packedAvatarEntityData = _packedAvatarEntityData; }); - foreach (auto entityID, recentlyRemovedAvatarEntities) { - if (!packedAvatarEntityData.contains(entityID)) { - entityTree->deleteEntity(entityID, true, true); + if (!recentlyRemovedAvatarEntities.empty()) { + std::vector idsToDelete; + idsToDelete.reserve(recentlyRemovedAvatarEntities.size()); + foreach (auto entityID, recentlyRemovedAvatarEntities) { + if (!packedAvatarEntityData.contains(entityID)) { + idsToDelete.push_back(entityID); + } + } + if (!idsToDelete.empty()) { + bool force = true; + bool ignoreWarnings = true; + entityTree->deleteEntitiesByID(idsToDelete, force, ignoreWarnings); } } diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 2602bdb0a0..756c8fab7f 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -121,8 +121,9 @@ bool CollisionPick::isLoaded() const { bool CollisionPick::getShapeInfoReady(const CollisionRegion& pick) { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { - computeShapeInfo(pick, *_mathPick.shapeInfo, _cachedResource); - _mathPick.loaded = true; + // TODO: Model CollisionPick support + //computeShapeInfo(pick, *_mathPick.shapeInfo, _cachedResource); + //_mathPick.loaded = true; } else { _mathPick.loaded = false; } @@ -134,7 +135,7 @@ bool CollisionPick::getShapeInfoReady(const CollisionRegion& pick) { return _mathPick.loaded; } -void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { +void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { ShapeType type = shapeInfo.getType(); glm::vec3 dimensions = pick.transform.getScale(); QString modelURL = (resource ? resource->getURL().toString() : ""); @@ -147,241 +148,12 @@ void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, } } -void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { - // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo - // TODO: Move to some shared code area (in entities-renderer? model-networking?) - // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo - // to consolidate the code. - // We may also want to make computeShapeInfo always abstract away from the gpu model mesh, like it does here. - const uint32_t TRIANGLE_STRIDE = 3; - const uint32_t QUAD_STRIDE = 4; - - ShapeType type = shapeInfo.getType(); - glm::vec3 dimensions = pick.transform.getScale(); - if (type == SHAPE_TYPE_COMPOUND) { - // should never fall in here when collision model not fully loaded - // TODO: assert that all geometries exist and are loaded - //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); - const HFMModel& collisionModel = resource->getHFMModel(); - - ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); - pointCollection.clear(); - uint32_t i = 0; - - // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect - // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const HFMMesh& mesh, collisionModel.meshes) { - // each meshPart is a convex hull - foreach (const HFMMeshPart &meshPart, mesh.parts) { - pointCollection.push_back(QVector()); - ShapeInfo::PointList& pointsInPart = pointCollection[i]; - - // run through all the triangles and (uniquely) add each point to the hull - uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); - // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up - //assert(numIndices % TRIANGLE_STRIDE == 0); - numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { - glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; - glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; - glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; - if (!pointsInPart.contains(p0)) { - pointsInPart << p0; - } - if (!pointsInPart.contains(p1)) { - pointsInPart << p1; - } - if (!pointsInPart.contains(p2)) { - pointsInPart << p2; - } - } - - // run through all the quads and (uniquely) add each point to the hull - numIndices = (uint32_t)meshPart.quadIndices.size(); - // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up - //assert(numIndices % QUAD_STRIDE == 0); - numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { - glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; - glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; - glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; - glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; - if (!pointsInPart.contains(p0)) { - pointsInPart << p0; - } - if (!pointsInPart.contains(p1)) { - pointsInPart << p1; - } - if (!pointsInPart.contains(p2)) { - pointsInPart << p2; - } - if (!pointsInPart.contains(p3)) { - pointsInPart << p3; - } - } - - if (pointsInPart.size() == 0) { - qCDebug(scriptengine) << "Warning -- meshPart has no faces"; - pointCollection.pop_back(); - continue; - } - ++i; - } - } - - // We expect that the collision model will have the same units and will be displaced - // from its origin in the same way the visual model is. The visual model has - // been centered and probably scaled. We take the scaling and offset which were applied - // to the visual model and apply them to the collision model (without regard for the - // collision model's extents). - - glm::vec3 scaleToFit = dimensions / resource->getHFMModel().getUnscaledMeshExtents().size(); - // multiply each point by scale - for (int32_t i = 0; i < pointCollection.size(); i++) { - for (int32_t j = 0; j < pointCollection[i].size(); j++) { - // back compensate for registration so we can apply that offset to the shapeInfo later - pointCollection[i][j] = scaleToFit * pointCollection[i][j]; - } - } - shapeInfo.setParams(type, dimensions, resource->getURL().toString()); - } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { - const HFMModel& hfmModel = resource->getHFMModel(); - int numHFMMeshes = hfmModel.meshes.size(); - int totalNumVertices = 0; - for (int i = 0; i < numHFMMeshes; i++) { - const HFMMesh& mesh = hfmModel.meshes.at(i); - totalNumVertices += mesh.vertices.size(); - } - const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; - if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { - qWarning() << "model" << "has too many vertices" << totalNumVertices << "and will collide as a box."; - shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); - return; - } - - auto& meshes = resource->getHFMModel().meshes; - int32_t numMeshes = (int32_t)(meshes.size()); - - const int MAX_ALLOWED_MESH_COUNT = 1000; - if (numMeshes > MAX_ALLOWED_MESH_COUNT) { - // too many will cause the deadlock timer to throw... - shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); - return; - } - - ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); - pointCollection.clear(); - if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - pointCollection.resize(numMeshes); - } else { - pointCollection.resize(1); - } - - ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); - triangleIndices.clear(); - - Extents extents; - int32_t meshCount = 0; - int32_t pointListIndex = 0; - for (auto& mesh : meshes) { - if (!mesh.vertices.size()) { - continue; - } - QVector vertices = mesh.vertices; - - ShapeInfo::PointList& points = pointCollection[pointListIndex]; - - // reserve room - int32_t sizeToReserve = (int32_t)(vertices.count()); - if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - // a list of points for each mesh - pointListIndex++; - } else { - // only one list of points - sizeToReserve += (int32_t)points.size(); - } - points.reserve(sizeToReserve); - - // copy points - const glm::vec3* vertexItr = vertices.cbegin(); - while (vertexItr != vertices.cend()) { - glm::vec3 point = *vertexItr; - points.push_back(point); - extents.addPoint(point); - ++vertexItr; - } - - if (type == SHAPE_TYPE_STATIC_MESH) { - // copy into triangleIndices - size_t triangleIndicesCount = 0; - for (const HFMMeshPart& meshPart : mesh.parts) { - triangleIndicesCount += meshPart.triangleIndices.count(); - } - triangleIndices.reserve((int)triangleIndicesCount); - - for (const HFMMeshPart& meshPart : mesh.parts) { - const int* indexItr = meshPart.triangleIndices.cbegin(); - while (indexItr != meshPart.triangleIndices.cend()) { - triangleIndices.push_back(*indexItr); - ++indexItr; - } - } - } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - // for each mesh copy unique part indices, separated by special bogus (flag) index values - for (const HFMMeshPart& meshPart : mesh.parts) { - // collect unique list of indices for this part - std::set uniqueIndices; - auto numIndices = meshPart.triangleIndices.count(); - // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up - //assert(numIndices% TRIANGLE_STRIDE == 0); - numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - auto indexItr = meshPart.triangleIndices.cbegin(); - while (indexItr != meshPart.triangleIndices.cend()) { - uniqueIndices.insert(*indexItr); - ++indexItr; - } - - // store uniqueIndices in triangleIndices - triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); - for (auto index : uniqueIndices) { - triangleIndices.push_back(index); - } - // flag end of part - triangleIndices.push_back(END_OF_MESH_PART); - } - // flag end of mesh - triangleIndices.push_back(END_OF_MESH); - } - ++meshCount; - } - - // scale and shift - glm::vec3 extentsSize = extents.size(); - glm::vec3 scaleToFit = dimensions / extentsSize; - for (int32_t i = 0; i < 3; ++i) { - if (extentsSize[i] < 1.0e-6f) { - scaleToFit[i] = 1.0f; - } - } - for (auto points : pointCollection) { - for (int32_t i = 0; i < points.size(); ++i) { - points[i] = (points[i] * scaleToFit); - } - } - - shapeInfo.setParams(type, 0.5f * dimensions, resource->getURL().toString()); - } -} - CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, bool scaleWithParent, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) : Pick(collisionRegion, filter, maxDistance, enabled), _scaleWithParent(scaleWithParent), _physicsEngine(physicsEngine) { if (collisionRegion.shouldComputeShapeInfo()) { - _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); + _cachedResource = DependencyManager::get()->getCollisionModelResource(collisionRegion.modelURL); } _mathPick.loaded = isLoaded(); } diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 24317bf19a..617c7b1f00 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -63,14 +63,13 @@ protected: bool isLoaded() const; // Returns true if _mathPick.shapeInfo is valid. Otherwise, attempts to get the _mathPick ready for use. bool getShapeInfoReady(const CollisionRegion& pick); - void computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); - void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); void filterIntersections(std::vector& intersections) const; bool _scaleWithParent; PhysicsEnginePointer _physicsEngine; - QSharedPointer _cachedResource; + QSharedPointer _cachedResource; // Options for what information to get from collision results bool _includeNormals; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 3a7b67a8e9..1f940f761d 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -99,9 +99,11 @@ PickFilter getPickFilter(unsigned int filter) { * @property {Vec3} [dirOffset] - Synonym for direction. * @property {Quat} [orientation] - Alternative property for specifying direction. The value is applied to the * default direction value. - * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or {@link Picks.getPickScriptParameters}. A ray pick's type is {@link PickType.Ray}. - * @property {Vec3} baseScale - Returned from {@link Picks.getPickProperties} when the pick has a parent with varying scale (usually an avatar or an entity). - * Its value is the original scale of the parent at the moment the pick was created, and is used to scale the pointer which owns this pick, if any. + * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or + * {@link Picks.getPickScriptParameters}. A ray pick's type is {@link PickType.Ray}. + * @property {Vec3} baseScale - Returned from {@link Picks.getPickProperties} when the pick has a parent with varying scale + * (usually an avatar or an entity). Its value is the original scale of the parent at the moment the pick was created, and + * is used to scale the pointer which owns this pick, if any. */ std::shared_ptr PickScriptingInterface::buildRayPick(const QVariantMap& propMap) { #if defined (Q_OS_ANDROID) @@ -170,7 +172,8 @@ std::shared_ptr PickScriptingInterface::buildRayPick(const QVariantMa * means no maximum. * @property {Vec3} [tipOffset=0,0.095,0] - The position of the stylus tip relative to the hand position at default avatar * scale. - * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or {@link Picks.getPickScriptParameters}. A stylus pick's type is {@link PickType.Stylus}. + * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or + * {@link Picks.getPickScriptParameters}. A stylus pick's type is {@link PickType.Stylus}. */ std::shared_ptr PickScriptingInterface::buildStylusPick(const QVariantMap& propMap) { bilateral::Side side = bilateral::Side::Invalid; @@ -204,7 +207,8 @@ std::shared_ptr PickScriptingInterface::buildStylusPick(const QVarian return std::make_shared(side, filter, maxDistance, enabled, tipOffset); } -// NOTE: Laser pointer still uses scaleWithAvatar. Until scaleWithAvatar is also deprecated for pointers, scaleWithAvatar should not be removed from the pick API. +// NOTE: Laser pointer still uses scaleWithAvatar. Until scaleWithAvatar is also deprecated for pointers, scaleWithAvatar +// should not be removed from the pick API. /**jsdoc * The properties of a parabola pick. * @@ -245,9 +249,11 @@ std::shared_ptr PickScriptingInterface::buildStylusPick(const QVarian * with the avatar or other parent. * @property {boolean} [scaleWithAvatar=true] - Synonym for scalewithParent. *

Deprecated: This property is deprecated and will be removed.

- * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or {@link Picks.getPickScriptParameters}. A parabola pick's type is {@link PickType.Parabola}. - * @property {Vec3} baseScale - Returned from {@link Picks.getPickProperties} when the pick has a parent with varying scale (usually an avatar or an entity). - * Its value is the original scale of the parent at the moment the pick was created, and is used to rescale the pick, and/or the pointer which owns this pick, if any. + * @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or + * {@link Picks.getPickScriptParameters}. A parabola pick's type is {@link PickType.Parabola}. + * @property {Vec3} baseScale - Returned from {@link Picks.getPickProperties} when the pick has a parent with varying scale + * (usually an avatar or an entity). Its value is the original scale of the parent at the moment the pick was created, and + * is used to rescale the pick and the pointer which owns this pick, if any. */ std::shared_ptr PickScriptingInterface::buildParabolaPick(const QVariantMap& propMap) { bool enabled = false; diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index e26b91b9a2..58ed3326ec 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -52,6 +52,10 @@ * Read-only. *

Warning: Not yet implemented.

* + * @property {FilterFlags} PICK_BYPASS_IGNORE - Allows pick to intersect entities even when their + * ignorePickIntersection property value is true. For debug purposes. + * Read-only. + * * @property {IntersectionType} INTERSECTED_NONE - Intersected nothing. Read-only. * @property {IntersectionType} INTERSECTED_ENTITY - Intersected an entity. Read-only. * @property {IntersectionType} INTERSECTED_LOCAL_ENTITY - Intersected a local entity. Read-only. @@ -87,6 +91,8 @@ class PickScriptingInterface : public QObject, public Dependency { Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) + Q_PROPERTY(unsigned int PICK_BYPASS_IGNORE READ PICK_BYPASS_IGNORE CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ INTERSECTED_LOCAL_ENTITY CONSTANT) @@ -147,19 +153,20 @@ public: * Gets the current properties of the pick. * @function Picks.getPickProperties * @param {number} id - The ID of the pick. - * @returns {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} Properties of the pick, per the pick type. + * @returns {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} + * Properties of the pick, per the pick type. */ Q_INVOKABLE QVariantMap getPickProperties(unsigned int uid) const; /**jsdoc - * Gets the parameters that were passed in to {@link Picks.createPick} to create the pick, - * if the pick was created through a script. - * Note that these properties do not reflect the current state of the pick. - * See {@link Picks.getPickProperties}. - * @function Picks.getPickScriptParameters - * @param {number} id - The ID of the pick. - * @returns {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} User-provided properties, per the pick type. - */ + * Gets the parameters that were passed in to {@link Picks.createPick} to create the pick, if the pick was created through + * a script. Note that these properties do not reflect the current state of the pick. + * See {@link Picks.getPickProperties}. + * @function Picks.getPickScriptParameters + * @param {number} id - The ID of the pick. + * @returns {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} + * Script-provided properties, per the pick type. + */ Q_INVOKABLE QVariantMap getPickScriptParameters(unsigned int uid) const; /**jsdoc @@ -282,6 +289,8 @@ public: unsigned int getPerFrameTimeBudget() const; void setPerFrameTimeBudget(unsigned int numUsecs); + static constexpr unsigned int PICK_BYPASS_IGNORE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_BYPASS_IGNORE); } + public slots: /**jsdoc diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 3520aacbd0..a3aeb314e5 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -89,10 +89,13 @@ QVariantMap PointerScriptingInterface::getPointerScriptParameters(unsigned int u * @property {Pointers.StylusPointerModel} [model] - Override some or all of the default stylus model properties. * @property {boolean} [hover=false] - true if the pointer generates {@link Entities} hover events, * false if it doesn't. - * @property {PickType} pointerType - The type of pointer when getting these properties from {@link Pointers.getPointerProperties} or {@link Pointers.getPointerScriptParameters}. A stylus pointer's type is {@link PickType.Stylus}. - * @property {number} [pickID] - Returned from {@link Pointers.getPointerProperties}. The ID of the pick created alongside this pointer. + * @property {PickType} pointerType - The type of the stylus pointer returned from {@link Pointers.getPointerProperties} + * or {@link Pointers.getPointerScriptParameters}. A stylus pointer's type is {@link PickType(0)|PickType.Stylus}. + * @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from + * {@link Pointers.getPointerProperties}. * @see {@link Picks.StylusPickProperties} for additional properties from the underlying stylus pick. */ + /**jsdoc * The properties of a stylus pointer model. * @typedef {object} Pointers.StylusPointerModel @@ -208,8 +211,10 @@ std::shared_ptr PointerScriptingInterface::buildStylus(const QVariant& * false if it doesn't. * @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger * events on the entity or overlay currently intersected. - * @property {PickType} pointerType - The type of pointer when getting these properties from {@link Pointers.getPointerProperties} or {@link Pointers.getPointerScriptParameters}. A laser pointer's type is {@link PickType.Ray}. - * @property {number} [pickID] - Returned from {@link Pointers.getPointerProperties}. The ID of the pick created alongside this pointer. + * @property {PickType} pointerType - The type of pointer returned from {@link Pointers.getPointerProperties} or + * {@link Pointers.getPointerScriptParameters}. A laser pointer's type is {@link PickType(0)|PickType.Ray}. + * @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from + * {@link Pointers.getPointerProperties}. * @see {@link Picks.RayPickProperties} for additional properties from the underlying ray pick. */ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVariant& properties) { @@ -401,8 +406,10 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVar * false if it doesn't. * @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger * events on the entity or overlay currently intersected. - * @property {PickType} pointerType - The type of pointer when getting these properties from {@link Pointers.getPointerProperties} or {@link Pointers.getPointerScriptParameters}. A parabola pointer's type is {@link PickType.Parabola}. - * @property {number} [pickID] - Returned from {@link Pointers.getPointerProperties}. The ID of the pick created alongside this pointer. + * @property {PickType} pointerType - The type of pointer returned from {@link Pointers.getPointerProperties} or + * {@link Pointers.getPointerScriptParameters}. A parabola pointer's type is {@link PickType(0)|PickType.Parabola}. + * @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from + * {@link Pointers.getPointerProperties}. * @see {@link Picks.ParabolaPickProperties} for additional properties from the underlying parabola pick. */ std::shared_ptr PointerScriptingInterface::buildParabolaPointer(const QVariant& properties) { diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 2d2f3f6dae..555136e7a7 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -161,13 +161,14 @@ public: Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get()->removePointer(uid); } /**jsdoc - * Gets the parameters that were passed in to {@link Pointers.createPointer} to create the pointer, - * if the pointer was created through a script. - * Note that these properties do not reflect the current state of the pointer. - * See {@link Pointers.getPointerProperties}. + * Gets the parameters that were passed in to {@link Pointers.createPointer} to create the pointer when the pointer was + * created through a script. + *

Note: These properties do not reflect the current state of the pointer. To get the current state + * of the pointer, see {@link Pointers.getPointerProperties}. * @function Pointers.getPointerScriptParameters * @param {number} id - The ID of the pointer. - * @returns {Pointers.RayPointerProperties|Picks.ParabolaPointerProperties|Picks.StylusPointerProperties} User-provided properties, per the pointer type. + * @returns {Pointers.RayPointerProperties|Pointers.ParabolaPointerProperties|Pointers.StylusPointerProperties} + * Script-provided properties, per the pointer type. */ Q_INVOKABLE QVariantMap getPointerScriptParameters(unsigned int uid) const; diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index 3ad637d0e6..288137e166 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -46,6 +46,7 @@ class AccountServicesScriptingInterface : public QObject { * @hifi-avatar * * @namespace AccountServices + * * @property {string} username - The user name of the user logged in. If there is no user logged in, it is * "Unknown user". Read-only. * @property {boolean} loggedIn - true if the user is logged in, otherwise false. @@ -61,6 +62,86 @@ class AccountServicesScriptingInterface : public QObject { * — typically "https://metaverse.highfidelity.com". Read-only. */ + /**jsdoc + * The Account API provides functions that give information on user connectivity, visibility, and asset + * download progress. + * + * @deprecated This API is the same as the {@link AccountServices} API and will be removed. + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @namespace Account + * + * @property {string} username - The user name of the user logged in. If there is no user logged in, it is + * "Unknown user". Read-only. + * @property {boolean} loggedIn - true if the user is logged in, otherwise false. + * Read-only. + * @property {string} findableBy - The user's visibility to other users: + *

    + *
  • "none" — user appears offline.
  • + *
  • "friends" — user is visible only to friends.
  • + *
  • "connections" — user is visible to friends and connections.
  • + *
  • "all" — user is visible to everyone.
  • + *
+ * @property {string} metaverseServerURL - The metaverse server that the user is authenticated against when logged in + * — typically "https://metaverse.highfidelity.com". Read-only. + * + * @borrows AccountServices.getDownloadInfo as getDownloadInfo + * @borrows AccountServices.updateDownloadInfo as updateDownloadInfo + * @borrows AccountServices.isLoggedIn as isLoggedIn + * @borrows AccountServices.checkAndSignalForAccessToken as checkAndSignalForAccessToken + * @borrows AccountServices.logOut as logOut + * + * @borrows AccountServices.connected as connected + * @borrows AccountServices.disconnected as disconnected + * @borrows AccountServices.myUsernameChanged as myUsernameChanged + * @borrows AccountServices.downloadInfoChanged as downloadInfoChanged + * @borrows AccountServices.findableByChanged as findableByChanged + * @borrows AccountServices.loggedInChanged as loggedInChanged + */ + + /**jsdoc + * The GlobalServices API provides functions that give information on user connectivity, visibility, and asset + * download progress. + * + * @deprecated This API is the same as the {@link AccountServices} API and will be removed. + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @namespace GlobalServices + * + * @property {string} username - The user name of the user logged in. If there is no user logged in, it is + * "Unknown user". Read-only. + * @property {boolean} loggedIn - true if the user is logged in, otherwise false. + * Read-only. + * @property {string} findableBy - The user's visibility to other users: + *
    + *
  • "none" — user appears offline.
  • + *
  • "friends" — user is visible only to friends.
  • + *
  • "connections" — user is visible to friends and connections.
  • + *
  • "all" — user is visible to everyone.
  • + *
+ * @property {string} metaverseServerURL - The metaverse server that the user is authenticated against when logged in + * — typically "https://metaverse.highfidelity.com". Read-only. + * + * @borrows AccountServices.getDownloadInfo as getDownloadInfo + * @borrows AccountServices.updateDownloadInfo as updateDownloadInfo + * @borrows AccountServices.isLoggedIn as isLoggedIn + * @borrows AccountServices.checkAndSignalForAccessToken as checkAndSignalForAccessToken + * @borrows AccountServices.logOut as logOut + * + * @borrows AccountServices.connected as connected + * @borrows AccountServices.disconnected as disconnected + * @borrows AccountServices.myUsernameChanged as myUsernameChanged + * @borrows AccountServices.downloadInfoChanged as downloadInfoChanged + * @borrows AccountServices.findableByChanged as findableByChanged + * @borrows AccountServices.loggedInChanged as loggedInChanged + */ + Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged) Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged) Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged) diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index ae4af48cd6..f78f7853ca 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -30,21 +30,6 @@ * @property {InteractiveWindow.DockArea} LEFT - Dock to the left edge of the Interface window. * @property {InteractiveWindow.DockArea} RIGHT - Dock to the right edge of the Interface window. */ -/**jsdoc - *

A docking location of an InteractiveWindow.

- * - * - * - * - * - * - * - * - * - * - *
ValueNameDescription
0TOPDock to the top edge of the Interface window.
1BOTTOMDock to the bottom edge of the Interface window.
2LEFTDock to the left edge of the Interface window.
3RIGHTDock to the right edge of the Interface window.
- * @typedef {number} InteractiveWindow.DockArea - */ static const QVariantMap DOCK_AREA { { "TOP", DockArea::TOP }, { "BOTTOM", DockArea::BOTTOM }, @@ -53,13 +38,17 @@ static const QVariantMap DOCK_AREA { }; /**jsdoc - * The possible "relative position anchors" of an InteractiveWindow. Used when defining the `relativePosition` property of an `InteractiveWindow`. + * The possible relative position anchors of an InteractiveWindow relative to the Interface window. * @typedef {object} InteractiveWindow.RelativePositionAnchors - * @property {InteractiveWindow.RelativePositionAnchor} NO_ANCHOR - Specifies that the position of the `InteractiveWindow` will not be relative to any part of the Interface window. - * @property {InteractiveWindow.RelativePositionAnchor} TOP_LEFT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the top left of the Interface window. - * @property {InteractiveWindow.RelativePositionAnchor} TOP_RIGHT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the top right of the Interface window. - * @property {InteractiveWindow.RelativePositionAnchor} BOTTOM_RIGHT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the bottom right of the Interface window. - * @property {InteractiveWindow.RelativePositionAnchor} BOTTOM_LEFT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the bottom left of the Interface window. + * @property {InteractiveWindow.RelativePositionAnchor} NO_ANCHOR - Position is not relative to any part of the Interface + * window. + * @property {InteractiveWindow.RelativePositionAnchor} TOP_LEFT - Position is offset from the top left of the Interface window. + * @property {InteractiveWindow.RelativePositionAnchor} TOP_RIGHT - Position is offset from the top right of the Interface + * window. + * @property {InteractiveWindow.RelativePositionAnchor} BOTTOM_RIGHT - Position offset from the bottom right of the Interface + * window. + * @property {InteractiveWindow.RelativePositionAnchor} BOTTOM_LEFT - Position is offset from the bottom left of the Interface + * window. */ static const QVariantMap RELATIVE_POSITION_ANCHOR { { "NO_ANCHOR", RelativePositionAnchor::NO_ANCHOR }, @@ -89,21 +78,6 @@ int DesktopScriptingInterface::getHeight() { * @property {InteractiveWindow.PresentationMode} NATIVE - The window is displayed separately from the Interface window, as its * own separate window. */ -/**jsdoc - *

A display mode for an InteractiveWindow.

- * - * - * - * - * - * - * - * - *
ValueNameDescription
0VIRTUALThe window is displayed inside Interface: in the desktop window in - * desktop mode or on the HUD surface in HMD mode.
1NATIVEThe window is displayed separately from the Interface window, as its - * own separate window.
- * @typedef {number} InteractiveWindow.PresentationMode - */ QVariantMap DesktopScriptingInterface::getPresentationMode() { static QVariantMap presentationModes { { "VIRTUAL", Virtual }, diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index c25f382891..e57d7a6805 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -22,7 +22,7 @@ /**jsdoc * The Desktop API provides the dimensions of the computer screen, sets the opacity of the HUD surface, and * enables QML and HTML windows to be shown inside or outside of Interface. - * + * * @namespace Desktop * * @hifi-interface @@ -42,8 +42,8 @@ * @property {InteractiveWindow.DockAreas} DockArea - The possible docking locations of an {@link InteractiveWindow}: top, * bottom, left, or right of the Interface window. * Read-only. - * @property {InteractiveWindow.RelativePositionAnchors} RelativePositionAnchor - The possible "relative position anchors" for an {@link InteractiveWindow}: top left, - * top right, bottom right, or bottom left of the Interface window. + * @property {InteractiveWindow.RelativePositionAnchors} RelativePositionAnchor - The possible relative position anchors for an + * {@link InteractiveWindow}: none, top left, top right, bottom right, or bottom left of the Interface window. * Read-only. */ class DesktopScriptingInterface : public QObject, public Dependency { @@ -84,7 +84,7 @@ public: * @param {string} url - The QML file that specifies the window content. The QML file can use a WebView * control (defined by "WebView.qml" included in the Interface install) to embed an HTML web page (complete with * EventBridge object). - * @param {InteractiveWindow.Properties} [properties] - Initial window properties. + * @param {InteractiveWindow.WindowProperties} [properties] - Initial window properties. * @returns {InteractiveWindow} A new window object. * @example Open a dialog in its own window separate from Interface. * var nativeWindow = Desktop.createWindow(Script.resourcesPath() + 'qml/OverlayWindowTest.qml', { diff --git a/interface/src/scripting/DialogsManagerScriptingInterface.h b/interface/src/scripting/DialogsManagerScriptingInterface.h index b223799cfe..ee1baeecf5 100644 --- a/interface/src/scripting/DialogsManagerScriptingInterface.h +++ b/interface/src/scripting/DialogsManagerScriptingInterface.h @@ -14,19 +14,56 @@ #include +/**jsdoc + * The DialogsMamnager API provides facilities to work with some key dialogs. + * + * @namespace DialogsManager + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + */ + class DialogsManagerScriptingInterface : public QObject { Q_OBJECT public: DialogsManagerScriptingInterface(); static DialogsManagerScriptingInterface* getInstance(); + + /**jsdoc + * Currently performs no action. + * @function DialogsManager.showFeed + */ Q_INVOKABLE void showFeed(); public slots: + /**jsdoc + * Shows the "Goto" dialog. + * @function DialogsManager.showAddressBar + */ void showAddressBar(); + + /**jsdoc + * Hides the "Goto" dialog. + * @function DialogsManager.hideAddressBar + */ void hideAddressBar(); + + /**jsdoc + * Shows the login dialog. + * @function DialogsManager.showLoginDialog + */ void showLoginDialog(); signals: + /**jsdoc + * Triggered when the "Goto" dialog is opened or closed. + *

Warning: Currently isn't always triggered.

+ * @function DialogsManager.addressBarShown + * @param {boolean} visible - true if the Goto dialog has been opened, false if it has been + * closed. + * @returns {Signal} + */ void addressBarShown(bool visible); }; diff --git a/interface/src/scripting/PerformanceScriptingInterface.h b/interface/src/scripting/PerformanceScriptingInterface.h index 92d4273dfb..8a7e403e5d 100644 --- a/interface/src/scripting/PerformanceScriptingInterface.h +++ b/interface/src/scripting/PerformanceScriptingInterface.h @@ -18,6 +18,18 @@ #include "../RefreshRateManager.h" +/**jsdoc + * The Performance API provides control and information on graphics performance settings. + * + * @namespace Performance + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {Performance.PerformancePreset} performancePreset - The current graphics performance preset. + * @property {Performance.RefreshRateProfile} refreshRateProfile - The current refresh rate profile. + */ class PerformanceScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(PerformancePreset performancePreset READ getPerformancePreset WRITE setPerformancePreset NOTIFY settingsChanged) @@ -25,6 +37,23 @@ class PerformanceScriptingInterface : public QObject { public: + /**jsdoc + *

Graphics performance presets.

+ * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0UNKNOWNCustom settings of world detail, rendering effects, and refresh + * rate.
1LOWLow world detail, no rendering effects, lo refresh rate.
2MIDMedium world detail, some rendering effects, medium refresh + * rate.
3HIGHMaximum world detail, all rendering effects, high refresh rate.
+ * @typedef {number} Performance.PerformancePreset + */ // PerformanceManager PerformancePreset tri state level enums enum PerformancePreset { UNKNOWN = PerformanceManager::PerformancePreset::UNKNOWN, @@ -34,6 +63,23 @@ public: }; Q_ENUM(PerformancePreset) + /**jsdoc + *

Refresh rate profile.

+ * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0ECOLow refresh rate, which is reduced when Interface doesn't have focus or + * is minimized.
1INTERACTIVEMedium refresh rate, which is reduced when Interface doesn't have + * focus or is minimized.
2REALTIMEHigh refresh rate, even when Interface doesn't have focus or is + * minimized.
+ * @typedef {number} Performance.RefreshRateProfile + */ // Must match RefreshRateManager enums enum RefreshRateProfile { ECO = RefreshRateManager::RefreshRateProfile::ECO, @@ -47,19 +93,81 @@ public: public slots: + /**jsdoc + * Sets graphics performance to a preset. + * @function Performance.setPerformancePreset + * @param {Performance.PerformancePreset} performancePreset - The graphics performance preset to to use. + */ void setPerformancePreset(PerformancePreset performancePreset); + + /**jsdoc + * Gets the current graphics performance preset in use. + * @function Performance.getPerformancePreset + * @returns {Performance.PerformancePreset} The current graphics performance preset in use. + */ PerformancePreset getPerformancePreset() const; + + /**jsdoc + * Gets the names of the graphics performance presets. + * @function Performance.getPerformancePresetNames + * @returns {string[]} The names of the graphics performance presets. The array index values correspond to + * {@link Performance.PerformancePreset} values. + */ QStringList getPerformancePresetNames() const; + + /**jsdoc + * Sets the curfrent refresh rate profile. + * @function Performance.setRefreshRateProfile + * @param {Performance.RefreshRateProfile} refreshRateProfile - The refresh rate profile. + */ void setRefreshRateProfile(RefreshRateProfile refreshRateProfile); + + /**jsdoc + * Gets the current refresh rate profile in use. + * @function Performance.getRefreshRateProfile + * @returns {Performance.RefreshRateProfile} The refresh rate profile. + */ RefreshRateProfile getRefreshRateProfile() const; + + /**jsdoc + * Gets the names of the refresh rate profiles. + * @function Performance.getRefreshRateProfileNames + * @returns {string[]} The names of the refresh rate profiles. The array index values correspond to + * {@link Performance.RefreshRateProfile} values. + */ QStringList getRefreshRateProfileNames() const; + + /**jsdoc + * Gets the current target refresh rate, in Hz, per the current refresh rate profile and refresh rate regime if in desktop + * mode; a higher rate if in VR mode. + * @function Performance.getActiveRefreshRate + * @returns {number} The current target refresh rate, in Hz. + */ int getActiveRefreshRate() const; + + /**jsdoc + * Gets the current user experience mode. + * @function Performance.getUXMode + * @returns {UXMode} The current user experience mode. + */ RefreshRateManager::UXMode getUXMode() const; + + /**jsdoc + * Gets the current refresh rate regime that's in effect. + * @function Performance.getRefreshRateRegime + * @returns {RefreshRateRegime} The current refresh rate regime. + */ RefreshRateManager::RefreshRateRegime getRefreshRateRegime() const; signals: + + /**jsdoc + * Triggered when the performance preset or refresh rate profile is changed. + * @function Performance.settingsChanged + * @returns {Signal} + */ void settingsChanged(); private: diff --git a/interface/src/scripting/RatesScriptingInterface.h b/interface/src/scripting/RatesScriptingInterface.h index 5658ed99a0..6feef94d17 100644 --- a/interface/src/scripting/RatesScriptingInterface.h +++ b/interface/src/scripting/RatesScriptingInterface.h @@ -14,6 +14,41 @@ #include +/**jsdoc + * The Rates API provides some information on current rendering performance. + * + * @namespace Rates + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {number} render - The rate at which new GPU frames are being created, in Hz. + * Read-only. + * @property {number} present - The rate at which the display plugin is presenting to the display device, in Hz. + * Read-only. + * @property {number} newFrame - The rate at which the display plugin is presenting new GPU frames, in Hz. + * Read-only. + * @property {number} dropped - The rate at which the display plugin is dropping GPU frames, in Hz. + * Read-only. + * @property {number} simulation - The rate at which the game loop is running, in Hz. + * Read-only. + * + * @example Report current rendering rates. + * // The rates to report. + * var rates = [ + * "render", + * "present", + * "newFrame", + * "dropped", + * "simulation" + * ]; + * + * // Report the rates. + * for (var i = 0; i < rates.length; i++) { + * print("Rates." + rates[i], "=", Rates[rates[i]]); + * } + */ class RatesScriptingInterface : public QObject { Q_OBJECT diff --git a/interface/src/scripting/ScreenshareScriptingInterface.cpp b/interface/src/scripting/ScreenshareScriptingInterface.cpp new file mode 100644 index 0000000000..3bf8336fe4 --- /dev/null +++ b/interface/src/scripting/ScreenshareScriptingInterface.cpp @@ -0,0 +1,348 @@ +// +// ScreenshareScriptingInterface.cpp +// interface/src/scripting/ +// +// Created by Milad Nazeri and Zach Fox on 2019-10-23. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "EntityScriptingInterface.h" +#include "ScreenshareScriptingInterface.h" + +static const int SCREENSHARE_INFO_REQUEST_RETRY_TIMEOUT_MS = 300; +ScreenshareScriptingInterface::ScreenshareScriptingInterface() { + auto esi = DependencyManager::get(); + if (!esi) { + return; + } + + // This signal/slot connection is used when the screen share local web entity sends an event bridge message. + QObject::connect(esi.data(), &EntityScriptingInterface::webEventReceived, this, &ScreenshareScriptingInterface::onWebEventReceived); + + _requestScreenshareInfoRetryTimer = new QTimer; + _requestScreenshareInfoRetryTimer->setSingleShot(true); + _requestScreenshareInfoRetryTimer->setInterval(SCREENSHARE_INFO_REQUEST_RETRY_TIMEOUT_MS); + connect(_requestScreenshareInfoRetryTimer, &QTimer::timeout, this, &ScreenshareScriptingInterface::requestScreenshareInfo); + + // This packet listener handles the packet containing information about the latest zone ID in which we are allowed to share. + auto nodeList = DependencyManager::get(); + PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacketOnClient"); +}; + +ScreenshareScriptingInterface::~ScreenshareScriptingInterface() { + stopScreenshare(); +} + +void ScreenshareScriptingInterface::processAvatarZonePresencePacketOnClient(QSharedPointer message) { + QUuid zone = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + if (zone.isNull()) { + qWarning() << "Ignoring avatar zone presence packet that doesn't specify a zone."; + return; + } + + // Set the last known authorized screenshare zone ID to the zone that the Domain Server just told us about. + _lastAuthorizedZoneID = zone; + + // If we had previously started the screenshare process but knew that we weren't going to be authorized to screenshare, + // let's continue the screenshare process here. + if (_waitingForAuthorization) { + requestScreenshareInfo(); + } +} + +static const int MAX_NUM_SCREENSHARE_INFO_REQUEST_RETRIES = 5; +void ScreenshareScriptingInterface::requestScreenshareInfo() { + // If the screenshare zone that we're currently in (i.e. `startScreenshare()` was called) is different from + // the zone in which we are authorized to screenshare... + // ...return early here and wait for the DS to send us a packet containing this zone's ID. + if (_screenshareZoneID != _lastAuthorizedZoneID) { + qDebug() << "Client not yet authorized to screenshare. Waiting for authorization message from domain server..."; + _waitingForAuthorization = true; + return; + } + + _waitingForAuthorization = false; + + _requestScreenshareInfoRetries++; + + if (_requestScreenshareInfoRetries >= MAX_NUM_SCREENSHARE_INFO_REQUEST_RETRIES) { + qDebug() << "Maximum number of retries for screenshare info exceeded. Screenshare will not function."; + return; + } + + // Don't continue with any more of this logic if we can't get the `AccountManager` or `AddressManager`. + auto accountManager = DependencyManager::get(); + if (!accountManager) { + return; + } + auto addressManager = DependencyManager::get(); + if (!addressManager) { + return; + } + + // Construct and send a request to the Metaverse to obtain the information + // necessary to start the screen sharing process. + // This request requires: + // 1. The domain ID of the domain in which the user's avatar is present + // 2. User authentication information that is automatically included when `sendRequest()` is passed + // with the `AccountManagerAuth::Required` argument. + // Note that this request will only return successfully if the Domain Server has already registered + // the user paired with the current domain with the Metaverse. + // See `DomainServer::screensharePresence()` for more info about that. + + QString currentDomainID = uuidStringWithoutCurlyBraces(addressManager->getDomainID()); + QString requestURLPath = "api/v1/domains/%1/screenshare"; + JSONCallbackParameters callbackParams; + callbackParams.callbackReceiver = this; + callbackParams.jsonCallbackMethod = "handleSuccessfulScreenshareInfoGet"; + callbackParams.errorCallbackMethod = "handleFailedScreenshareInfoGet"; + accountManager->sendRequest( + requestURLPath.arg(currentDomainID), + AccountManagerAuth::Required, + QNetworkAccessManager::GetOperation, + callbackParams + ); +} + +static const EntityTypes::EntityType LOCAL_SCREENSHARE_WEB_ENTITY_TYPE = EntityTypes::Web; +static const uint8_t LOCAL_SCREENSHARE_WEB_ENTITY_FPS = 30; +// This is going to be a good amount of work to make this work dynamically for any screensize. +// V1 will have only hardcoded values. +// The `z` value here is dynamic. +static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0f); +static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS(3.6790f, 2.0990f, 0.0100f); +static const QString LOCAL_SCREENSHARE_WEB_ENTITY_URL = + "https://content.highfidelity.com/Experiences/Releases/usefulUtilities/smartBoard/screenshareViewer/screenshareClient.html"; +static const QString LOCAL_SCREENSHARE_WEB_ENTITY_HOST_TYPE = "local"; +void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZoneID, + const QUuid& smartboardEntityID, + const bool& isPresenter) { + // We must start a new QProcess from the main thread. + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startScreenshare", Q_ARG(const QUuid&, screenshareZoneID), + Q_ARG(const QUuid&, smartboardEntityID), Q_ARG(const bool&, isPresenter)); + return; + } + + // These three private member variables are set now so that they may be used later during asynchronous + // callbacks. + _screenshareZoneID = screenshareZoneID; + _smartboardEntityID = smartboardEntityID; + _isPresenter = isPresenter; + + // If we are presenting, and the screenshare process is already running, don't do anything else here. + if (_isPresenter && _screenshareProcess && _screenshareProcess->state() != QProcess::NotRunning) { + return; + } + + // If we're presenting... + if (_isPresenter) { + // ...make sure we first reset this `std::unique_ptr`. + _screenshareProcess.reset(new QProcess(this)); + + // Ensure that the screenshare executable exists where we expect it to. + // Error out and reset the screen share state machine if the executable doesn't exist. + QFileInfo screenshareExecutable(SCREENSHARE_EXE_PATH); + if (!screenshareExecutable.exists() || !(screenshareExecutable.isFile() || screenshareExecutable.isBundle())) { + qDebug() << "Screenshare executable doesn't exist at" << SCREENSHARE_EXE_PATH; + stopScreenshare(); + emit screenshareError(); + return; + } + } + + if (_requestScreenshareInfoRetryTimer && _requestScreenshareInfoRetryTimer->isActive()) { + _requestScreenshareInfoRetryTimer->stop(); + } + + _requestScreenshareInfoRetries = 0; + requestScreenshareInfo(); +} + +void ScreenshareScriptingInterface::stopScreenshare() { + // We can only deal with our Screen Share `QProcess` on the main thread. + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopScreenshare"); + return; + } + + // If the retry timer is active, stop it. + if (_requestScreenshareInfoRetryTimer && _requestScreenshareInfoRetryTimer->isActive()) { + _requestScreenshareInfoRetryTimer->stop(); + } + + // If the Screen Share process is running... + if (_screenshareProcess && _screenshareProcess->state() != QProcess::NotRunning) { + //...terminate it and make sure that scripts know we terminated it by emitting + // `screenshareProcessTerminated()`. + _screenshareProcess->terminate(); + } + + // Delete the local web entity if we know about it here. + if (!_screenshareViewerLocalWebEntityUUID.isNull()) { + auto esi = DependencyManager::get(); + if (esi) { + esi->deleteEntity(_screenshareViewerLocalWebEntityUUID); + } + } + + // Reset all private member variables related to screen share here. + _screenshareViewerLocalWebEntityUUID = "{00000000-0000-0000-0000-000000000000}"; + _token = ""; + _projectAPIKey = ""; + _sessionID = ""; + _isPresenter = false; + _waitingForAuthorization = false; +} + +// Called when the Metaverse returns the information necessary to start/view a screen share. +void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkReply* reply) { + // Read the reply and get it into a format we understand. + QString answer = reply->readAll(); + QByteArray answerByteArray = answer.toUtf8(); + QJsonDocument answerJSONObject = QJsonDocument::fromJson(answerByteArray); + + // This Metaverse endpoint will always return a status key/value pair of "success" if things went well. + QString status = answerJSONObject["status"].toString(); + if (status != "success") { + qDebug() << "Error when retrieving screenshare info via HTTP. Error:" << reply->errorString(); + stopScreenshare(); + emit screenshareError(); + return; + } + + // Store the information necessary to start/view a screen share in these private member variables. + _token = answerJSONObject["token"].toString(); + _projectAPIKey = answerJSONObject["projectApiKey"].toString(); + _sessionID = answerJSONObject["sessionID"].toString(); + + // Make sure we have all of the info that we need. + if (_token.isEmpty() || _projectAPIKey.isEmpty() || _sessionID.isEmpty()) { + qDebug() << "Not all Screen Share information was retrieved from the backend. Stopping..."; + stopScreenshare(); + emit screenshareError(); + return; + } + + // If we're presenting: + // 1. Build a list of arguments that we're going to pass to the screen share Electron app. + // 2. Make sure we connect a signal/slot to know when the user quits the Electron app. + // 3. Start the screen share Electron app with the list of args from (1). + if (_isPresenter) { + QStringList arguments; + arguments << " "; + arguments << "--token=" + _token << " "; + arguments << "--projectAPIKey=" + _projectAPIKey << " "; + arguments << "--sessionID=" + _sessionID << " "; + + connect(_screenshareProcess.get(), QOverload::of(&QProcess::finished), + [=](int exitCode, QProcess::ExitStatus exitStatus) { + stopScreenshare(); + emit screenshareProcessTerminated(); + }); + + _screenshareProcess->start(SCREENSHARE_EXE_PATH, arguments); + } + + // Make sure we can grab the entity scripting interface. Error out if we can't. + auto esi = DependencyManager::get(); + if (!esi) { + stopScreenshare(); + emit screenshareError(); + return; + } + + // If, for some reason, we already have a record of a screen share local Web entity, delete it. + if (!_screenshareViewerLocalWebEntityUUID.isNull()) { + esi->deleteEntity(_screenshareViewerLocalWebEntityUUID); + } + + // Set up the entity properties associated with the screen share local Web entity. + EntityItemProperties localScreenshareWebEntityProps; + localScreenshareWebEntityProps.setType(LOCAL_SCREENSHARE_WEB_ENTITY_TYPE); + localScreenshareWebEntityProps.setMaxFPS(LOCAL_SCREENSHARE_WEB_ENTITY_FPS); + glm::vec3 localPosition(LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION); + localPosition.z = _localWebEntityZOffset; + localScreenshareWebEntityProps.setLocalPosition(localPosition); + localScreenshareWebEntityProps.setSourceUrl(LOCAL_SCREENSHARE_WEB_ENTITY_URL); + localScreenshareWebEntityProps.setParentID(_smartboardEntityID); + localScreenshareWebEntityProps.setDimensions(LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS); + + // The lines below will be used when writing the feature to support scaling the Smartboard entity to any arbitrary size. + //EntityPropertyFlags desiredSmartboardProperties; + //desiredSmartboardProperties += PROP_POSITION; + //desiredSmartboardProperties += PROP_DIMENSIONS; + //EntityItemProperties smartboardProps = esi->getEntityProperties(_smartboardEntityID, desiredSmartboardProperties); + + // Add the screen share local Web entity to Interface's entity tree. + // When the Web entity loads the page specified by `LOCAL_SCREENSHARE_WEB_ENTITY_URL`, it will broadcast an Event Bridge + // message, which we will consume inside `ScreenshareScriptingInterface::onWebEventReceived()`. + _screenshareViewerLocalWebEntityUUID = esi->addEntity(localScreenshareWebEntityProps, LOCAL_SCREENSHARE_WEB_ENTITY_HOST_TYPE); +} + +void ScreenshareScriptingInterface::handleFailedScreenshareInfoGet(QNetworkReply* reply) { + if (_requestScreenshareInfoRetries >= MAX_NUM_SCREENSHARE_INFO_REQUEST_RETRIES) { + qDebug() << "Failed to get screenshare info via HTTP after" << MAX_NUM_SCREENSHARE_INFO_REQUEST_RETRIES << "retries. Error:" << reply->errorString(); + stopScreenshare(); + emit screenshareError(); + return; + } + + _requestScreenshareInfoRetryTimer->start(); +} + +// This function will handle _all_ web events received via `EntityScriptingInterface::webEventReceived()`, including +// those not related to screen sharing. +void ScreenshareScriptingInterface::onWebEventReceived(const QUuid& entityID, const QVariant& message) { + // Bail early if the entity that sent the Web event isn't the one we care about. + if (entityID == _screenshareViewerLocalWebEntityUUID) { + // Bail early if we can't grab the Entity Scripting Interface. + auto esi = DependencyManager::get(); + if (!esi) { + return; + } + + // Web events received from the screen share Web JS will always be in stringified JSON format. + QByteArray jsonByteArray = QVariant(message).toString().toUtf8(); + QJsonDocument jsonObject = QJsonDocument::fromJson(jsonByteArray); + + // It should never happen where the screen share Web JS sends a message without the `app` key's value + // set to "screenshare". + if (jsonObject["app"] != "screenshare") { + return; + } + + // The screen share Web JS only sends a message with one method: "eventBridgeReady". Handle it here. + if (jsonObject["method"] == "eventBridgeReady") { + // Stuff a JSON object full of information necessary for the screen share local Web entity + // to connect to the screen share session associated with the room in which the user's avatar is standing. + QJsonObject responseObject; + responseObject.insert("app", "screenshare"); + responseObject.insert("method", "receiveConnectionInfo"); + QJsonObject responseObjectData; + responseObjectData.insert("token", _token); + responseObjectData.insert("projectAPIKey", _projectAPIKey); + responseObjectData.insert("sessionID", _sessionID); + responseObject.insert("data", responseObjectData); + + esi->emitScriptEvent(_screenshareViewerLocalWebEntityUUID, responseObject.toVariantMap()); + } + } +} diff --git a/interface/src/scripting/ScreenshareScriptingInterface.h b/interface/src/scripting/ScreenshareScriptingInterface.h new file mode 100644 index 0000000000..ec8f3cd619 --- /dev/null +++ b/interface/src/scripting/ScreenshareScriptingInterface.h @@ -0,0 +1,92 @@ +// +// ScreenshareScriptingInterface.h +// interface/src/scripting/ +// +// Created by Milad Nazeri and Zach Fox on 2019-10-23. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScreenshareScriptingInterface_h +#define hifi_ScreenshareScriptingInterface_h + +#include +#include +#include +#include + +#include +#include + +class ScreenshareScriptingInterface : public QObject, public Dependency { + Q_OBJECT + Q_PROPERTY(float localWebEntityZOffset MEMBER _localWebEntityZOffset NOTIFY localWebEntityZOffsetChanged) +public: + ScreenshareScriptingInterface(); + ~ScreenshareScriptingInterface(); + + Q_INVOKABLE void startScreenshare(const QUuid& screenshareZoneID, const QUuid& smartboardEntityID, const bool& isPresenter = false); + Q_INVOKABLE void stopScreenshare(); + +signals: + void screenshareError(); + void screenshareProcessTerminated(); + void startScreenshareViewer(); + void localWebEntityZOffsetChanged(const float& newZOffset); + +private slots: + void processAvatarZonePresencePacketOnClient(QSharedPointer message); + void onWebEventReceived(const QUuid& entityID, const QVariant& message); + void handleSuccessfulScreenshareInfoGet(QNetworkReply* reply); + void handleFailedScreenshareInfoGet(QNetworkReply* reply); + +private: +#if DEV_BUILD +#ifdef Q_OS_WIN + const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/hifi-screenshare-win32-x64/hifi-screenshare.exe" }; +#elif defined(Q_OS_MAC) + const QString SCREENSHARE_EXE_PATH{ QCoreApplication::applicationDirPath() + "/../Resources/hifi-screenshare.app" }; +#else + // This path won't exist on other platforms, so the Screenshare Scripting Interface will exit early when invoked. + const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/hifi-screenshare-other-os/hifi-screenshare" }; +#endif +#else +#ifdef Q_OS_WIN + const QString SCREENSHARE_EXE_PATH{ QCoreApplication::applicationDirPath() + "/hifi-screenshare/hifi-screenshare.exe" }; +#elif defined(Q_OS_MAC) + const QString SCREENSHARE_EXE_PATH{ QCoreApplication::applicationDirPath() + "/../Resources/hifi-screenshare.app" }; +#else + // This path won't exist on other platforms, so the Screenshare Scripting Interface will exit early when invoked. + const QString SCREENSHARE_EXE_PATH{ QCoreApplication::applicationDirPath() + "/hifi-screenshare/hifi-screenshare" }; +#endif +#endif + + QTimer* _requestScreenshareInfoRetryTimer{ nullptr }; + int _requestScreenshareInfoRetries{ 0 }; + void requestScreenshareInfo(); + + // Empirically determined. The default value here can be changed in Screenshare scripts, which enables faster iteration when we discover + // positional issues with various Smartboard entities. + // The following four values are closely linked: + // 1. The z-offset of whiteboard polylines (`STROKE_FORWARD_OFFSET_M` in `drawSphereClient.js`). + // 2. The z-offset of the screenshare local web entity (`LOCAL_WEB_ENTITY_Z_OFFSET` in `smartboardZoneClient.js`). + // 3. The z-offset of the screenshare "glass bezel" (`DEFAULT_SMARTBOARD_SCREENSHARE_GLASS_PROPS` in `smartboardZoneClient.js`). + // 4. The z-offset of the screenshare "status icon" (handled in the screenshare JSON file). + float _localWebEntityZOffset{ 0.0375f }; + + std::unique_ptr _screenshareProcess{ nullptr }; + QUuid _screenshareViewerLocalWebEntityUUID; + QString _token{ "" }; + QString _projectAPIKey{ "" }; + QString _sessionID{ "" }; + QUuid _screenshareZoneID; + QUuid _smartboardEntityID; + bool _isPresenter{ false }; + + QUuid _lastAuthorizedZoneID; + bool _waitingForAuthorization{ false }; +}; + +#endif // hifi_ScreenshareScriptingInterface_h diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index d2147ac5cc..32f837668d 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -17,7 +17,7 @@ GameplayObjects::GameplayObjects() { } bool GameplayObjects::addToGameplayObjects(const QUuid& avatarID) { - containsData = true; + _containsData = true; if (std::find(_avatarIDs.begin(), _avatarIDs.end(), avatarID) == _avatarIDs.end()) { _avatarIDs.push_back(avatarID); } @@ -29,7 +29,7 @@ bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) { } bool GameplayObjects::addToGameplayObjects(const EntityItemID& entityID) { - containsData = true; + _containsData = true; if (std::find(_entityIDs.begin(), _entityIDs.end(), entityID) == _entityIDs.end()) { _entityIDs.push_back(entityID); } diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 4386ee5ee6..f477a25b42 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -26,7 +26,7 @@ class GameplayObjects { public: GameplayObjects(); - bool getContainsData() const { return containsData; } + bool getContainsData() const { return _containsData; } std::vector getAvatarIDs() const { return _avatarIDs; } bool addToGameplayObjects(const QUuid& avatarID); @@ -37,7 +37,7 @@ public: bool removeFromGameplayObjects(const EntityItemID& entityID); private: - bool containsData { false }; + bool _containsData { false }; std::vector _avatarIDs; std::vector _entityIDs; }; diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index e9535ceb4e..849caa8427 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -145,7 +145,8 @@ signals: /**jsdoc * Triggered when a certified avatar entity's ownership check requested via - * {@link WalletScriptingInterface.proveAvatarEntityOwnershipVerification|proveAvatarEntityOwnershipVerification} succeeds. + * {@link WalletScriptingInterface.proveAvatarEntityOwnershipVerification|proveAvatarEntityOwnershipVerification} or + * {@link ContextOverlay.requestOwnershipVerification} succeeds. * @function WalletScriptingInterface.ownershipVerificationSuccess * @param {Uuid} entityID - The ID of the avatar entity checked. * @returns {Signal} @@ -154,7 +155,8 @@ signals: /**jsdoc * Triggered when a certified avatar entity's ownership check requested via - * {@link WalletScriptingInterface.proveAvatarEntityOwnershipVerification|proveAvatarEntityOwnershipVerification} fails. + * {@link WalletScriptingInterface.proveAvatarEntityOwnershipVerification|proveAvatarEntityOwnershipVerification} or + * {@link ContextOverlay.requestOwnershipVerification} fails. * @function WalletScriptingInterface.ownershipVerificationFailed * @param {Uuid} entityID - The ID of the avatar entity checked. * @returns {Signal} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 341b012c2d..0c2fdd1614 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -206,7 +206,7 @@ public slots: void browseAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); /**jsdoc - * Prompts the user to specify the path and name of a file to save to. Displays a model dialog that navigates the directory + * Prompts the user to specify the path and name of a file to save to. Displays a modal dialog that navigates the directory * tree and allows the user to type in a file name. * @function Window.save * @param {string} [title=""] - The title to display at the top of the dialog. @@ -222,7 +222,7 @@ public slots: QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); /**jsdoc - * Prompts the user to specify the path and name of a file to save to. Displays a non-model dialog that navigates the + * Prompts the user to specify the path and name of a file to save to. Displays a non-modal dialog that navigates the * directory tree and allows the user to type in a file name. A {@link Window.saveFileChanged|saveFileChanged} signal is * emitted when a file is specified; no signal is emitted if the user cancels the dialog. * @function Window.saveAsync @@ -325,7 +325,7 @@ public slots: * @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is 0, the * full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the * dimensions is adjusted in order to match the aspect ratio. - * @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-<user + * @param {string} [filename=""] - If a filename is not provided, the image is saved as "vircadia-snap-by-<user * name>-on-YYYY-MM-DD_HH-MM-SS". *

Still images are saved in JPEG or PNG format according to the extension provided — ".jpg", * ".jpeg", or ".png" — or if not provided then in JPEG format with an extension of @@ -364,7 +364,7 @@ public slots: * @function Window.takeSecondaryCameraSnapshot * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} * signal. - * @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-<user + * @param {string} [filename=""] - If a filename is not provided, the image is saved as "vircadia-snap-by-<user * name>-on-YYYY-MM-DD_HH-MM-SS". *

Images are saved in JPEG or PNG format according to the extension provided — ".jpg", * ".jpeg", or ".png" — or if not provided then in JPEG format with an extension of @@ -383,7 +383,7 @@ public slots: * otherwise it is saved as an equirectangular image. * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} * signal. - * @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-<user + * @param {string} [filename=""] - If a filename is not provided, the image is saved as "vircadia-snap-by-<user * name>-on-YYYY-MM-DD_HH-MM-SS". *

Images are saved in JPEG or PNG format according to the extension provided — ".jpg", * ".jpeg", or ".png" — or if not provided then in JPEG format with an extension of @@ -818,8 +818,12 @@ signals: /**jsdoc * Triggered when "minimized" state of the Interface window changes. * @function Window.minimizedChanged - * @param {bool} isMinimized - true if the Interface window is now minimized; false otherwise. + * @param {boolean} isMinimized - true if the Interface window is minimized, false if it isn't. * @returns {Signal} + * @example Report the "minimized" state of the Interface window when it changes. + * function onWindowMinimizedChanged(minimized) { + * print("Window minimized: " + minimized); + * } * * Window.minimizedChanged.connect(onWindowMinimizedChanged); */ diff --git a/interface/src/ui/AvatarCertifyBanner.cpp b/interface/src/ui/AvatarCertifyBanner.cpp index 5101188885..3fe2ed2027 100644 --- a/interface/src/ui/AvatarCertifyBanner.cpp +++ b/interface/src/ui/AvatarCertifyBanner.cpp @@ -62,16 +62,7 @@ void AvatarCertifyBanner::show(const QUuid& avatarID) { void AvatarCertifyBanner::clear() { if (_active) { - auto entityTreeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = entityTreeRenderer->getTree(); - if (!entityTree) { - return; - } - - entityTree->withWriteLock([&] { - entityTree->deleteEntity(_bannerID); - }); - + DependencyManager::get()->deleteEntity(_bannerID); _active = false; } } diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index 6cc26e2409..0ac1f05737 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -129,8 +129,8 @@ void InteractiveWindow::emitMainWindowResizeEvent() { } /**jsdoc - * A set of properties used when creating an InteractiveWindow. - * @typedef {object} InteractiveWindow.Properties + * Property values used when creating an InteractiveWindow. + * @typedef {object} InteractiveWindow.WindowProperties * @property {string} [title="InteractiveWindow] - The title of the window. * @property {Vec2} [position] - The initial position of the window, in pixels. * @property {Vec2} [size] - The initial size of the window, in pixels @@ -142,13 +142,36 @@ void InteractiveWindow::emitMainWindowResizeEvent() { * @property {InteractiveWindow.PresentationWindowInfo} [presentationWindowInfo] - Controls how a NATIVE window is * displayed. If used, the window is docked to the specified edge of the Interface window, otherwise the window is * displayed as its own separate window. - * @property {InteractiveWindow.AdditionalFlags} [additionalFlags=0] - Window behavior flags in addition to "native window flags" (minimize/maximize/close), - * set at window creation. Possible flag values are provided as {@link Desktop|Desktop.ALWAYS_ON_TOP} and {@link Desktop|Desktop.CLOSE_BUTTON_HIDES}. - * Additional flag values can be found on Qt's website at https://doc.qt.io/qt-5/qt.html#WindowType-enum. - * @property {InteractiveWindow.OverrideFlags} [overrideFlags=0] - Window behavior flags instead of the default window flags. - * Set at window creation. Possible flag values are provided as {@link Desktop|Desktop.ALWAYS_ON_TOP} and {@link Desktop|Desktop.CLOSE_BUTTON_HIDES}. - * Additional flag values can be found on Qt's website at https://doc.qt.io/qt-5/qt.html#WindowType-enum. + * @property {InteractiveWindow.Flags} [additionalFlags=0] - Customizes window behavior. + * @property {InteractiveWindow.OverrideFlags} [overrideFlags=0] - Customizes window controls. + + * @property {InteractiveWindow.RelativePositionAnchor} [relativePositionAnchor] - The anchor for the + * relativePosition, if used. + * @property {Vec2} [relativePosition] - The position of the window, relative to the relativePositionAnchor, in + * pixels. Excludes the window frame. + * @property {boolean} [isFullScreenWindow] - true to make the window full screen. */ +/**jsdoc + *

A set of flags customizing InteractiveWindow controls. The value is constructed by using the | + * (bitwise OR) operator on the individual flag values..

+ * + * + * + * + * + * + * + *
ValueNameDescription
0x00000001WindowDisplays the window as a window rather than a dialog.
0x00001000WindowTitleHintAdds a title bar. + *
0x00002000WindowSystemMenuHintAdds a window system menu. + *
0x00004000WindowMinimizeButtonHintAdds a minimize button. + *
0x00008000WindowMaximizeButtonHintAdds a maximize button. + *
0x00040000WindowStaysOnTopHintThe window stays on top of other windows. + * Not used on Windows. + *
0x08000000WindowCloseButtonHintAdds a close button. + *
+ * @typedef {number} InteractiveWindow.OverrideFlags + */ +// OverrideFlags is per InteractiveWindow.qml. InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties, bool restricted) { InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native; diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index fb10aac444..7c8897059f 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -76,12 +76,42 @@ namespace InteractiveWindowEnums { }; Q_ENUM_NS(InteractiveWindowFlags); + /**jsdoc + *

A display mode for an InteractiveWindow.

+ * + * + * + * + * + * + * + * + *
ValueNameDescription
0VIRTUALThe window is displayed inside Interface: in the desktop window in + * desktop mode or on the HUD surface in HMD mode.
1NATIVEThe window is displayed separately from the Interface window, as its + * own separate window.
+ * @typedef {number} InteractiveWindow.PresentationMode + */ enum InteractiveWindowPresentationMode { Virtual, Native }; Q_ENUM_NS(InteractiveWindowPresentationMode); + /**jsdoc + *

A docking location of an InteractiveWindow.

+ * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0TOPDock to the top edge of the Interface window.
1BOTTOMDock to the bottom edge of the Interface window.
2LEFTDock to the left edge of the Interface window.
3RIGHTDock to the right edge of the Interface window.
+ * @typedef {number} InteractiveWindow.DockArea + */ enum DockArea { TOP, BOTTOM, @@ -90,6 +120,24 @@ namespace InteractiveWindowEnums { }; Q_ENUM_NS(DockArea); + /**jsdoc + *

The anchor for a relative position of an InteractiveWindow.

+ * + * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0NO_ANCHORPosition is not relative to any part of the Interface window.
1TOP_LEFTPosition is offset from the top left of the Interface window.
2TOP_RIGHTPosition is offset from the top right of the Interface window.
3BOTTOM_RIGHTPosition offset from the bottom right of the Interface + * window.
4BOTTOM_LEFFTPosition is offset from the bottom left of the Interface + * window.
+ * @typedef {number} InteractiveWindow.RelativePositionAnchor + */ enum RelativePositionAnchor { NO_ANCHOR, TOP_LEFT, @@ -110,13 +158,18 @@ using namespace InteractiveWindowEnums; *

Create using {@link Desktop.createWindow}.

* * @class InteractiveWindow + * @hideconstructor * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @property {string} title - The title of the window. - * @property {Vec2} position - The position of the window, in pixels. + * @property {Vec2} position - The absolute position of the window, in pixels. + * @property {InteractiveWindow.RelativePositionAnchor} relativePositionAnchor - The anchor for the + * relativePosition, if used. + * @property {Vec2} relativePosition - The position of the window, relative to the relativePositionAnchor, in + * pixels. Excludes the window frame. * @property {Vec2} size - The size of the window, in pixels. * @property {boolean} visible - true if the window is visible, false if it isn't. * @property {InteractiveWindow.PresentationMode} presentationMode - The presentation mode of the window: @@ -186,24 +239,24 @@ public slots: * @example Send and receive messages with a QML window. * // JavaScript file. * - * var qmlWindow = Desktop.createWindow(Script.resolvePath("QMLWindow.qml"), { - * title: "QML Window", + * var interactiveWindow = Desktop.createWindow(Script.resolvePath("InteractiveWindow.qml"), { + * title: "Interactive Window", * size: { x: 400, y: 300 } * }); * - * qmlWindow.fromQml.connect(function (message) { + * interactiveWindow.fromQml.connect(function (message) { * print("Message received: " + message); * }); * * Script.setTimeout(function () { - * qmlWindow.sendToQml("Hello world!"); + * interactiveWindow.sendToQml("Hello world!"); * }, 2000); * * Script.scriptEnding.connect(function () { - * qmlWindow.close(); + * interactiveWindow.close(); * }); * @example - * // QML file, "QMLWindow.qml". + * // QML file, "InteractiveWindow.qml". * * import QtQuick 2.5 * import QtQuick.Controls 1.4 @@ -227,7 +280,7 @@ public slots: /**jsdoc * Sends a message to an embedded HTML web page. To receive the message, the HTML page's script must connect to the - * EventBridge that is automatically provided to the script: + * EventBridge that is automatically provided for the script: *
EventBridge.scriptEventReceived.connect(function(message) {
      *     ...
      * });
@@ -239,8 +292,8 @@ public slots: /**jsdoc * @function InteractiveWindow.emitWebEvent - * @param {object|string} message - The message. - * @deprecated This function is deprecated and will be removed from the API. + * @param {object|string} message - Message. + * @deprecated This function is deprecated and will be removed. */ void emitWebEvent(const QVariant& webMessage); @@ -318,9 +371,9 @@ signals: /**jsdoc * @function InteractiveWindow.scriptEventReceived - * @param {object} message - The message. + * @param {object} message - Message. * @returns {Signal} - * @deprecated This signal is deprecated and will be removed from the API. + * @deprecated This signal is deprecated and will be removed. */ // InteractiveWindow content may include WebView requiring EventBridge. void scriptEventReceived(const QVariant& message); @@ -337,9 +390,8 @@ signals: protected slots: /**jsdoc * @function InteractiveWindow.qmlToScript - * @param {object} message - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed from the API. + * @param {object} message - Message. + * @deprecated This method is deprecated and will be removed. */ void qmlToScript(const QVariant& message); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index bb9971e582..4882d6e5da 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -42,9 +42,9 @@ #include "Snapshot.h" #include "SnapshotUploader.h" -// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg +// filename format: vircadia-snap-by-%username%-on-%date%_%time%_@-%location%.jpg // %1 <= username, %2 <= date and time, %3 <= current location -const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg"; +const QString FILENAME_PATH_FORMAT = "vircadia-snap-by-%1-on-%2.jpg"; const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss"; const QString SNAPSHOTS_DIRECTORY = "Snapshots"; const QString URL = "highfidelity_url"; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index a9e2e4d8e3..b013bbff0d 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -251,7 +251,7 @@ void Stats::updateStats(bool force) { SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); auto audioClient = DependencyManager::get().data(); - if (audioMixerNode || force) { + if (audioMixerNode) { STAT_UPDATE(audioMixerKbps, (int)roundf(audioMixerNode->getInboundKbps() + audioMixerNode->getOutboundKbps())); STAT_UPDATE(audioMixerPps, audioMixerNode->getInboundPPS() + @@ -261,6 +261,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(audioMixerInPps, audioMixerNode->getInboundPPS()); STAT_UPDATE(audioMixerOutKbps, (int)roundf(audioMixerNode->getOutboundKbps())); STAT_UPDATE(audioMixerOutPps, audioMixerNode->getOutboundPPS()); + STAT_UPDATE(audioInboundPPS, (int)audioClient->getAudioInboundPPS()); STAT_UPDATE(audioAudioInboundPPS, (int)audioClient->getAudioInboundPPS()); STAT_UPDATE(audioSilentInboundPPS, (int)audioClient->getSilentInboundPPS()); STAT_UPDATE(audioOutboundPPS, (int)audioClient->getAudioOutboundPPS()); @@ -274,6 +275,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(audioMixerOutPps, -1); STAT_UPDATE(audioOutboundPPS, -1); STAT_UPDATE(audioSilentOutboundPPS, -1); + STAT_UPDATE(audioInboundPPS, -1); STAT_UPDATE(audioAudioInboundPPS, -1); STAT_UPDATE(audioSilentInboundPPS, -1); } diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 96f0ea725a..d4dc4d3307 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -24,6 +24,10 @@ private: \ type _##name{ initialValue }; /**jsdoc + * The Stats API provides statistics on Interface and domain operation, per the statistics overlay. + * + *

Note: This API is primarily an internal diagnostics tool and is provided "as is".

+ * * @namespace Stats * * @hifi-interface @@ -32,159 +36,422 @@ private: \ * @hifi-server-entity * @hifi-assignment-client * - * @property {boolean} expanded - * @property {boolean} timingExpanded - Read-only. - * @property {string} monospaceFont - Read-only. + * @property {boolean} expanded - true if the statistics overlay should be in expanded form when the overlay is + * displayed, false if it shouldn't be expanded. + * @property {boolean} timingExpanded - true if timing details should be displayed when the statistics overlay is + * displayed in expanded form, false if timing details should not be displayed. Set by the menu item, + * Developer > Timing > Performance Timer > Display Timing Details. + * Read-only. + * @property {string} monospaceFont - The name of the monospace font used in the statistics overlay. + * Read-only. * - * @property {number} serverCount - Read-only. - * @property {number} renderrate - How often the app is creating new gpu::Frames. Read-only. - * @property {number} presentrate - How often the display plugin is presenting to the device. Read-only. - * @property {number} stutterrate - How often the display device is reprojecting old frames. Read-only. + * @property {number} serverCount - The number of servers that Interface is connected to. + * Read-only. + * @property {number} renderrate - The rate at which new GPU frames are being created, in Hz. + * Read-only. + * @property {number} presentrate - The rate at which the display plugin is presenting to the display device, in Hz. + * Read-only. + * @property {number} stutterrate - The rate at which the display plugin is reprojecting old GPU frames, in Hz. + * Read-only. * - * @property {number} appdropped - Read-only. - * @property {number} longsubmits - Read-only. - * @property {number} longrenders - Read-only. - * @property {number} longframes - Read-only. + * @property {number} appdropped - The number of times a frame has not been provided to the display device in time. + * Read-only. + * @property {number} longsubmits - The number of times the display device has taken longer than 11ms to return after being + * given a frame. + * Read-only. + * @property {number} longrenders - The number of times it has taken longer than 11ms to submit a new frame to the display + * device. + * Read-only. + * @property {number} longframes - The number of times longsubmits + longrenders has taken longer than 15ms. + * Read-only. * - * @property {number} presentnewrate - Read-only. - * @property {number} presentdroprate - Read-only. - * @property {number} gameLoopRate - Read-only. - * @property {number} avatarCount - Read-only. - * @property {number} heroAvatarCount - Read-only. - * @property {number} physicsObjectCount - Read-only. - * @property {number} updatedAvatarCount - Read-only. - * @property {number} updatedHeroAvatarCount - Read-only. - * @property {number} notUpdatedAvatarCount - Read-only. - * @property {number} packetInCount - Read-only. - * @property {number} packetOutCount - Read-only. - * @property {number} mbpsIn - Read-only. - * @property {number} mbpsOut - Read-only. - * @property {number} assetMbpsIn - Read-only. - * @property {number} assetMbpsOut - Read-only. - * @property {number} audioPing - Read-only. - * @property {number} avatarPing - Read-only. - * @property {number} entitiesPing - Read-only. - * @property {number} assetPing - Read-only. - * @property {number} messagePing - Read-only. - * @property {Vec3} position - Read-only. - * @property {number} speed - Read-only. - * @property {number} yaw - Read-only. - * @property {number} avatarMixerInKbps - Read-only. - * @property {number} avatarMixerInPps - Read-only. - * @property {number} avatarMixerOutKbps - Read-only. - * @property {number} avatarMixerOutPps - Read-only. - * @property {number} myAvatarSendRate - Read-only. - * - * @property {number} audioMixerInKbps - Read-only. - * @property {number} audioMixerInPps - Read-only. - * @property {number} audioMixerOutKbps - Read-only. - * @property {number} audioMixerOutPps - Read-only. - * @property {number} audioMixerKbps - Read-only. - * @property {number} audioMixerPps - Read-only. - * @property {number} audioOutboundPPS - Read-only. - * @property {number} audioSilentOutboundPPS - Read-only. - * @property {number} audioAudioInboundPPS - Read-only. - * @property {number} audioSilentInboundPPS - Read-only. - * @property {number} audioPacketLoss - Read-only. - * @property {string} audioCodec - Read-only. - * @property {string} audioNoiseGate - Read-only. - * @property {Vec2} audioInjectors - Read-only. - * @property {number} entityPacketsInKbps - Read-only. - * - * @property {number} downloads - Read-only. - * @property {number} downloadLimit - Read-only. - * @property {number} downloadsPending - Read-only. - * @property {string[]} downloadUrls - Read-only. - * @property {number} processing - Read-only. - * @property {number} processingPending - Read-only. - * @property {number} triangles - Read-only. - * @property {number} materialSwitches - Read-only. - * @property {number} itemConsidered - Read-only. - * @property {number} itemOutOfView - Read-only. - * @property {number} itemTooSmall - Read-only. - * @property {number} itemRendered - Read-only. - * @property {number} shadowConsidered - Read-only. - * @property {number} shadowOutOfView - Read-only. - * @property {number} shadowTooSmall - Read-only. - * @property {number} shadowRendered - Read-only. - * @property {string} sendingMode - Read-only. - * @property {string} packetStats - Read-only. - * @property {number} lodAngle - Read-only. - * @property {number} lodTargetFramerate - Read-only. - * @property {string} lodStatus - Read-only. - * @property {string} numEntityUpdates - Read-only. - * @property {string} numNeededEntityUpdates - Read-only. - * @property {string} timingStats - Read-only. - * @property {string} gameUpdateStats - Read-only. - * @property {number} serverElements - Read-only. - * @property {number} serverInternal - Read-only. - * @property {number} serverLeaves - Read-only. - * @property {number} localElements - Read-only. - * @property {number} localInternal - Read-only. - * @property {number} localLeaves - Read-only. - * @property {number} rectifiedTextureCount - Read-only. - * @property {number} decimatedTextureCount - Read-only. - * @property {number} gpuBuffers - Read-only. - * @property {number} gpuBufferMemory - Read-only. - * @property {number} gpuTextures - Read-only. - * @property {number} glContextSwapchainMemory - Read-only. - * @property {number} qmlTextureMemory - Read-only. - * @property {number} texturePendingTransfers - Read-only. - * @property {number} gpuTextureMemory - Read-only. - * @property {number} gpuTextureResidentMemory - Read-only. - * @property {number} gpuTextureFramebufferMemory - Read-only. - * @property {number} gpuTextureResourceMemory - Read-only. - * @property {number} gpuTextureResourceIdealMemory - Read-only. - * @property {number} gpuTextureResourcePopulatedMemory - Read-only. - * @property {number} gpuTextureExternalMemory - Read-only. - * @property {string} gpuTextureMemoryPressureState - Read-only. - * @property {number} gpuFreeMemory - Read-only. - * @property {number} gpuFrameTime - Read-only. - * @property {number} batchFrameTime - Read-only. - * @property {number} engineFrameTime - Read-only. - * @property {number} avatarSimulationTime - Read-only. - * - * - * @property {number} x - * @property {number} y - * @property {number} z - * @property {number} width - * @property {number} height - * - * @property {number} opacity - * @property {boolean} enabled - * @property {boolean} visible - * - * @property {string} state - * @property {object} anchors - Read-only. - * @property {number} baselineOffset - * - * @property {boolean} clip - * - * @property {boolean} focus - * @property {boolean} activeFocus - Read-only. - * @property {boolean} activeFocusOnTab - * - * @property {number} rotation - * @property {number} scale - * @property {number} transformOrigin - * - * @property {boolean} smooth - * @property {boolean} antialiasing - * @property {number} implicitWidth - * @property {number} implicitHeight - * - * @property {object} layer - Read-only. + * @property {number} presentnewrate - The rate at which the display plugin is presenting new GPU frames, in Hz. + * Read-only. + * @property {number} presentdroprate - The rate at which the display plugin is dropping GPU frames, in Hz. + * Read-only. - * @property {number} stylusPicksCount - Read-only. - * @property {number} rayPicksCount - Read-only. - * @property {number} parabolaPicksCount - Read-only. - * @property {number} collisionPicksCount - Read-only. - * @property {Vec3} stylusPicksUpdated - Read-only. - * @property {Vec3} rayPicksUpdated - Read-only. - * @property {Vec3} parabolaPicksUpdated - Read-only. - * @property {Vec3} collisionPicksUpdated - Read-only. - * @property {bool} eventQueueDebuggingOn - Read-only. + * @property {number} gameLoopRate - The rate at which the game loop is running, in Hz. + * Read-only. + * @property {number} refreshRateTarget - The current target refresh rate, in Hz, per the current refreshRateMode + * and refreshRateRegime if in desktop mode; a higher rate if in VR mode. + * Read-only. + * @property {RefreshRateProfileName} refreshRateMode - The current refresh rate profile. + * Read-only. + * @property {RefreshRateRegimeName} refreshRateRegime - The current refresh rate regime. + * Read-only. + * @property {UXModeName} uxMode - The user experience (UX) mode that Interface is running in. + * Read-only. + * @property {number} avatarCount - The number of avatars in the domain other than the client's. + * Read-only. + * @property {number} heroAvatarCount - The number avatars in a "hero" zone in the domain, other than the client's. + * Read-only. + * @property {number} physicsObjectCount - The number of objects that have collisions enabled. + * Read-only. + * @property {number} updatedAvatarCount - The number of avatars in the domain, other than the client's, that were updated in + * the most recent game loop. + * Read-only. + * @property {number} updatedHeroAvatarCount - The number of avatars in a "hero" zone in the domain, other than the client's, + * that were updated in the most recent game loop. + * Read-only. + * @property {number} notUpdatedAvatarCount - The number of avatars in the domain, other than the client's, that weren't able + * to be updated in the most recent game loop because there wasn't enough time to. + * Read-only. + * @property {number} packetInCount - The number of packets being received from the domain server, in packets per second. + * Read-only. + * @property {number} packetOutCount - The number of packets being sent to the domain server, in packets per second. + * Read-only. + * @property {number} mbpsIn - The amount of data being received from the domain server, in megabits per second. + * Read-only. + * @property {number} mbpsOut - The amount of data being sent to the domain server, in megabits per second. + * Read-only. + @property {number} assetMbpsIn - The amount of data being received from the asset server, in megabits per second. + * 0.0 if not connected to an avatar mixer. + * Read-only. + * @property {number} assetMbpsOut - The amount of data being sent to the asset server, in megabits per second. + * 0.0 if not connected to an avatar mixer. + * Read-only. + * @property {number} audioPing - The ping time to the audio mixer, in ms. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} avatarPing - The ping time to the avatar mixer, in ms. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} entitiesPing - The average ping time to the entity servers, in ms. + * -1 if not connected to an entity server. + * Read-only. + * @property {number} assetPing - The ping time to the asset server, in ms. + * -1 if not connected to an asset server. + * Read-only. + * @property {number} messagePing - The ping time to the message mixer, in ms. + * -1 if not connected to a message mixer. + * Read-only. + * @property {Vec3} position - The position of the user's avatar. + * Read-only. + *

Note: Property not available in the API.

+ * @property {number} speed - The speed of the user's avatar, in m/s. + * Read-only. + * @property {number} yaw - The yaw of the user's avatar body, in degrees. + * Read-only. + * @property {number} avatarMixerInKbps - The amount of data being received from the avatar mixer, in kilobits per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} avatarMixerInPps - The number of packets being received from the avatar mixer, in packets per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} avatarMixerOutKbps - The amount of data being sent to the avatar mixer, in kilobits per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} avatarMixerOutPps - The number of packets being sent to the avatar mixer, in packets per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} myAvatarSendRate - The number of avatar packets being sent by the user's avatar, in packets per second. + * Read-only. + * + * @property {number} audioMixerInKbps - The amount of data being received from the audio mixer, in kilobits per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerInPps - The number of packets being received from the audio mixer, in packets per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerOutKbps - The amount of data being sent to the audio mixer, in kilobits per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerOutPps - The number of packets being sent to the audio mixer, in packets per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerKbps - The total amount of data being sent to and received from the audio mixer, in kilobits + * per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerPps - The total number of packets being sent to and received from the audio mixer, in packets + * per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioOutboundPPS - The number of non-silent audio packets being sent by the user, in packets per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioSilentOutboundPPS - The number of silent audio packets being sent by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioInboundPPS - The number of non-silent audio packets being received by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioAudioInboundPPS - The number of non-silent audio packets being received by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + *

Deprecated: This property is deprecated and will be removed. Use audioInboundPPS + * instead.

+ * @property {number} audioSilentInboundPPS - The number of silent audio packets being received by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioPacketLoss - The number of audio packets being lost, sent to or received from the audio mixer, in %. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {string} audioCodec - The name of the audio codec. + * Read-only. + * @property {string} audioNoiseGate - The status of the audio noise gate: "Open" or "Closed". + * Read-only. + * @property {Vec2} audioInjectors - The number of audio injectors, local and non-local. + * Read-only. + *

Note: Property not available in the API.

+ * @property {number} entityPacketsInKbps - The average amount of data being received from entity servers, in kilobits per + * second. (Multiply by the number of entity servers to get the total amount of data being received.) + * -1 if not connected to an entity server. + * Read-only. + * + * @property {number} downloads - The number of downloads in progress. + * Read-only. + * @property {number} downloadLimit - The maximum number of concurrent downloads. + * Read-only. + * @property {number} downloadsPending - The number of downloads pending. + * Read-only. + * @property {string[]} downloadUrls - The download URLs. + * Read-only. + *

Note: Property not available in the API.

+ * @property {number} processing - The number of completed downloads being processed. + * Read-only. + * @property {number} processingPending - The number of completed downloads waiting to be processed. + * Read-only. + * @property {number} triangles - The number of triangles in the rendered scene. + * Read-only. + * @property {number} drawcalls - The number of draw calls made for the rendered scene. + * Read-only. + * @property {number} materialSwitches - The number of material switches performed for the rendered scene. + * Read-only. + * @property {number} itemConsidered - The number of item considerations made for rendering. + * Read-only. + * @property {number} itemOutOfView - The number of items out of view. + * Read-only. + * @property {number} itemTooSmall - The number of items too small to render. + * Read-only. + * @property {number} itemRendered - The number of items rendered. + * Read-only. + * @property {number} shadowConsidered - The number of shadow considerations made for rendering. + * Read-only. + * @property {number} shadowOutOfView - The number of shadows out of view. + * Read-only. + * @property {number} shadowTooSmall - The number of shadows too small to render. + * Read-only. + * @property {number} shadowRendered - The number of shadows rendered. + * Read-only. + * @property {string} sendingMode - Description of the octree sending mode. + * Read-only. + * @property {string} packetStats - Description of the octree packet processing state. + * Read-only. + * @property {number} lodAngle - The target LOD angle, in degrees. + * Read-only. + * @property {number} lodTargetFramerate - The target LOD frame rate, in Hz. + * Read-only. + * @property {string} lodStatus - Description of the current LOD. + * Read-only. + * @property {string} numEntityUpdates - The number of entity updates that happened last frame. + * Read-only. + * @property {string} numNeededEntityUpdates - The total number of entity updates scheduled for last frame. + * Read-only. + * @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of + * the code. Provided only if timingExpanded is true. Only the top 10 items are provided if + * Developer > Timing > Performance Timer > Only Display Top 10 is enabled. + * Read-only. + * @property {string} gameUpdateStats - Details of the average time (ms) spent in different parts of the game loop. + * Read-only. + * @property {number} serverElements - The total number of elements in the server octree. + * Read-only. + * @property {number} serverInternal - The number of internal elements in the server octree. + * Read-only. + * @property {number} serverLeaves - The number of leaf elements in the server octree. + * Read-only. + * @property {number} localElements - The total number of elements in the client octree. + * Read-only. + * @property {number} localInternal - The number of internal elements in the client octree. + * Read-only. + * @property {number} localLeaves - The number of leaf elements in the client octree. + * Read-only. + * @property {number} rectifiedTextureCount - The number of textures that have been resized so that their dimensions is a power + * of 2 if smaller than 128 pixels, or a multiple of 128 if greater than 128 pixels. + * Read-only. + * @property {number} decimatedTextureCount - The number of textures that have been reduced in size because they were over the + * maximum allowed dimensions of 4096 pixels on desktop or 2048 pixels on mobile. + * Read-only. + * @property {number} gpuBuffers - The number of OpenGL buffer objects managed by the GPU back-end. + * Read-only. + * @property {number} gpuBufferMemory - The total memory size of the gpuBuffers, in MB. + * Read-only. + * @property {number} gpuTextures - The number of OpenGL textures managed by the GPU back-end. This is the sum of the number of + * textures managed for gpuTextureResidentMemory, gpuTextureResourceMemory, and + * gpuTextureFramebufferMemory. + * Read-only. + * @property {number} gpuTextureMemory - The total memory size of the gpuTextures, in MB. This is the sum of + * gpuTextureResidentMemory, gpuTextureResourceMemory, and + * gpuTextureFramebufferMemory. + * Read-only. + * @property {number} glContextSwapchainMemory - The estimated memory used by the default OpenGL frame buffer, in MB. + * Read-only. + * @property {number} qmlTextureMemory - The memory size of textures managed by the offscreen QML surface, in MB. + * Read-only. + * @property {number} texturePendingTransfers - The memory size of textures pending transfer to the GPU, in MB. + * Read-only. + * @property {number} gpuTextureResidentMemory - The memory size of the "strict" textures that always have their full + * resolution in GPU memory, in MB. + * Read-only. + * @property {number} gpuTextureFramebufferMemory - The memory size of the frame buffer on the GPU, in MB. + * Read-only. + * @property {number} gpuTextureResourceMemory - The amount of GPU memory that has been allocated for "variable" textures that + * don't necessarily always have their full resolution in GPU memory, in MB. + * Read-only. + * @property {number} gpuTextureResourceIdealMemory - The amount of memory that "variable" textures would take up if they were + * all completely loaded, in MB. + * Read-only. + * @property {number} gpuTextureResourcePopulatedMemory - How much of the GPU memory allocated has actually been populated, in +* MB. + * Read-only. + * @property {string} gpuTextureMemoryPressureState - The stats of the texture transfer engine. + *
    + *
  • "Undersubscribed": There is texture data that can fit in memory but that isn't on the GPU, so more + * GPU texture memory should be allocated if possible.
  • + *
  • "Transfer": More GPU texture memory has been allocated and texture data is being transferred.
  • + *
  • "Idle": Either all texture data has been transferred to the GPU or there is nor more space + * available.
  • + *
+ * Read-only. + * @property {number} gpuFreeMemory - The amount of GPU memory available after all allocations, in MB. + * Read-only. + *

Note: This is not a reliable number because OpenGL doesn't have an official method of getting this + * information.

+ * @property {number} gpuTextureExternalMemory - The estimated amount of memory consumed by textures being used but that are + * not managed by the GPU library, in MB. + * Read-only. + * @property {Vec2} gpuFrameSize - The dimensions of the frames being rendered, in pixels. + * Read-only. + *

Note: Property not available in the API.

+ * @property {number} gpuFrameTime - The time the GPU is spending on a frame, in ms. + * Read-only. + * @property {number} gpuFrameTimePerPixel - The time the GPU is spending on a pixel, in ns. + * Read-only. + * @property {number} batchFrameTime - The time being spent batch processing each frame, in ms. + * Read-only. + * @property {number} engineFrameTime - The time being spent in the render engine each frame, in ms. + * Read-only. + * @property {number} avatarSimulationTime - The time being spent simulating avatars each frame, in ms. + * Read-only. + * + * @property {number} stylusPicksCount - The number of stylus picks currently in effect. + * Read-only. + * @property {number} rayPicksCount - The number of ray picks currently in effect. + * Read-only. + * @property {number} parabolaPicksCount - The number of parabola picks currently in effect. + * Read-only. + * @property {number} collisionPicksCount - The number of collision picks currently in effect. + * Read-only. + * @property {Vec3} stylusPicksUpdated - The number of stylus pick intersection that were found in the most recent game loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * @property {Vec3} rayPicksUpdated - The number of ray pick intersections that were found in the most recent game loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * @property {Vec3} parabolaPicksUpdated - The number of parabola pick intersections that were found in the most recent game + * loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * @property {Vec3} collisionPicksUpdated - The number of collision pick intersections that were found in the most recent game + * loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * + * @property {boolean} eventQueueDebuggingOn - true if event queue statistics are provided, false if + * they're not. + * Read-only. + * @property {number} mainThreadQueueDepth - The number of events in the main thread's event queue. + * Only provided if eventQueueDebuggingOn is true. + * Read-only. + * @property {number} nodeListThreadQueueDepth - The number of events in the node list thread's event queue. + * Only provided if eventQueueDebuggingOn is true. + * Read-only. + * + * @comment The following property is from Stats.qml. It shouldn't be in the API. + * @property {string} bgColor + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * + * @comment The following properties are from QQuickItem. They shouldn't be in the API. + * @property {boolean} activeFocus + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} activeFocusOnTab + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object} anchors + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} antialiasing + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} baselineOffset + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object[]} children + * Read-only. + *

Note: Property not available in the API.

+ *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} clip + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object} containmentMask + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} enabled + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} focus + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} height + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} implicitHeight + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} implicitWidth + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object} layer + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} opacity + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} rotation + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} scale + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} smooth + *

Deprecated: This property is deprecated and will be removed.

+ * @property {string} state + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} transformOrigin + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} visible + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} width + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} x + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} y + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} z + *

Deprecated: This property is deprecated and will be removed.

*/ // Properties from x onwards are QQuickItem properties. @@ -249,6 +516,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, audioMixerPps, 0) STATS_PROPERTY(int, audioOutboundPPS, 0) STATS_PROPERTY(int, audioSilentOutboundPPS, 0) + STATS_PROPERTY(int, audioInboundPPS, 0) STATS_PROPERTY(int, audioAudioInboundPPS, 0) STATS_PROPERTY(int, audioSilentInboundPPS, 0) STATS_PROPERTY(int, audioPacketLoss, 0) @@ -291,6 +559,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, localLeaves, 0) STATS_PROPERTY(int, rectifiedTextureCount, 0) STATS_PROPERTY(int, decimatedTextureCount, 0) + STATS_PROPERTY(int, gpuBuffers, 0) STATS_PROPERTY(int, gpuBufferMemory, 0) STATS_PROPERTY(int, gpuTextures, 0) @@ -356,37 +625,35 @@ public: QStringList downloadUrls () { return _downloadUrls; } public slots: + + /**jsdoc + * Updates statistics to make current values available to scripts even though the statistics overlay may not be displayed. + * (Many statistics values are normally updated only if the statistics overlay is displayed.) + *

Note: Not all statistics values are updated when the statistics overlay isn't displayed or + * expanded.

+ * @function Stats.forceUpdateStats + * @example Report avatar mixer data and packet rates. + * // The statistics to report. + * var stats = [ + * "avatarMixerInKbps", + * "avatarMixerInPps", + * "avatarMixerOutKbps", + * "avatarMixerOutPps" + * ]; + * + * // Update the statistics for the script. + * Stats.forceUpdateStats(); + * + * // Report the statistics. + * for (var i = 0; i < stats.length; i++) { + * print(stats[i], "=", Stats[stats[i]]); + * } + */ void forceUpdateStats() { updateStats(true); } signals: - /**jsdoc - * Triggered when the value of the longsubmits property changes. - * @function Stats.longsubmitsChanged - * @returns {Signal} - */ - void longsubmitsChanged(); - - /**jsdoc - * Triggered when the value of the longrenders property changes. - * @function Stats.longrendersChanged - * @returns {Signal} - */ - void longrendersChanged(); - - /**jsdoc - * Triggered when the value of the longframes property changes. - * @function Stats.longframesChanged - * @returns {Signal} - */ - void longframesChanged(); - - /**jsdoc - * Triggered when the value of the appdropped property changes. - * @function Stats.appdroppedChanged - * @returns {Signal} - */ - void appdroppedChanged(); + // Signals for properties... /**jsdoc * Triggered when the value of the expanded property changes. @@ -423,6 +690,41 @@ signals: */ void presentrateChanged(); + /**jsdoc + * Triggered when the value of the stutterrate property changes. + * @function Stats.stutterrateChanged + * @returns {Signal} + */ + void stutterrateChanged(); + + /**jsdoc + * Triggered when the value of the appdropped property changes. + * @function Stats.appdroppedChanged + * @returns {Signal} + */ + void appdroppedChanged(); + + /**jsdoc + * Triggered when the value of the longsubmits property changes. + * @function Stats.longsubmitsChanged + * @returns {Signal} + */ + void longsubmitsChanged(); + + /**jsdoc + * Triggered when the value of the longrenders property changes. + * @function Stats.longrendersChanged + * @returns {Signal} + */ + void longrendersChanged(); + + /**jsdoc + * Triggered when the value of the longframes property changes. + * @function Stats.longframesChanged + * @returns {Signal} + */ + void longframesChanged(); + /**jsdoc * Triggered when the value of the presentnewrate property changes. * @function Stats.presentnewrateChanged @@ -437,13 +739,6 @@ signals: */ void presentdroprateChanged(); - /**jsdoc - * Triggered when the value of the stutterrate property changes. - * @function Stats.stutterrateChanged - * @returns {Signal} - */ - void stutterrateChanged(); - /**jsdoc * Triggered when the value of the gameLoopRate property changes. * @function Stats.gameLoopRateChanged @@ -451,13 +746,6 @@ signals: */ void gameLoopRateChanged(); - /**jsdoc - * Trigered when - * @function Stats.numPhysicsBodiesChanged - * @returns {Signal} - */ - void physicsObjectCountChanged(); - /**jsdoc * Triggered when the value of the avatarCount property changes. * @function Stats.avatarCountChanged @@ -465,6 +753,34 @@ signals: */ void avatarCountChanged(); + /**jsdoc + * Triggered when the value of the refreshRateTarget property changes. + * @function Stats.refreshRateTargetChanged + * @returns {Signal} + */ + void refreshRateTargetChanged(); + + /**jsdoc + * Triggered when the value of the refreshRateMode property changes. + * @function Stats.refreshRateModeChanged + * @returns {Signal} + */ + void refreshRateModeChanged(); + + /**jsdoc + * Triggered when the value of the refreshRateRegime property changes. + * @function Stats.refreshRateRegimeChanged + * @returns {Signal} + */ + void refreshRateRegimeChanged(); + + /**jsdoc + * Triggered when the value of the uxMode property changes. + * @function Stats.uxModeChanged + * @returns {Signal} + */ + void uxModeChanged(); + /**jsdoc * Triggered when the value of the heroAvatarCount property changes. * @function Stats.heroAvatarCountChanged @@ -472,6 +788,13 @@ signals: */ void heroAvatarCountChanged(); + /**jsdoc + * Triggered when the value of the physicsObjectCount property changes. + * @function Stats.physicsObjectCountChanged + * @returns {Signal} + */ + void physicsObjectCountChanged(); + /**jsdoc * Triggered when the value of the updatedAvatarCount property changes. * @function Stats.updatedAvatarCountChanged @@ -682,10 +1005,19 @@ signals: */ void audioSilentOutboundPPSChanged(); + /**jsdoc + * Triggered when the value of the audioInboundPPS property changes. + * @function Stats.audioInboundPPSChanged + * @returns {Signal} + */ + void audioInboundPPSChanged(); + /**jsdoc * Triggered when the value of the audioAudioInboundPPS property changes. * @function Stats.audioAudioInboundPPSChanged * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. Use + * {@link Stats.audioInboundPPSChanged|audioInboundPPSChanged} instead. */ void audioAudioInboundPPSChanged(); @@ -731,7 +1063,6 @@ signals: */ void entityPacketsInKbpsChanged(); - /**jsdoc * Triggered when the value of the downloads property changes. * @function Stats.downloadsChanged @@ -782,11 +1113,10 @@ signals: void trianglesChanged(); /**jsdoc - * Triggered when the value of the drawcalls property changes. - * This - * @function Stats.drawcallsChanged - * @returns {Signal} - */ + * Triggered when the value of the drawcalls property changes. + * @function Stats.drawcallsChanged + * @returns {Signal} + */ void drawcallsChanged(); /**jsdoc @@ -901,6 +1231,20 @@ signals: */ void numNeededEntityUpdatesChanged(); + /**jsdoc + * Triggered when the value of the timingStats property changes. + * @function Stats.timingStatsChanged + * @returns {Signal} + */ + void timingStatsChanged(); + + /**jsdoc + * Triggered when the value of the gameUpdateStats property changes. + * @function Stats.gameUpdateStatsChanged + * @returns {Signal} + */ + void gameUpdateStatsChanged(); + /**jsdoc * Triggered when the value of the serverElements property changes. * @function Stats.serverElementsChanged @@ -944,39 +1288,18 @@ signals: void localLeavesChanged(); /**jsdoc - * Triggered when the value of the timingStats property changes. - * @function Stats.timingStatsChanged + * Triggered when the value of the rectifiedTextureCount property changes. + * @function Stats.rectifiedTextureCountChanged * @returns {Signal} */ - void timingStatsChanged(); + void rectifiedTextureCountChanged(); /**jsdoc - * Triggered when the value of the gameUpdateStats property changes. - * @function Stats.gameUpdateStatsChanged + * Triggered when the value of the decimatedTextureCount property changes. + * @function Stats.decimatedTextureCountChanged * @returns {Signal} */ - void gameUpdateStatsChanged(); - - /**jsdoc - * Triggered when the value of the glContextSwapchainMemory property changes. - * @function Stats.glContextSwapchainMemoryChanged - * @returns {Signal} - */ - void glContextSwapchainMemoryChanged(); - - /**jsdoc - * Triggered when the value of the qmlTextureMemory property changes. - * @function Stats.qmlTextureMemoryChanged - * @returns {Signal} - */ - void qmlTextureMemoryChanged(); - - /**jsdoc - * Triggered when the value of the texturePendingTransfers property changes. - * @function Stats.texturePendingTransfersChanged - * @returns {Signal} - */ - void texturePendingTransfersChanged(); + void decimatedTextureCountChanged(); /**jsdoc * Triggered when the value of the gpuBuffers property changes. @@ -999,6 +1322,27 @@ signals: */ void gpuTexturesChanged(); + /**jsdoc + * Triggered when the value of the glContextSwapchainMemory property changes. + * @function Stats.glContextSwapchainMemoryChanged + * @returns {Signal} + */ + void glContextSwapchainMemoryChanged(); + + /**jsdoc + * Triggered when the value of the qmlTextureMemory property changes. + * @function Stats.qmlTextureMemoryChanged + * @returns {Signal} + */ + void qmlTextureMemoryChanged(); + + /**jsdoc + * Triggered when the value of the texturePendingTransfers property changes. + * @function Stats.texturePendingTransfersChanged + * @returns {Signal} + */ + void texturePendingTransfersChanged(); + /**jsdoc * Triggered when the value of the gpuTextureMemory property changes. * @function Stats.gpuTextureMemoryChanged @@ -1063,15 +1407,8 @@ signals: void gpuFreeMemoryChanged(); /**jsdoc - * Triggered when the value of the gpuFrameTime property changes. - * @function Stats.gpuFrameTimeChanged - * @returns {Signal} - */ - void gpuFrameTimeChanged(); - - /**jsdoc - * Triggered when the value of the gpuFrameTime property changes. - * @function Stats.gpuFrameTimeChanged + * Triggered when the value of the gpuFrameSize property changes. + * @function Stats.gpuFrameSizeChanged * @returns {Signal} */ void gpuFrameSizeChanged(); @@ -1081,6 +1418,13 @@ signals: * @function Stats.gpuFrameTimeChanged * @returns {Signal} */ + void gpuFrameTimeChanged(); + + /**jsdoc + * Triggered when the value of the gpuFrameTimePerPixel property changes. + * @function Stats.gpuFrameTimePerPixelChanged + * @returns {Signal} + */ void gpuFrameTimePerPixelChanged(); /**jsdoc @@ -1104,250 +1448,6 @@ signals: */ void avatarSimulationTimeChanged(); - /**jsdoc - * Triggered when the value of the rectifiedTextureCount property changes. - * @function Stats.rectifiedTextureCountChanged - * @returns {Signal} - */ - void rectifiedTextureCountChanged(); - - /**jsdoc - * Triggered when the value of the decimatedTextureCount property changes. - * @function Stats.decimatedTextureCountChanged - * @returns {Signal} - */ - void decimatedTextureCountChanged(); - - - void refreshRateTargetChanged(); - - void refreshRateModeChanged(); - - void refreshRateRegimeChanged(); - - void uxModeChanged(); - - // QQuickItem signals. - - /**jsdoc - * Triggered when the parent item changes. - * @function Stats.parentChanged - * @param {object} parent - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the x property changes. - * @function Stats.xChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the y property changes. - * @function Stats.yChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the z property changes. - * @function Stats.zChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the width property changes. - * @function Stats.widthChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the height property changes. - * @function Stats.heightChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the opacity property changes. - * @function Stats.opacityChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the enabled property changes. - * @function Stats.enabledChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the visibleChanged property changes. - * @function Stats.visibleChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the list of visible children changes. - * @function Stats.visibleChildrenChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the state property changes. - * @function Stats.stateChanged - * @paramm {string} state - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the position and size of the rectangle containing the children changes. - * @function Stats.childrenRectChanged - * @param {Rect} childrenRect - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the baselineOffset property changes. - * @function Stats.baselineOffsetChanged - * @param {number} baselineOffset - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the clip property changes. - * @function Stats.clipChanged - * @param {boolean} clip - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the focus property changes. - * @function Stats.focusChanged - * @param {boolean} focus - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the activeFocus property changes. - * @function Stats.activeFocusChanged - * @param {boolean} activeFocus - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the activeFocusOnTab property changes. - * @function Stats.activeFocusOnTabChanged - * @param {boolean} activeFocusOnTab - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the rotation property changes. - * @function Stats.rotationChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the scaleChanged property changes. - * @function Stats.scaleChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the transformOrigin property changes. - * @function Stats.transformOriginChanged - * @param {number} transformOrigin - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the smooth property changes. - * @function Stats.smoothChanged - * @param {boolean} smooth - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the antialiasing property changes. - * @function Stats.antialiasingChanged - * @param {boolean} antialiasing - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the implicitWidth property changes. - * @function Stats.implicitWidthChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the implicitHeight property changes. - * @function Stats.implicitHeightChanged - * @returns {Signal} - */ - - /**jsdoc - * @function Stats.windowChanged - * @param {object} window - * @returns {Signal} - */ - - - // QQuickItem functions. - - /**jsdoc - * @function Stats.grabToImage - * @param {object} callback - * @param {Size} [targetSize=0,0] - * @returns {boolean} - */ - - /**jsdoc - * @function Stats.contains - * @param {Vec2} point - * @returns {boolean} - */ - - /**jsdoc - * @function Stats.mapFromItem - * @param {object} item - */ - - /**jsdoc - * @function Stats.mapToItem - * @param {object} item - */ - - /**jsdoc - * @function Stats.mapFromGlobal - * @param {object} global - */ - - /**jsdoc - * @function Stats.mapToGlobal - * @param {object} global - */ - - /**jsdoc - * @function Stats.forceActiveFocus - * @param {number} [reason=7] - */ - - /**jsdoc - * @function Stats.nextItemInFocusChain - * @param {boolean} [forward=true] - * @returns {object} - */ - - /**jsdoc - * @function Stats.childAt - * @param {number} x - * @param {number} y - * @returns {object} - */ - - /**jsdoc - * @function Stats.update - */ - /**jsdoc * Triggered when the value of the stylusPicksCount property changes. * @function Stats.stylusPicksCountChanged @@ -1405,11 +1505,11 @@ signals: void collisionPicksUpdatedChanged(); /**jsdoc - * Triggered when the value of the eventQueueDebuggingOn property changes. - * @function Stats.eventQueueDebuggingOn + * Triggered when the value of the mainThreadQueueDepth property changes. + * @function Stats.mainThreadQueueDepthChanged * @returns {Signal} */ - void eventQueueDebuggingOnChanged(); + void mainThreadQueueDepthChanged(); /**jsdoc * Triggered when the value of the nodeListThreadQueueDepth property changes. @@ -1419,11 +1519,294 @@ signals: void nodeListThreadQueueDepthChanged(); /**jsdoc - * Triggered when the value of the nodeListThreadQueueDepth property changes. - * @function Stats.nodeListThreadQueueDepth + * Triggered when the value of the eventQueueDebuggingOn property changes. + * @function Stats.eventQueueDebuggingOnChanged * @returns {Signal} */ - void mainThreadQueueDepthChanged(); + void eventQueueDebuggingOnChanged(); + + + // Stats.qml signals: shouldn't be in the API. + + /**jsdoc + * Triggered when the value of the bgColor property changes. + * @function Stats.bgColorChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + + // QQuickItem signals: shouldn't be in the API. + + /**jsdoc + * Triggered when the value of the activeFocus property changes. + * @function Stats.activeFocusChanged + * @param {boolean} activeFocus - Active focus. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the activeFocusOnTab property changes. + * @function Stats.activeFocusOnTabChanged + * @param {boolean} activeFocusOnTab - Active focus on tab. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the antialiasing property changes. + * @function Stats.antialiasingChanged + * @param {boolean} antialiasing - Antialiasing. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the baselineOffset property changes. + * @function Stats.baselineOffsetChanged + * @param {number} baselineOffset - Baseline offset. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the children property changes. + * @function Stats.childrenChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the position and size of the rectangle containing the children changes. + * @function Stats.childrenRectChanged + * @param {Rect} childrenRect - Children rect. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + + /**jsdoc + * Triggered when the value of the clip property changes. + * @function Stats.clipChanged + * @param {boolean} clip - Clip. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the containmentMask property changes. + * @function Stats.containmentMaskChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the enabled property changes. + * @function Stats.enabledChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the focus property changes. + * @function Stats.focusChanged + * @param {boolean} focus - Focus. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the height property changes. + * @function Stats.heightChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the implicitHeight property changes. + * @function Stats.implicitHeightChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the implicitWidth property changes. + * @function Stats.implicitWidthChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the opacity property changes. + * @function Stats.opacityChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the parent item changes. + * @function Stats.parentChanged + * @param {object} parent - Parent. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the rotation property changes. + * @function Stats.rotationChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the scale property changes. + * @function Stats.scaleChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the smooth property changes. + * @function Stats.smoothChanged + * @param {boolean} smooth - Smooth. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the state property changes. + * @function Stats.stateChanged + * @paramm {string} state - State. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the transformOrigin property changes. + * @function Stats.transformOriginChanged + * @param {number} transformOrigin - Transformm origin. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the visibleChanged property changes. + * @function Stats.visibleChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the list of visible children changes. + * @function Stats.visibleChildrenChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the width property changes. + * @function Stats.widthChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the stats window changes. + * @function Stats.windowChanged + * @param {object} window - Window. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the x property changes. + * @function Stats.xChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the y property changes. + * @function Stats.yChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the z property changes. + * @function Stats.zChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + + // QQuickItem methods: shouldn't be in the API. + + /**jsdoc + * @function Stats.childAt + * @param {number} x - X. + * @param {number} y - Y. + * @returns {object} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.contains + * @param {Vec2} point - Point + * @returns {boolean} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.forceActiveFocus + * @param {number} [reason=7] - Reason + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.grabToImage + * @param {object} callback - Callback. + * @param {Size} [targetSize=0,0] - Target size. + * @returns {boolean} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapFromGlobal + * @param {object} global - Global. + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapFromItem + * @param {object} item - Item. + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapToGlobal + * @param {object} global - Global. + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapToItem + * @param {object} item - Item + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.nextItemInFocusChain + * @param {boolean} [forward=true] - Forward. + * @returns {object} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.update + * @deprecated This method is deprecated and will be removed. + */ private: int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index e688d1c115..5d11b254fc 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -29,6 +29,22 @@ #include "EntityTree.h" #include "ContextOverlayLogging.h" +/**jsdoc + * The ContextOverlay API manages the "i" proof-of-provenance context overlay that appears on Marketplace items + * when a user right-clicks them. + * + * @namespace ContextOverlay + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {boolean} enabled - true if the context overlay is enabled to be displayed, false if it + * is disabled and will never be displayed. + * @property {Uuid} entityWithContextOverlay - The ID of the entity that the context overlay is currently displayed for, + * null if the context overlay is not currently displayed. + * @property {boolean} isInMarketplaceInspectionMode - Currently not used. + */ class ContextOverlayInterface : public QObject, public Dependency { Q_OBJECT @@ -44,30 +60,140 @@ class ContextOverlayInterface : public QObject, public Dependency { QUuid _contextOverlayID { UNKNOWN_ENTITY_ID }; public: ContextOverlayInterface(); + + /**jsdoc + * Gets the ID of the entity that the context overlay is currently displayed for. + * @function ContextOverlay.getCurrentEntityWithContextOverlay + * @returns {Uuid} - The ID of the entity that the context overlay is currently displayed for, null if the + * context overlay is not currently displayed. + */ Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } + void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; } + + /**jsdoc + * Initiates a check on an avatar entity belongs to the user wearing it. The result is returned via + * {@link WalletScriptingInterface.ownershipVerificationSuccess} or + * {@link WalletScriptingInterface.ownershipVerificationFailed}. + *

Warning: Neither of these signals are triggered if the entity is not an avatar entity or is not + * certified.

+ * @function ContextOverlay.requestOwnershipVerification + * @param {Uuid} entityID - The ID of the entity to check. + */ Q_INVOKABLE void requestOwnershipVerification(const QUuid& entityID); + EntityPropertyFlags getEntityPropertyFlags() { return _entityPropertyFlags; } signals: + /**jsdoc + * Triggered when the user clicks on the context overlay. + * @function ContextOverlay.contextOverlayClicked + * @param {Uuid} id - The ID of the entity that the context overlay is for. + * @returns {Signal} + * @example Report when a context overlay is clicked. + * ContextOverlay.contextOverlayClicked.connect(function (id) { + * print("Context overlay clicked for:", id); + * }); + */ void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay); public slots: + + /**jsdoc + * @function ContextOverlay.clickDownOnEntity + * @param {Uuid} id - Entity ID. + * @param {PointerEvent} event - Pointer event. + * @deprecated This method is deprecated and will be removed. + */ + // FIXME: Method shouldn't be in the API. void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * @function ContextOverlay.mouseReleaseOnEntity + * @param {Uuid} id - Entity ID. + * @param {PointerEvent} event - Pointer event. + * @deprecated This method is deprecated and will be removed. + */ + // FIXME: Method shouldn't be in the API. void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + /**jsdoc + * Displays or deletes the context overlay as appropriate for the target entity and a pointer event: the context overlay + * must be enabled and the pointer event must be a right-click; if so, then any current context overlay is deleted, and if + * the target entity should have a context overlay then it is displayed. + * @function ContextOverlay.createOrDestroyContextOverlay + * @param {Uuid} entityID - The target entity. + * @param {PointerEvent} pointerEvent - The pointer event. + * @returns {boolean} - true if the context overlay was deleted or displayed on the specified entity, + * false if no action was taken. + */ bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Deletes the context overlay and removes the entity highlight, if shown. + * @function ContextOverlay.destroyContextOverlay + * @param {Uuid} entityID - The ID of the entity. + * @param {PointerEvent} [event] - Not used. + * @returns {boolean} - true if the context overlay was deleted, false if it wasn't (e.g., it + * wasn't displayed). + */ bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID); + + /**jsdoc + * @function ContextOverlay.contextOverlays_hoverEnterOverlay + * @param {Uuid} id - Overlay ID. + * @param {PointerEvent} event - Pointer event. + * @deprecated This method is deprecated and will be removed. + */ + // FIXME: Method shouldn't be in the API. void contextOverlays_hoverEnterOverlay(const QUuid& id, const PointerEvent& event); + + /**jsdoc + * @function ContextOverlay.contextOverlays_hoverLeaveOverlay + * @param {Uuid} id - Overlay ID. + * @param {PointerEvent} event - Pointer event. + * @deprecated This method is deprecated and will be removed. + */ + // FIXME: Method shouldn't be in the API. void contextOverlays_hoverLeaveOverlay(const QUuid& id, const PointerEvent& event); + + /**jsdoc + * @function ContextOverlay.contextOverlays_hoverEnterEntity + * @param {Uuid} id - Entity ID. + * @param {PointerEvent} event - Pointer event. + * @deprecated This method is deprecated and will be removed. + */ + // FIXME: Method shouldn't be in the API. void contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event); + + /**jsdoc + * @function ContextOverlay.contextOverlays_hoverLeaveEntity + * @param {Uuid} id - Entity ID. + * @param {PointerEvent} event - Pointer event. + * @deprecated This method is deprecated and will be removed. + */ + // FIXME: Method shouldn't be in the API. void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); + + /**jsdoc + * Checks with a context overlay should be displayed for an entity — in particular, whether the item has a non-empty + * certificate ID. + * @function ContextOverlay.contextOverlayFilterPassed + * @param {Uuid} entityID - The ID of the entity to check. + * @returns {boolean} - true if the context overlay should be shown for the entity, false if it + * shouldn't. + * @example Report whether the context overlay should be displayed for entities clicked. + * Entities.clickDownOnEntity.connect(function (id, event) { + * print("Item clicked:", id); + * print("Should display context overlay:", ContextOverlay.contextOverlayFilterPassed(id)); + * }); + */ bool contextOverlayFilterPassed(const EntityItemID& entityItemID); private slots: diff --git a/interface/src/workload/GameWorkload.cpp b/interface/src/workload/GameWorkload.cpp index afbd166c89..d9cda7f16a 100644 --- a/interface/src/workload/GameWorkload.cpp +++ b/interface/src/workload/GameWorkload.cpp @@ -9,6 +9,7 @@ // #include "GameWorkload.h" #include "GameWorkloadRenderer.h" +#include "SelectedWorkloadRenderer.h" #include #include #include @@ -35,6 +36,7 @@ public: model.addJob("PhysicsBoundary", regionTrackerOut); model.addJob("SpaceToRender"); + model.addJob("SelectedWorkloadRender"); out = regionTrackerOut; } diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp index 2bb73999f1..f65bf88754 100644 --- a/interface/src/workload/GameWorkloadRenderer.cpp +++ b/interface/src/workload/GameWorkloadRenderer.cpp @@ -16,6 +16,7 @@ #include #include +#include "SelectedWorkloadRenderer.h" void GameSpaceToRender::configure(const Config& config) { _freezeViews = config.freezeViews; diff --git a/interface/src/workload/SelectedWorkloadRenderer.cpp b/interface/src/workload/SelectedWorkloadRenderer.cpp new file mode 100644 index 0000000000..29cdc46f35 --- /dev/null +++ b/interface/src/workload/SelectedWorkloadRenderer.cpp @@ -0,0 +1,88 @@ +// +// SelectedWorkloadRenderer.cpp +// +// Created by Andrew Meadows 2019.11.08 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "SelectedWorkloadRenderer.h" + +#include +#include + +#include + +#include "Application.h" +#include "GameWorkloadRenderer.h" +#include "scripting/SelectionScriptingInterface.h" + +void SelectedWorkloadRenderer::run(const workload::WorkloadContextPointer& runContext, Outputs& outputs) { + auto gameWorkloadContext = std::dynamic_pointer_cast(runContext); + if (!gameWorkloadContext) { + return; + } + auto space = gameWorkloadContext->_space; + if (!space) { + return; + } + + render::Transaction transaction; + auto scene = gameWorkloadContext->_scene; + + auto selection = DependencyManager::get(); + // Note: the "DebugWorkloadSelection" name is a secret hard-coded C++ debug feature. + // If you create such a named list using JS and the "Selection" API then it will be picked up here + // and the workload proxies for corresponding entities will be rendered. + GameplayObjects selectedObjects = selection->getList("DebugWorkloadSelection"); + + if (!selectedObjects.getContainsData()) { + // nothing to render + // clear item if it exists and bail + if (render::Item::isValidID(_spaceRenderItemID)) { + transaction.updateItem(_spaceRenderItemID, [](GameWorkloadRenderItem& item) { + item.setVisible(false); + }); + scene->enqueueTransaction(transaction); + } + return; + } + + std::vector entityIDs = selectedObjects.getEntityIDs(); + workload::indexed_container::Indices indices; + indices.reserve(entityIDs.size()); + + auto entityTreeRenderer = qApp->getEntities(); + auto entityTree = entityTreeRenderer->getTree(); + for (auto id : entityIDs) { + EntityItemPointer entity = entityTree->findEntityByID(id); + if (entity) { + indices.push_back(entity->getSpaceIndex()); + } + } + + workload::Proxy::Vector proxies; + proxies.reserve(indices.size()); + space->copySelectedProxyValues(proxies, indices); + + if (!render::Item::isValidID(_spaceRenderItemID)) { + _spaceRenderItemID = scene->allocateID(); + auto renderItem = std::make_shared(); + renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f); + transaction.resetItem(_spaceRenderItemID, std::make_shared(renderItem)); + } + + bool showProxies = true; + bool showViews = false; + bool visible = true; + workload::Views views(0); + transaction.updateItem(_spaceRenderItemID, [visible, showProxies, proxies, showViews, views](GameWorkloadRenderItem& item) { + item.setVisible(visible); + item.showProxies(showProxies); + item.setAllProxies(proxies); + item.showViews(showViews); + item.setAllViews(views); + }); + scene->enqueueTransaction(transaction); +} diff --git a/interface/src/workload/SelectedWorkloadRenderer.h b/interface/src/workload/SelectedWorkloadRenderer.h new file mode 100644 index 0000000000..9b3ac1005e --- /dev/null +++ b/interface/src/workload/SelectedWorkloadRenderer.h @@ -0,0 +1,32 @@ +// +// GameWorkloadRender.h +// +// Created by Sam Gateau on 2/20/2018. +// 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_SelectedWorkloadRenderer_h +#define hifi_SelectedWorkloadRenderer_h + +#include "GameWorkload.h" + +#include "GameWorkloadRenderer.h" + +class SelectedWorkloadRenderer { +public: + using Config = GameSpaceToRenderConfig; + using Outputs = render::Transaction; + using JobModel = workload::Job::ModelO; + + SelectedWorkloadRenderer() {} + + void configure(const Config& config) {} + void run(const workload::WorkloadContextPointer& renderContext, Outputs& outputs); + +protected: + render::ItemID _spaceRenderItemID{ render::Item::INVALID_ITEM_ID }; +}; + +#endif diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt index 48ae0485b5..a25aec37a7 100644 --- a/launchers/darwin/CMakeLists.txt +++ b/launchers/darwin/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.0) -set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9) +set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.11) project(HQLauncher) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") include("cmake/macros/SetPackagingParameters.cmake") diff --git a/launchers/qt/BUILD.md b/launchers/qt/BUILD.md new file mode 100644 index 0000000000..d08aa6bfa0 --- /dev/null +++ b/launchers/qt/BUILD.md @@ -0,0 +1,13 @@ +# Dependencies +- [cmake](https://cmake.org/download/): 3.9 + +# Windows +* Download `Visual Studio 2019` +`cmake -G "Visual Studio 16 2019" ..` + +# MacOS +* Install `Xcode` +`cmake -G Xcode ..` + + +If you wish to not use the compiled qml files, pass the `-DLAUNCHER_SOURCE_TREE_RESOURCES=On` argument to cmake. \ No newline at end of file diff --git a/launchers/qt/readme.md b/launchers/qt/readme.md new file mode 100644 index 0000000000..ce4e846172 --- /dev/null +++ b/launchers/qt/readme.md @@ -0,0 +1,34 @@ +# HQ Launcher +Behavior of the HQ Launcher is as follows: +* Update the HQ Launcher to the latest version +* Sign up or sign in if is the user is not already signed in +* Download the latest Interface client +* Launch the user in the current HQ domain + +# directory structure + +## src/ - contains the c++ and objective-c. +* `BuildsRequest` - getting / parsing the build info from thunder api +* `CommandlineOptions` - parses and stores commandline arguments +* `Helper` - helper functions +* `Helper_darwin` - objective-c implemention of helper funcions +* `Helper_windows` - helper function that depend on windows api +* `Launcher` - initialized the Launcher Application and resources +* `LauncherInstaller_windows` - logic of how to install/uninstall HQ Launcher on windows +* `LauncherState` - hold majority of the logic of the launcher (signin, config file, updating, running launcher) + * config files hold the following saved data + * logged in + * home location +* `LauncherWindows` - wrapper for `QQuickWindow` that implements drag feature +* `LoginRequest` - checks the login credentials the user typed in. +* `NSTask+NSTaskExecveAdditions` - Extension of NSTask for replacing Launcher process with interface client process +* `PathUtils` - Helper class for getting relative paths for HQ Launcher +* `SignupRequest` - Determines if the users request to signup for a new account succeeded based on the entered credentials +* `Unzipper` - helper class for extracting zip files +* `UserSettingsRequest` - getting the users setting (home location) from metaverse + +## resources/ +* `images/`- Holds the images and icon that are used by the launcher +* `qml/` + * UI elements + * `QML_FILE_FOR_UI_STATE` variable in `LauncherState` defines what QML files are used by the Launcher. \ No newline at end of file diff --git a/libraries/animation/src/AnimBlendDirectional.cpp b/libraries/animation/src/AnimBlendDirectional.cpp index 4e7c67f276..4cc67683da 100644 --- a/libraries/animation/src/AnimBlendDirectional.cpp +++ b/libraries/animation/src/AnimBlendDirectional.cpp @@ -96,7 +96,9 @@ const AnimPoseVec& AnimBlendDirectional::evaluate(const AnimVariantMap& animVars } } _poses.resize(minSize); - blend4(minSize, &poseVecs[0][0], &poseVecs[1][0], &poseVecs[2][0], &poseVecs[3][0], &alphas[0], &_poses[0]); + if (minSize > 0) { + blend4(minSize, &poseVecs[0][0], &poseVecs[1][0], &poseVecs[2][0], &poseVecs[3][0], &alphas[0], &_poses[0]); + } // animation stack debug stats for (int i = 0; i < 9; i++) { diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index b26d00d8d0..e5f05ab45f 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -20,24 +20,17 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) { _geometryOffset = hfmModel.offset; - // convert to std::vector of joints - std::vector joints; - joints.reserve(hfmModel.joints.size()); - for (auto& joint : hfmModel.joints) { - joints.push_back(joint); - } - buildSkeletonFromJoints(joints, hfmModel.jointRotationOffsets); + buildSkeletonFromJoints(hfmModel.joints, hfmModel.jointRotationOffsets); // we make a copy of the inverseBindMatrices in order to prevent mutating the model bind pose // when we are dealing with a joint offset in the model - for (int i = 0; i < (int)hfmModel.meshes.size(); i++) { - const HFMMesh& mesh = hfmModel.meshes.at(i); + for (uint32_t i = 0; i < (uint32_t)hfmModel.skinDeformers.size(); i++) { + const auto& deformer = hfmModel.skinDeformers[i]; std::vector dummyClustersList; - for (int j = 0; j < mesh.clusters.size(); j++) { - std::vector bindMatrices; + for (uint32_t j = 0; j < (uint32_t)deformer.clusters.size(); j++) { // cast into a non-const reference, so we can mutate the FBXCluster - HFMCluster& cluster = const_cast(mesh.clusters.at(j)); + HFMCluster& cluster = const_cast(deformer.clusters.at(j)); HFMCluster localCluster; localCluster.jointIndex = cluster.jointIndex; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index efc1c1599f..a6470ac609 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -68,7 +68,7 @@ public: void dump(const AnimPoseVec& poses) const; std::vector lookUpJointIndices(const std::vector& jointNames) const; - const HFMCluster getClusterBindMatricesOriginalValues(const int meshIndex, const int clusterIndex) const { return _clusterBindMatrixOriginalValues[meshIndex][clusterIndex]; } + const HFMCluster getClusterBindMatricesOriginalValues(int skinDeformerIndex, int clusterIndex) const { return _clusterBindMatrixOriginalValues[skinDeformerIndex][clusterIndex]; } protected: void buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets); diff --git a/libraries/animation/src/AnimationCacheScriptingInterface.h b/libraries/animation/src/AnimationCacheScriptingInterface.h index 0ceb302913..fc31ecaa2b 100644 --- a/libraries/animation/src/AnimationCacheScriptingInterface.h +++ b/libraries/animation/src/AnimationCacheScriptingInterface.h @@ -38,6 +38,10 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h index 303d0e0d9e..40fd534a71 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -23,6 +23,7 @@ class QScriptEngine; * Information about an animation resource, created by {@link AnimationCache.getAnimation}. * * @class AnimationObject + * @hideconstructor * * @hifi-interface * @hifi-client-entity @@ -57,9 +58,10 @@ public: }; /**jsdoc - * Joint rotations in one frame of an animation. + * Joint rotations in one frame of an {@link AnimationObject}. * * @class AnimationFrameObject + * @hideconstructor * * @hifi-interface * @hifi-client-entity diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 85b9ecdc42..06fe558964 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1093,6 +1093,12 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos const float TURN_ENTER_SPEED_THRESHOLD = 0.5f; // rad/sec const float TURN_EXIT_SPEED_THRESHOLD = 0.2f; // rad/sec + //stategraph vars based on input + const float INPUT_DEADZONE_THRESHOLD = 0.05f; + const float SLOW_SPEED_THRESHOLD = 1.5f; + const float HAS_MOMENTUM_THRESHOLD = 2.2f; + const float RESET_MOMENTUM_THRESHOLD = 0.05f; + if (ccState == CharacterControllerState::Hover) { if (_desiredState != RigRole::Hover) { _desiredStateAge = 0.0f; @@ -1171,6 +1177,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _desiredStateAge += deltaTime; + if (_state == RigRole::Move) { glm::vec3 horizontalVel = localVel - glm::vec3(0.0f, localVel.y, 0.0f); if (glm::length(horizontalVel) > MOVE_ENTER_SPEED_THRESHOLD) { @@ -1244,6 +1251,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotInAir", true); _animVars.set("isSeated", false); _animVars.set("isNotSeated", true); + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); } else if (_state == RigRole::Turn) { if (turningSpeed > 0.0f) { @@ -1274,6 +1284,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotInAir", true); _animVars.set("isSeated", false); _animVars.set("isNotSeated", true); + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); } else if (_state == RigRole::Idle) { // default anim vars to notMoving and notTurning @@ -1297,6 +1310,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotInAir", true); _animVars.set("isSeated", false); _animVars.set("isNotSeated", true); + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); } else if (_state == RigRole::Hover) { // flying. @@ -1320,6 +1336,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotInAir", true); _animVars.set("isSeated", false); _animVars.set("isNotSeated", true); + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); } else if (_state == RigRole::Takeoff) { // jumping in-air @@ -1351,6 +1370,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotInAir", false); _animVars.set("isSeated", false); _animVars.set("isNotSeated", true); + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); } else if (_state == RigRole::InAir) { // jumping in-air @@ -1371,6 +1393,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTakeoff", true); _animVars.set("isSeated", false); _animVars.set("isNotSeated", true); + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); bool inAirRun = forwardSpeed > 0.1f; if (inAirRun) { @@ -1393,6 +1418,23 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("inAirAlpha", alpha); } else if (_state == RigRole::Seated) { + if (fabsf(_previousControllerParameters.inputX) <= INPUT_DEADZONE_THRESHOLD) { + // seated not turning + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", true); + } else if (_previousControllerParameters.inputX > 0.0f) { + // seated turning right + _animVars.set("isSeatedTurningRight", true); + _animVars.set("isSeatedTurningLeft", false); + _animVars.set("isSeatedNotTurning", false); + } else { + // seated turning left + _animVars.set("isSeatedTurningRight", false); + _animVars.set("isSeatedTurningLeft", true); + _animVars.set("isSeatedNotTurning", false); + } + _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingRight", false); @@ -1434,20 +1476,36 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _lastEnableInverseKinematics = _enableInverseKinematics; - //stategraph vars based on input - const float INPUT_DEADZONE_THRESHOLD = 0.05f; - const float SLOW_SPEED_THRESHOLD = 1.5f; + if (fabsf(_previousControllerParameters.inputX) <= INPUT_DEADZONE_THRESHOLD && fabsf(_previousControllerParameters.inputZ) <= INPUT_DEADZONE_THRESHOLD) { // no WASD input if (fabsf(forwardSpeed) <= SLOW_SPEED_THRESHOLD && fabsf(lateralSpeed) <= SLOW_SPEED_THRESHOLD) { + + //reset this when stopped + if (fabsf(forwardSpeed) <= RESET_MOMENTUM_THRESHOLD && + fabsf(lateralSpeed) <= RESET_MOMENTUM_THRESHOLD) { + _isMovingWithMomentum = false; + } + + _animVars.set("isInputForward", false); _animVars.set("isInputBackward", false); _animVars.set("isInputRight", false); _animVars.set("isInputLeft", false); - _animVars.set("isNotInput", true); - _animVars.set("isNotInputSlow", true); + + // directly reflects input + _animVars.set("isNotInput", true); + + // no input + speed drops to SLOW_SPEED_THRESHOLD + // (don't transition run->idle - slow to walk first) + _animVars.set("isNotInputSlow", _isMovingWithMomentum); + + // no input + speed didn't get above HAS_MOMENTUM_THRESHOLD since last idle + // (brief inputs and movement adjustments) + _animVars.set("isNotInputNoMomentum", !_isMovingWithMomentum); + } else { _animVars.set("isInputForward", false); @@ -1456,8 +1514,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInputLeft", false); _animVars.set("isNotInput", true); _animVars.set("isNotInputSlow", false); + _animVars.set("isNotInputNoMomentum", false); } } else if (fabsf(_previousControllerParameters.inputZ) >= fabsf(_previousControllerParameters.inputX)) { + if (fabsf(forwardSpeed) > HAS_MOMENTUM_THRESHOLD) { + _isMovingWithMomentum = true; + } + if (_previousControllerParameters.inputZ > 0.0f) { // forward _animVars.set("isInputForward", true); @@ -1466,6 +1529,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInputLeft", false); _animVars.set("isNotInput", false); _animVars.set("isNotInputSlow", false); + _animVars.set("isNotInputNoMomentum", false); } else { // backward _animVars.set("isInputForward", false); @@ -1474,8 +1538,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInputLeft", false); _animVars.set("isNotInput", false); _animVars.set("isNotInputSlow", false); + _animVars.set("isNotInputNoMomentum", false); } } else { + if (fabsf(lateralSpeed) > HAS_MOMENTUM_THRESHOLD) { + _isMovingWithMomentum = true; + } + if (_previousControllerParameters.inputX > 0.0f) { // right if (!_headEnabled) { @@ -1489,6 +1558,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInputBackward", false); _animVars.set("isNotInput", false); _animVars.set("isNotInputSlow", false); + _animVars.set("isNotInputNoMomentum", false); } else { // left if (!_headEnabled) { @@ -1502,6 +1572,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInputRight", false); _animVars.set("isNotInput", false); _animVars.set("isNotInputSlow", false); + _animVars.set("isNotInputNoMomentum", false); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index b2b9ecd5b4..60a2602316 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -330,6 +330,7 @@ protected: glm::vec3 _lastForward; glm::vec3 _lastPosition; glm::vec3 _lastVelocity; + bool _isMovingWithMomentum{ false }; QUrl _animGraphURL; std::shared_ptr _animNode; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7494081c55..d9ad82fb51 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -111,11 +111,17 @@ QList getAvailableDevices(QAudio::Mode mode, const QString& } if (defaultDesktopDevice.getDevice().isNull()) { - qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list:" << defDeviceName - << "Setting Default to: " << devices.first().deviceName(); - defaultDesktopDevice = HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::desktop); + if (devices.size() > 0) { + qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list:" << defDeviceName + << "Setting Default to: " << devices.first().deviceName(); + newDevices.push_front(HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::desktop)); + } else { + //current audio list is empty for some reason. + qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list no alternative selection available"; + } + } else { + newDevices.push_front(defaultDesktopDevice); } - newDevices.push_front(defaultDesktopDevice); if (!hmdName.isNull()) { HifiAudioDeviceInfo hmdDevice; @@ -153,16 +159,20 @@ void AudioClient::checkDevices() { auto inputDevices = getAvailableDevices(QAudio::AudioInput, hmdInputName); auto outputDevices = getAvailableDevices(QAudio::AudioOutput, hmdOutputName); - Lock lock(_deviceMutex); - if (inputDevices != _inputDevices) { - _inputDevices.swap(inputDevices); - emit devicesChanged(QAudio::AudioInput, _inputDevices); - } + static const QMetaMethod devicesChangedSig= QMetaMethod::fromSignal(&AudioClient::devicesChanged); + //only emit once the scripting interface has connected to the signal + if (isSignalConnected(devicesChangedSig)) { + Lock lock(_deviceMutex); + if (inputDevices != _inputDevices) { + _inputDevices.swap(inputDevices); + emit devicesChanged(QAudio::AudioInput, _inputDevices); + } - if (outputDevices != _outputDevices) { - _outputDevices.swap(outputDevices); - emit devicesChanged(QAudio::AudioOutput, _outputDevices); - } + if (outputDevices != _outputDevices) { + _outputDevices.swap(outputDevices); + emit devicesChanged(QAudio::AudioOutput, _outputDevices); + } + } } HifiAudioDeviceInfo AudioClient::getActiveAudioDevice(QAudio::Mode mode) const { @@ -325,9 +335,9 @@ AudioClient::AudioClient() { connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash - getAvailableDevices(QAudio::AudioInput, QString()); - getAvailableDevices(QAudio::AudioOutput, QString()); - + defaultAudioDeviceName(QAudio::AudioInput); + defaultAudioDeviceName(QAudio::AudioOutput); + // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; @@ -777,8 +787,11 @@ void AudioClient::start() { inputName = _hmdInputName; outputName = _hmdOutputName; } + + //initialize input to the dummy device to prevent starves + switchInputToAudioDevice(HifiAudioDeviceInfo()); + switchOutputToAudioDevice(defaultAudioDeviceForMode(QAudio::AudioOutput, QString())); - #if defined(Q_OS_ANDROID) connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); _checkInputTimer.start(CHECK_INPUT_READS_MSECS); @@ -1829,6 +1842,8 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice _audioInput->deleteLater(); _audioInput = NULL; _numInputCallbackBytes = 0; + + _inputDeviceInfo.setDevice(QAudioDeviceInfo()); } if (_dummyAudioInput) { @@ -2052,6 +2067,11 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi Lock localAudioLock(_localAudioMutex); _localSamplesAvailable.exchange(0, std::memory_order_release); + //wait on local injectors prep to finish running + if ( !_localPrepInjectorFuture.isFinished()) { + _localPrepInjectorFuture.waitForFinished(); + } + // cleanup any previously initialized device if (_audioOutput) { _audioOutputIODevice.close(); @@ -2075,6 +2095,8 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi delete[] _localOutputMixBuffer; _localOutputMixBuffer = NULL; + + _outputDeviceInfo.setDevice(QAudioDeviceInfo()); } // cleanup any resamplers @@ -2328,9 +2350,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { qCDebug(audiostream, "Read %d samples from injectors (%d available, %d requested)", injectorSamplesPopped, _localInjectorsStream.samplesAvailable(), samplesRequested); } } - + // prepare injectors for the next callback - QtConcurrent::run(QThreadPool::globalInstance(), [this] { + _audio->_localPrepInjectorFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { _audio->prepareLocalAudioInjectors(); }); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index e31b4789ce..09abb2c356 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -506,7 +507,8 @@ private: #endif AudioSolo _solo; - + + QFuture _localPrepInjectorFuture; QReadWriteLock _hmdNameLock; Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; diff --git a/libraries/audio-client/src/AudioIOStats.h b/libraries/audio-client/src/AudioIOStats.h index ffd7163586..9265ae3062 100644 --- a/libraries/audio-client/src/AudioIOStats.h +++ b/libraries/audio-client/src/AudioIOStats.h @@ -40,138 +40,183 @@ class AudioStreamStatsInterface : public QObject { Q_OBJECT /**jsdoc + * Statistics for an audio stream. + * + *

Provided in properties of the {@link AudioStats} API.

+ * * @class AudioStats.AudioStreamStats + * @hideconstructor * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {number} lossRate Read-only. - * @property {number} lossCount Read-only. - * @property {number} lossRateWindow Read-only. - * @property {number} lossCountWindow Read-only. - * @property {number} framesDesired Read-only. - * @property {number} framesAvailable Read-only. - * @property {number} framesAvailableAvg Read-only. - * @property {number} unplayedMsMax Read-only. - * @property {number} starveCount Read-only. - * @property {number} lastStarveDurationCount Read-only. - * @property {number} dropCount Read-only. - * @property {number} overflowCount Read-only. - * @property {number} timegapMsMax Read-only. - * @property {number} timegapMsAvg Read-only. - * @property {number} timegapMsMaxWindow Read-only. - * @property {number} timegapMsAvgWindow Read-only. + * @property {number} dropCount - The number of silent or old audio frames dropped. + * Read-only. + * @property {number} framesAvailable - The number of audio frames containing data available. + * Read-only. + * @property {number} framesAvailableAvg - The time-weighted average of audio frames containing data available. + * Read-only. + * @property {number} framesDesired - The desired number of audio frames for the jitter buffer. + * Read-only. + * @property {number} lastStarveDurationCount - The most recent number of consecutive times that audio frames have not been + * available for processing. + * Read-only. + * @property {number} lossCount - The total number of audio packets lost. + * Read-only. + * @property {number} lossCountWindow - The number of audio packets lost since the previous statistic. + * Read-only. + * @property {number} lossRate - The ratio of the total number of audio packets lost to the total number of audio packets + * expected. + * Read-only. + * @property {number} lossRateWindow - The ratio of the number of audio packets lost to the number of audio packets + * expected since the previous statistic. + * Read-only. + * @property {number} overflowCount - The number of times that the audio ring buffer has overflowed. + * Read-only. + * @property {number} starveCount - The total number of times that audio frames have not been available for processing. + * Read-only. + * @property {number} timegapMsAvg - The overall average time between data packets, in ms. + * Read-only. + * @property {number} timegapMsAvgWindow - The recent average time between data packets, in ms. + * Read-only. + * @property {number} timegapMsMax - The overall maximum time between data packets, in ms. + * Read-only. + * @property {number} timegapMsMaxWindow - The recent maximum time between data packets, in ms. + * Read-only. + * @property {number} unplayedMsMax - The duration of audio waiting to be played, in ms. + * Read-only. */ /**jsdoc + * Triggered when the ratio of the total number of audio packets lost to the total number of audio packets expected changes. * @function AudioStats.AudioStreamStats.lossRateChanged - * @param {number} lossRate + * @param {number} lossRate - The ratio of the total number of audio packets lost to the total number of audio packets + * expected. * @returns {Signal} */ AUDIO_PROPERTY(float, lossRate) /**jsdoc + * Triggered when the total number of audio packets lost changes. * @function AudioStats.AudioStreamStats.lossCountChanged - * @param {number} lossCount + * @param {number} lossCount - The total number of audio packets lost. * @returns {Signal} */ AUDIO_PROPERTY(float, lossCount) /**jsdoc + * Triggered when the ratio of the number of audio packets lost to the number of audio packets expected since the previous + * statistic changes. * @function AudioStats.AudioStreamStats.lossRateWindowChanged - * @param {number} lossRateWindow + * @param {number} lossRateWindow - The ratio of the number of audio packets lost to the number of audio packets expected + * since the previous statistic. * @returns {Signal} */ AUDIO_PROPERTY(float, lossRateWindow) /**jsdoc + * Triggered when the number of audio packets lost since the previous statistic changes. * @function AudioStats.AudioStreamStats.lossCountWindowChanged - * @param {number} lossCountWindow + * @param {number} lossCountWindow - The number of audio packets lost since the previous statistic. * @returns {Signal} */ AUDIO_PROPERTY(float, lossCountWindow) /**jsdoc + * Triggered when the desired number of audio frames for the jitter buffer changes. * @function AudioStats.AudioStreamStats.framesDesiredChanged - * @param {number} framesDesired + * @param {number} framesDesired - The desired number of audio frames for the jitter buffer. * @returns {Signal} */ AUDIO_PROPERTY(int, framesDesired) /**jsdoc + * Triggered when the number of audio frames containing data available changes. * @function AudioStats.AudioStreamStats.framesAvailableChanged - * @param {number} framesAvailable + * @param {number} framesAvailable - The number of audio frames containing data available. * @returns {Signal} */ AUDIO_PROPERTY(int, framesAvailable) /**jsdoc + * Triggered when the time-weighted average of audio frames containing data available changes. * @function AudioStats.AudioStreamStats.framesAvailableAvgChanged - * @param {number} framesAvailableAvg + * @param {number} framesAvailableAvg - The time-weighted average of audio frames containing data available. * @returns {Signal} */ AUDIO_PROPERTY(int, framesAvailableAvg) /**jsdoc + * Triggered when the duration of audio waiting to be played changes. * @function AudioStats.AudioStreamStats.unplayedMsMaxChanged - * @param {number} unplayedMsMax + * @param {number} unplayedMsMax - The duration of audio waiting to be played, in ms. * @returns {Signal} */ AUDIO_PROPERTY(float, unplayedMsMax) /**jsdoc + * Triggered when the total number of times that audio frames have not been available for processing changes. * @function AudioStats.AudioStreamStats.starveCountChanged - * @param {number} starveCount + * @param {number} starveCount - The total number of times that audio frames have not been available for processing. * @returns {Signal} */ AUDIO_PROPERTY(int, starveCount) /**jsdoc + * Triggered when the most recenbernumber of consecutive times that audio frames have not been available for processing + * changes. * @function AudioStats.AudioStreamStats.lastStarveDurationCountChanged - * @param {number} lastStarveDurationCount + * @param {number} lastStarveDurationCount - The most recent number of consecutive times that audio frames have not been + * available for processing. * @returns {Signal} */ AUDIO_PROPERTY(int, lastStarveDurationCount) /**jsdoc + * Triggered when the number of silent or old audio frames dropped changes. * @function AudioStats.AudioStreamStats.dropCountChanged - * @param {number} dropCount + * @param {number} dropCount - The number of silent or old audio frames dropped. * @returns {Signal} */ AUDIO_PROPERTY(int, dropCount) /**jsdoc + * Triggered when the number of times that the audio ring buffer has overflowed changes. * @function AudioStats.AudioStreamStats.overflowCountChanged - * @param {number} overflowCount + * @param {number} overflowCount - The number of times that the audio ring buffer has overflowed. * @returns {Signal} */ AUDIO_PROPERTY(int, overflowCount) /**jsdoc + * Triggered when the overall maximum time between data packets changes. * @function AudioStats.AudioStreamStats.timegapMsMaxChanged - * @param {number} timegapMsMax + * @param {number} timegapMsMax - The overall maximum time between data packets, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsMax) /**jsdoc + * Triggered when the overall average time between data packets changes. * @function AudioStats.AudioStreamStats.timegapMsAvgChanged - * @param {number} timegapMsAvg + * @param {number} timegapMsAvg - The overall average time between data packets, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsAvg) /**jsdoc + * Triggered when the recent maximum time between data packets changes. * @function AudioStats.AudioStreamStats.timegapMsMaxWindowChanged - * @param {number} timegapMsMaxWindow + * @param {number} timegapMsMaxWindow - The recent maximum time between data packets, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsMaxWindow) /**jsdoc + * Triggered when the recent average time between data packets changes. * @function AudioStats.AudioStreamStats.timegapMsAvgWindowChanged - * @param {number} timegapMsAvgWindow + * @param {number} timegapMsAvgWindow - The recent average time between data packets, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsAvgWindow) @@ -188,79 +233,106 @@ class AudioStatsInterface : public QObject { Q_OBJECT /**jsdoc - * Audio stats from the client. + * The AudioStats API provides statistics of the client and mixer audio. + * * @namespace AudioStats * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {number} pingMs Read-only. - * @property {number} inputReadMsMax Read-only. - * @property {number} inputUnplayedMsMax Read-only. - * @property {number} outputUnplayedMsMax Read-only. - * @property {number} sentTimegapMsMax Read-only. - * @property {number} sentTimegapMsAvg Read-only. - * @property {number} sentTimegapMsMaxWindow Read-only. - * @property {number} sentTimegapMsAvgWindow Read-only. - * @property {AudioStats.AudioStreamStats} clientStream Read-only. - * @property {AudioStats.AudioStreamStats} mixerStream Read-only. + * @property {AudioStats.AudioStreamStats} clientStream - Statistics of the client's audio stream. + * Read-only. + * @property {number} inputReadMsMax - The maximum duration of a block of audio data recently read from the microphone, in + * ms. + * Read-only. + * @property {number} inputUnplayedMsMax - The maximum duration of microphone audio recently in the input buffer waiting to + * be played, in ms. + * Read-only. + * @property {AudioStats.AudioStreamStats} mixerStream - Statistics of the audio mixer's stream. + * Read-only. + * @property {number} outputUnplayedMsMax - The maximum duration of output audio recently in the output buffer waiting to + * be played, in ms. + * Read-only. + * @property {number} pingMs - The current ping time to the audio mixer, in ms. + * Read-only. + * @property {number} sentTimegapMsAvg - The overall average time between sending data packets to the audio mixer, in ms. + * Read-only. + * @property {number} sentTimegapMsAvgWindow - The recent average time between sending data packets to the audio mixer, in + * ms. + * Read-only. + * @property {number} sentTimegapMsMax - The overall maximum time between sending data packets to the audio mixer, in ms. + * Read-only. + * @property {number} sentTimegapMsMaxWindow - The recent maximum time between sending data packets to the audio mixer, in + * ms. + * Read-only. */ /**jsdoc + * Triggered when the ping time to the audio mixer changes. * @function AudioStats.pingMsChanged - * @param {number} pingMs + * @param {number} pingMs - The ping time to the audio mixer, in ms. * @returns {Signal} */ AUDIO_PROPERTY(float, pingMs); /**jsdoc + * Triggered when the maximum duration of a block of audio data recently read from the microphone changes. * @function AudioStats.inputReadMsMaxChanged - * @param {number} inputReadMsMax + * @param {number} inputReadMsMax - The maximum duration of a block of audio data recently read from the microphone, in ms. * @returns {Signal} */ AUDIO_PROPERTY(float, inputReadMsMax); /**jsdoc + * Triggered when the maximum duration of microphone audio recently in the input buffer waiting to be played changes. * @function AudioStats.inputUnplayedMsMaxChanged - * @param {number} inputUnplayedMsMax + * @param {number} inputUnplayedMsMax - The maximum duration of microphone audio recently in the input buffer waiting to be + * played, in ms. * @returns {Signal} */ AUDIO_PROPERTY(float, inputUnplayedMsMax); /**jsdoc + * Triggered when the maximum duration of output audio recently in the output buffer waiting to be played changes. * @function AudioStats.outputUnplayedMsMaxChanged - * @param {number} outputUnplayedMsMax + * @param {number} outputUnplayedMsMax - The maximum duration of output audio recently in the output buffer waiting to be + * played, in ms. * @returns {Signal} */ AUDIO_PROPERTY(float, outputUnplayedMsMax); /**jsdoc + * Triggered when the overall maximum time between sending data packets to the audio mixer changes. * @function AudioStats.sentTimegapMsMaxChanged - * @param {number} sentTimegapMsMax + * @param {number} sentTimegapMsMax - The overall maximum time between sending data packets to the audio mixer, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsMax); /**jsdoc + * Triggered when the overall average time between sending data packets to the audio mixer changes. * @function AudioStats.sentTimegapMsAvgChanged - * @param {number} sentTimegapMsAvg + * @param {number} sentTimegapMsAvg - The overall average time between sending data packets to the audio mixer, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsAvg); /**jsdoc + * Triggered when the recent maximum time between sending data packets to the audio mixer changes. * @function AudioStats.sentTimegapMsMaxWindowChanged - * @param {number} sentTimegapMsMaxWindow + * @param {number} sentTimegapMsMaxWindow - The recent maximum time between sending data packets to the audio mixer, in ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsMaxWindow); /**jsdoc + * Triggered when the recent average time between sending data packets to the audio mixer changes. * @function AudioStats.sentTimegapMsAvgWindowChanged - * @param {number} sentTimegapMsAvgWindow + * @param {number} sentTimegapMsAvgWindow - The recent average time between sending data packets to the audio mixer, in + * ms. * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsAvgWindow); @@ -287,18 +359,22 @@ public: signals: /**jsdoc + * Triggered when the mixer's stream statistics have been updated. * @function AudioStats.mixerStreamChanged * @returns {Signal} */ void mixerStreamChanged(); /**jsdoc + * Triggered when the client's stream statisticss have been updated. * @function AudioStats.clientStreamChanged * @returns {Signal} */ void clientStreamChanged(); /**jsdoc + * Triggered when the injector streams' statistics have been updated. + *

Note: The injector streams' statistics are currently not provided.

* @function AudioStats.injectorStreamsChanged * @returns {Signal} */ diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index e090832510..48fe44feb5 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -18,7 +18,7 @@ /**jsdoc * Audio effect options used by the {@link Audio} API. * - *

Create using new AudioEffectOptions(reverbOptions).

+ *

Create using new AudioEffectOptions(...).

* * @class AudioEffectOptions * @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] - Reverberation options. diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index e5e32781b0..be7fecb450 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -25,13 +25,6 @@ #define ALIGN32 #endif -#ifndef MAX -#define MAX(a,b) (((a) > (b)) ? (a) : (b)) -#endif -#ifndef MIN -#define MIN(a,b) (((a) < (b)) ? (a) : (b)) -#endif - // // Equal-gain crossfade // @@ -114,106 +107,49 @@ static const float nearFieldTable[NNEARFIELD][3] = { // { b0, b1, a1 } }; // -// Model the frequency-dependent attenuation of sound propogation in air. +// Parametric lowpass filter to model sound propogation or occlusion. // -// Fit using linear regression to a log-log model of lowpass cutoff frequency vs distance, -// loosely based on data from Handbook of Acoustics. Only the onset of significant -// attenuation is modelled, not the filter slope. +// lpf = 0.0 -> -3dB @ 50kHz +// lpf = 0.5 -> -3dB @ 1kHz +// lpf = 1.0 -> -3dB @ 20Hz // -// 1m -> -3dB @ 55kHz -// 10m -> -3dB @ 12kHz -// 100m -> -3dB @ 2.5kHz -// 1km -> -3dB @ 0.6kHz -// 10km -> -3dB @ 0.1kHz -// -static const int NLOWPASS = 64; -static const float lowpassTable[NLOWPASS][5] = { // { b0, b1, b2, a1, a2 } - // distance = 1 - { 0.999772371f, 1.399489756f, 0.454495527f, 1.399458985f, 0.454298669f }, - { 0.999631480f, 1.357609808f, 0.425210203f, 1.357549905f, 0.424901586f }, - { 0.999405154f, 1.311503050f, 0.394349994f, 1.311386830f, 0.393871368f }, - { 0.999042876f, 1.260674595f, 0.361869089f, 1.260450057f, 0.361136504f }, - // distance = 2 - { 0.998465222f, 1.204646525f, 0.327757118f, 1.204214978f, 0.326653886f }, - { 0.997548106f, 1.143019308f, 0.292064663f, 1.142195387f, 0.290436690f }, - { 0.996099269f, 1.075569152f, 0.254941286f, 1.074009405f, 0.252600301f }, - { 0.993824292f, 1.002389610f, 0.216688640f, 0.999469185f, 0.213433357f }, - // distance = 4 - { 0.990280170f, 0.924075266f, 0.177827150f, 0.918684864f, 0.173497723f }, - { 0.984818279f, 0.841917936f, 0.139164195f, 0.832151968f, 0.133748443f }, - { 0.976528670f, 0.758036513f, 0.101832398f, 0.740761682f, 0.095635899f }, - { 0.964216485f, 0.675305244f, 0.067243474f, 0.645654855f, 0.061110348f }, - // distance = 8 - { 0.946463038f, 0.596943020f, 0.036899688f, 0.547879974f, 0.032425772f }, - { 0.921823868f, 0.525770189f, 0.012060451f, 0.447952111f, 0.011702396f }, - { 0.890470015f, 0.463334299f, -0.001227816f, 0.347276405f, 0.005300092f }, - { 0.851335343f, 0.407521164f, -0.009353968f, 0.241900234f, 0.007602305f }, - // distance = 16 - { 0.804237360f, 0.358139558f, -0.014293332f, 0.130934213f, 0.017149373f }, - { 0.750073259f, 0.314581568f, -0.016625381f, 0.014505388f, 0.033524057f }, - { 0.690412072f, 0.275936128f, -0.017054561f, -0.106682490f, 0.055976129f }, - { 0.627245545f, 0.241342015f, -0.016246850f, -0.231302564f, 0.083643275f }, - // distance = 32 - { 0.562700627f, 0.210158533f, -0.014740899f, -0.357562697f, 0.115680957f }, - { 0.498787849f, 0.181982455f, -0.012925406f, -0.483461730f, 0.151306628f }, - { 0.437224055f, 0.156585449f, -0.011055180f, -0.607042210f, 0.189796534f }, - { 0.379336998f, 0.133834032f, -0.009281617f, -0.726580065f, 0.230469477f }, - // distance = 64 - { 0.326040627f, 0.113624970f, -0.007683443f, -0.840693542f, 0.272675696f }, - { 0.277861727f, 0.095845793f, -0.006291936f, -0.948380091f, 0.315795676f }, - { 0.234997480f, 0.080357656f, -0.005109519f, -1.049001190f, 0.359246807f }, - { 0.197386484f, 0.066993521f, -0.004122547f, -1.142236313f, 0.402493771f }, - // distance = 128 - { 0.164780457f, 0.055564709f, -0.003309645f, -1.228023442f, 0.445058962f }, - { 0.136808677f, 0.045870650f, -0.002646850f, -1.306498037f, 0.486530514f }, - { 0.113031290f, 0.037708627f, -0.002110591f, -1.377937457f, 0.526566783f }, - { 0.092980475f, 0.030881892f, -0.001679255f, -1.442713983f, 0.564897095f }, - // distance = 256 - { 0.076190239f, 0.025205585f, -0.001333863f, -1.501257246f, 0.601319206f }, - { 0.062216509f, 0.020510496f, -0.001058229f, -1.554025452f, 0.635694228f }, - { 0.050649464f, 0.016644994f, -0.000838826f, -1.601484205f, 0.667939837f }, - { 0.041120009f, 0.013475547f, -0.000664513f, -1.644091518f, 0.698022561f }, - // distance = 512 - { 0.033302044f, 0.010886252f, -0.000526217f, -1.682287704f, 0.725949783f }, - { 0.026911868f, 0.008777712f, -0.000416605f, -1.716488979f, 0.751761953f }, - { 0.021705773f, 0.007065551f, -0.000329788f, -1.747083800f, 0.775525335f }, - { 0.017476603f, 0.005678758f, -0.000261057f, -1.774431204f, 0.797325509f }, - // distance = 1024 - { 0.014049828f, 0.004558012f, -0.000206658f, -1.798860530f, 0.817261711f }, - { 0.011279504f, 0.003654067f, -0.000163610f, -1.820672082f, 0.835442043f }, - { 0.009044384f, 0.002926264f, -0.000129544f, -1.840138412f, 0.851979516f }, - { 0.007244289f, 0.002341194f, -0.000102586f, -1.857505967f, 0.866988864f }, - // distance = 2048 - { 0.005796846f, 0.001871515f, -0.000081250f, -1.872996926f, 0.880584038f }, - { 0.004634607f, 0.001494933f, -0.000064362f, -1.886811124f, 0.892876302f }, - { 0.003702543f, 0.001193324f, -0.000050993f, -1.899127955f, 0.903972829f }, - { 0.002955900f, 0.000951996f, -0.000040407f, -1.910108223f, 0.913975712f }, - // distance = 4096 - { 0.002358382f, 0.000759068f, -0.000032024f, -1.919895894f, 0.922981321f }, - { 0.001880626f, 0.000604950f, -0.000025383f, -1.928619738f, 0.931079931f }, - { 0.001498926f, 0.000481920f, -0.000020123f, -1.936394836f, 0.938355560f }, - { 0.001194182f, 0.000383767f, -0.000015954f, -1.943323983f, 0.944885977f }, - // distance = 8192 - { 0.000951028f, 0.000305502f, -0.000012651f, -1.949498943f, 0.950742822f }, - { 0.000757125f, 0.000243126f, -0.000010033f, -1.955001608f, 0.955991826f }, - { 0.000602572f, 0.000193434f, -0.000007957f, -1.959905036f, 0.960693085f }, - { 0.000479438f, 0.000153861f, -0.000006312f, -1.964274383f, 0.964901371f }, - // distance = 16384 - { 0.000381374f, 0.000122359f, -0.000005007f, -1.968167752f, 0.968666478f }, - { 0.000303302f, 0.000097288f, -0.000003972f, -1.971636944f, 0.972033562f }, - { 0.000241166f, 0.000077342f, -0.000003151f, -1.974728138f, 0.975043493f }, - { 0.000191726f, 0.000061475f, -0.000002500f, -1.977482493f, 0.977733194f }, - // distance = 32768 - { 0.000152399f, 0.000048857f, -0.000001984f, -1.979936697f, 0.980135969f }, - { 0.000121122f, 0.000038825f, -0.000001574f, -1.982123446f, 0.982281818f }, - { 0.000096252f, 0.000030849f, -0.000001249f, -1.984071877f, 0.984197728f }, - { 0.000076480f, 0.000024509f, -0.000000991f, -1.985807957f, 0.985907955f }, +static const int NLOWPASS = 32; +static const float lowpassTable[NLOWPASS+1][5] = { // { b0, b1, b2, a1, a2 } + { 0.9996582613f, 1.3644521648f, 0.4299107175f, 1.3643981990f, 0.4296229446f }, + { 0.9990601568f, 1.2627213717f, 0.3631477252f, 1.2625024258f, 0.3624268280f }, + { 0.9974547575f, 1.1378303854f, 0.2891398515f, 1.1369629374f, 0.2874620569f }, + { 0.9932384344f, 0.9872078424f, 0.2089943789f, 0.9839050501f, 0.2055356056f }, + { 0.9825457933f, 0.8153687744f, 0.1271135720f, 0.8036320348f, 0.1213961050f }, + { 0.9572356804f, 0.6404312275f, 0.0534129844f, 0.6033230637f, 0.0477568288f }, + { 0.9052878744f, 0.4902779401f, 0.0035032262f, 0.3924772681f, 0.0065917726f }, + { 0.8204774205f, 0.3736089028f, -0.0129974730f, 0.1678426876f, 0.0132461627f }, + { 0.7032096959f, 0.2836328681f, -0.0170877258f, -0.0810811878f, 0.0508360260f }, + { 0.5685067272f, 0.2128349296f, -0.0148937235f, -0.3461942779f, 0.1126422113f }, + { 0.4355093111f, 0.1558974062f, -0.0110025095f, -0.6105302595f, 0.1909344673f }, + { 0.3186188589f, 0.1108581568f, -0.0074653192f, -0.8569688248f, 0.2789805212f }, + { 0.2244962739f, 0.0766060095f, -0.0048289293f, -1.0745081373f, 0.3707814914f }, + { 0.1535044640f, 0.0516447640f, -0.0030384640f, -1.2590370066f, 0.4611477706f }, + { 0.1025113288f, 0.0341204303f, -0.0018818088f, -1.4113207964f, 0.5460707468f }, + { 0.0672016063f, 0.0221823522f, -0.0011552756f, -1.5347007285f, 0.6229294113f }, + { 0.0434202931f, 0.0142393067f, -0.0007060306f, -1.6334567973f, 0.6904103664f }, + { 0.0277383489f, 0.0090500025f, -0.0004305987f, -1.7118804671f, 0.7482382198f }, + { 0.0175636227f, 0.0057072537f, -0.0002624537f, -1.7738404438f, 0.7968488665f }, + { 0.0110441068f, 0.0035773504f, -0.0001599927f, -1.8226329785f, 0.8370944430f }, + { 0.0069069312f, 0.0022316608f, -0.0000975848f, -1.8609764152f, 0.8700174224f }, + { 0.0043012064f, 0.0013870046f, -0.0000595614f, -1.8910688315f, 0.8966974811f }, + { 0.0026696068f, 0.0008595333f, -0.0000363798f, -1.9146662133f, 0.9181589737f }, + { 0.0016526098f, 0.0005314445f, -0.0000222355f, -1.9331608518f, 0.9353226705f }, + { 0.0010209520f, 0.0003280036f, -0.0000135987f, -1.9476515008f, 0.9489868578f }, + { 0.0006297162f, 0.0002021591f, -0.0000083208f, -1.9590027292f, 0.9598262837f }, + { 0.0003879180f, 0.0001244611f, -0.0000050936f, -1.9678935939f, 0.9684008793f }, + { 0.0002387308f, 0.0000765601f, -0.0000031192f, -1.9748568416f, 0.9751690132f }, + { 0.0001468057f, 0.0000470631f, -0.0000019106f, -1.9803101382f, 0.9805020963f }, + { 0.0000902227f, 0.0000289155f, -0.0000011706f, -1.9845807858f, 0.9846987534f }, + { 0.0000554223f, 0.0000177584f, -0.0000007174f, -1.9879252038f, 0.9879976671f }, + { 0.0000340324f, 0.0000109027f, -0.0000004397f, -1.9905442465f, 0.9905887419f }, + { 0.0000208917f, 0.0000066920f, -0.0000002695f, -1.9925952275f, 0.9926225417f }, }; -static const float HALFPI = 1.570796327f; -static const float PI = 3.141592654f; -static const float TWOPI = 6.283185307f; - // // on x86 architecture, assume that SSE2 is present // @@ -811,44 +747,38 @@ static void splitf(float x, int& expn, float& frac) { expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS; } -static void distanceBiquad(float distance, float& b0, float& b1, float& b2, float& a1, float& a2) { +static void lowpassBiquad(float lpf, float& b0, float& b1, float& b2, float& a1, float& a2) { // - // Computed from a lookup table quantized to distance = 2^(N/4) - // and reconstructed by piecewise linear interpolation. + // Computed from a lookup table and piecewise linear interpolation. // Approximation error < 0.25dB // + float x = lpf * NLOWPASS; - float x = distance; - x = MIN(x, 1<<30); - x *= x; - x *= x; // x = distance^4 - - // split x into e and frac, such that x = 2^(e+0) + frac * (2^(e+1) - 2^(e+0)) - int e; - float frac; - splitf(x, e, frac); + // split x into index and fraction + int i = (int)x; + float frac = x - (float)i; // clamp to table limits - if (e < 0) { - e = 0; + if (i < 0) { + i = 0; frac = 0.0f; } - if (e > NLOWPASS-2) { - e = NLOWPASS-2; + if (i > NLOWPASS-1) { + i = NLOWPASS-1; frac = 1.0f; } assert(frac >= 0.0f); assert(frac <= 1.0f); - assert(e+0 >= 0); - assert(e+1 < NLOWPASS); + assert(i+0 >= 0); + assert(i+1 <= NLOWPASS); // piecewise linear interpolation - b0 = lowpassTable[e+0][0] + frac * (lowpassTable[e+1][0] - lowpassTable[e+0][0]); - b1 = lowpassTable[e+0][1] + frac * (lowpassTable[e+1][1] - lowpassTable[e+0][1]); - b2 = lowpassTable[e+0][2] + frac * (lowpassTable[e+1][2] - lowpassTable[e+0][2]); - a1 = lowpassTable[e+0][3] + frac * (lowpassTable[e+1][3] - lowpassTable[e+0][3]); - a2 = lowpassTable[e+0][4] + frac * (lowpassTable[e+1][4] - lowpassTable[e+0][4]); + b0 = lowpassTable[i+0][0] + frac * (lowpassTable[i+1][0] - lowpassTable[i+0][0]); + b1 = lowpassTable[i+0][1] + frac * (lowpassTable[i+1][1] - lowpassTable[i+0][1]); + b2 = lowpassTable[i+0][2] + frac * (lowpassTable[i+1][2] - lowpassTable[i+0][2]); + a1 = lowpassTable[i+0][3] + frac * (lowpassTable[i+1][3] - lowpassTable[i+0][3]); + a2 = lowpassTable[i+0][4] + frac * (lowpassTable[i+1][4] - lowpassTable[i+0][4]); } // @@ -903,13 +833,13 @@ static void nearFieldGainCorrection(float azimuth, float distance, float& gainL, float d = (HRTF_NEARFIELD_MAX - distance) * (1.0f / (HRTF_NEARFIELD_MAX - HRTF_HEAD_RADIUS)); // angle of incidence at each ear - float angleL = azimuth + HALFPI; - float angleR = azimuth - HALFPI; + float angleL = azimuth + PI_OVER_TWO; + float angleR = azimuth - PI_OVER_TWO; if (angleL > +PI) { - angleL -= TWOPI; + angleL -= TWO_PI; } if (angleR < -PI) { - angleR += TWOPI; + angleR += TWO_PI; } assert(angleL >= -PI); assert(angleL <= +PI); @@ -968,7 +898,7 @@ static void nearFieldFilter(float gain, float& b0, float& b1, float& a1) { static void azimuthToIndex(float azimuth, int& index0, int& index1, float& frac) { // convert from radians to table units - azimuth *= (HRTF_AZIMUTHS / TWOPI); + azimuth *= (HRTF_AZIMUTHS / TWO_PI); if (azimuth < 0.0f) { azimuth += HRTF_AZIMUTHS; @@ -993,15 +923,15 @@ static void azimuthToIndex(float azimuth, int& index0, int& index1, float& frac) // compute new filters for a given azimuth, distance and gain static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int delay[4], - int index, float azimuth, float distance, float gain, int channel) { + int index, float azimuth, float distance, float gain, float lpf, int channel) { if (azimuth > PI) { - azimuth -= TWOPI; + azimuth -= TWO_PI; } assert(azimuth >= -PI); assert(azimuth <= +PI); - distance = MAX(distance, HRTF_NEARFIELD_MIN); + distance = std::max(distance, HRTF_NEARFIELD_MIN); // compute the azimuth correction at each ear float azimuthL = azimuth; @@ -1109,7 +1039,7 @@ static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int dela } else { - distanceBiquad(distance, b0, b1, b2, a1, a2); + lowpassBiquad(lpf, b0, b1, b2, a1, a2); bqCoef[0][channel+4] = b0; bqCoef[1][channel+4] = b1; @@ -1125,7 +1055,8 @@ static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int dela } } -void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames) { +void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames, + float lpfDistance) { assert(index >= 0); assert(index < HRTF_TABLES); @@ -1141,23 +1072,29 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, // apply global and local gain adjustment gain *= _gainAdjust; + // apply distance filter + float lpf = 0.5f * fastLog2f(std::max(distance, 1.0f)) / fastLog2f(std::max(lpfDistance, 2.0f)); + lpf = std::min(std::max(lpf, 0.0f), 1.0f); + // disable interpolation from reset state if (_resetState) { _azimuthState = azimuth; _distanceState = distance; _gainState = gain; + _lpfState = lpf; } // to avoid polluting the cache, old filters are recomputed instead of stored - setFilters(firCoef, bqCoef, delay, index, _azimuthState, _distanceState, _gainState, L0); + setFilters(firCoef, bqCoef, delay, index, _azimuthState, _distanceState, _gainState, _lpfState, L0); // compute new filters - setFilters(firCoef, bqCoef, delay, index, azimuth, distance, gain, L1); + setFilters(firCoef, bqCoef, delay, index, azimuth, distance, gain, lpf, L1); // new parameters become old _azimuthState = azimuth; _distanceState = distance; _gainState = gain; + _lpfState = lpf; // convert mono input to float for (int i = 0; i < HRTF_BLOCK; i++) { diff --git a/libraries/audio/src/AudioHRTF.h b/libraries/audio/src/AudioHRTF.h index 436d6318a5..6bbd9bc08a 100644 --- a/libraries/audio/src/AudioHRTF.h +++ b/libraries/audio/src/AudioHRTF.h @@ -14,6 +14,9 @@ #include #include +#include + +#include "AudioHelpers.h" static const int HRTF_AZIMUTHS = 72; // 360 / 5-degree steps static const int HRTF_TAPS = 64; // minimum-phase FIR coefficients @@ -34,6 +37,9 @@ static const float HRTF_HEAD_RADIUS = 0.0875f; // average human head in meters static const float ATTN_DISTANCE_REF = 2.0f; // distance where attn is 0dB static const float ATTN_GAIN_MAX = 16.0f; // max gain allowed by distance attn (+24dB) +// Distance filter +static const float LPF_DISTANCE_REF = 256.0f; // approximation of sound propogation in air + class AudioHRTF { public: @@ -47,8 +53,10 @@ public: // distance: source distance in meters // gain: gain factor for distance attenuation // numFrames: must be HRTF_BLOCK in this version + // lpfDistance: distance filter adjustment (distance to 1kHz lowpass in meters) // - void render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames); + void render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames, + float lpfDistance = LPF_DISTANCE_REF); // // Non-spatialized direct mix (accumulates into existing output) @@ -59,11 +67,14 @@ public: // // Fast path when input is known to be silent and state as been flushed // - void setParameterHistory(float azimuth, float distance, float gain) { + void setParameterHistory(float azimuth, float distance, float gain, float lpfDistance = LPF_DISTANCE_REF) { // new parameters become old _azimuthState = azimuth; _distanceState = distance; _gainState = gain; + + _lpfState = 0.5f * fastLog2f(std::max(distance, 1.0f)) / fastLog2f(std::max(lpfDistance, 2.0f)); + _lpfState = std::min(std::max(_lpfState, 0.0f), 1.0f); } // @@ -88,6 +99,7 @@ public: _azimuthState = 0.0f; _distanceState = 0.0f; _gainState = 0.0f; + _lpfState = 0.0f; // _gainAdjust is retained @@ -123,6 +135,7 @@ private: float _azimuthState = 0.0f; float _distanceState = 0.0f; float _gainState = 0.0f; + float _lpfState = 0.0f; // global and local gain adjustment float _gainAdjust = HRTF_GAIN; diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 62fdb9dcdc..205e1cba33 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -130,6 +130,7 @@ typedef QSharedPointer SharedSoundPointer; * * * @class SoundObject + * @hideconstructor * * @hifi-interface * @hifi-client-entity diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index ea767e00b4..28425f0406 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -39,6 +39,10 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 868d49bb5c..3792057052 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -333,18 +333,22 @@ void Avatar::setTargetScale(float targetScale) { } void Avatar::removeAvatarEntitiesFromTree() { + if (_packedAvatarEntityData.empty()) { + return; + } auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { - QList avatarEntityIDs; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityIDs = _packedAvatarEntityData.keys(); - }); - entityTree->withWriteLock([&] { - for (const auto& entityID : avatarEntityIDs) { - entityTree->deleteEntity(entityID, true, true); - } - }); + std::vector ids; + ids.reserve(_packedAvatarEntityData.size()); + PackedAvatarEntityMap::const_iterator itr = _packedAvatarEntityData.constBegin(); + while (itr != _packedAvatarEntityData.constEnd()) { + ids.push_back(itr.key()); + ++itr; + } + bool force = true; + bool ignoreWarnings = true; + entityTree->deleteEntitiesByID(ids, force, ignoreWarnings); // locks tree } } @@ -939,7 +943,7 @@ void Avatar::simulateAttachments(float deltaTime) { bool texturesLoaded = _attachmentModelsTexturesLoaded.at(i); // Watch for texture loading - if (!texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + if (!texturesLoaded && model->getNetworkModel() && model->getNetworkModel()->areTexturesLoaded()) { _attachmentModelsTexturesLoaded[i] = true; model->updateRenderItems(); } @@ -1916,6 +1920,13 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) { } } +/**jsdoc + * Information about a joint in an avatar's skeleton hierarchy. + * @typedef {object} SkeletonJoint + * @property {string} name - Joint name. + * @property {number} index - Joint index. + * @property {number} parentIndex - Index of this joint's parent (-1 if no parent). + */ QList Avatar::getSkeleton() { SkeletonModelPointer skeletonModel = _skeletonModel; if (skeletonModel) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 6e5f22f473..cd1fc7c7fc 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -442,7 +442,7 @@ public: void setOrientationViaScript(const glm::quat& orientation) override; /**jsdoc - * Gets the ID of the entity of avatar that the avatar is parented to. + * Gets the ID of the entity or avatar that the avatar is parented to. * @function MyAvatar.getParentID * @returns {Uuid} The ID of the entity or avatar that the avatar is parented to. {@link Uuid(0)|Uuid.NULL} if not parented. */ @@ -450,7 +450,7 @@ public: Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); } /**jsdoc - * Sets the ID of the entity of avatar that the avatar is parented to. + * Sets the ID of the entity or avatar that the avatar is parented to. * @function MyAvatar.setParentID * @param {Uuid} parentID - The ID of the entity or avatar that the avatar should be parented to. Set to * {@link Uuid(0)|Uuid.NULL} to unparent. @@ -480,14 +480,7 @@ public: /**jsdoc * Gets information on all the joints in the avatar's skeleton. * @function MyAvatar.getSkeleton - * @returns {MyAvatar.SkeletonJoint[]} Information about each joint in the avatar's skeleton. - */ - /**jsdoc - * Information about a single joint in an Avatar's skeleton hierarchy. - * @typedef {object} MyAvatar.SkeletonJoint - * @property {string} name - Joint name. - * @property {number} index - Joint index. - * @property {number} parentIndex - Index of this joint's parent (-1 if no parent). + * @returns {SkeletonJoint[]} Information about each joint in the avatar's skeleton. */ Q_INVOKABLE QList getSkeleton(); @@ -662,6 +655,7 @@ protected: std::vector> _attachmentsToDelete; float _bodyYawDelta { 0.0f }; // degrees/sec + float _seatedBodyYawDelta{ 0.0f }; // degrees/renderframe // These position histories and derivatives are in the world-frame. // The derivatives are the MEASURED results of all external and internal forces diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index b8bc7a03e8..ee0543fa6b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -96,17 +96,21 @@ void Head::simulate(float deltaTime) { const float BLINK_START_VARIABILITY = 0.25f; const float FULLY_OPEN = 0.0f; const float FULLY_CLOSED = 1.0f; + const float TALKING_LOUDNESS = 150.0f; + + _timeWithoutTalking += deltaTime; + if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { + _timeWithoutTalking = 0.0f; + } + if (getProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation) && !getSuppressProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation)) { + // handle automatic blinks // Detect transition from talking to not; force blink after that and a delay bool forceBlink = false; - const float TALKING_LOUDNESS = 150.0f; const float BLINK_AFTER_TALKING = 0.25f; - _timeWithoutTalking += deltaTime; - if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { - _timeWithoutTalking = 0.0f; - } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { + if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { forceBlink = true; } if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { @@ -150,11 +154,13 @@ void Head::simulate(float deltaTime) { } else { _rightEyeBlink = FULLY_OPEN; _leftEyeBlink = FULLY_OPEN; + updateEyeLookAt(); } // use data to update fake Faceshift blendshape coefficients if (getProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation) && !getSuppressProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation)) { + // Update audio attack data for facial animation (eyebrows and mouth) float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz _audioAttack = audioAttackAveragingRate * _audioAttack + @@ -188,6 +194,7 @@ void Head::simulate(float deltaTime) { if (getProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation) && !getSuppressProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation)) { + // This controls two things, the eye brow and the upper eye lid, it is driven by the vertical up/down angle of the // eyes relative to the head. This is to try to help prevent sleepy eyes/crazy eyes. applyEyelidOffset(getOrientation()); diff --git a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h index 3bc98e72a0..0b89e9d59e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h @@ -16,6 +16,64 @@ #include "Avatar.h" +/**jsdoc + * Information about an avatar. + * + *

Create using {@link MyAvatar.getTargetAvatar} or {@link AvatarList.getAvatar}.

+ * + * @class ScriptAvatar + * @hideconstructor + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-assignment-client + * @hifi-server-entity + * + * @property {Vec3} position - The avatar's position. + * @property {number} scale - The target scale of the avatar without any restrictions on permissible values imposed by the + * domain. + * @property {Vec3} handPosition - A user-defined hand position, in world coordinates. The position moves with the avatar but + * is otherwise not used or changed by Interface. + * @property {number} bodyPitch - The pitch of the avatar's body, in degrees. + * @property {number} bodyYaw - The yaw of the avatar's body, in degrees. + * @property {number} bodyRoll - The roll of the avatar's body, in degrees. + * @property {Quat} orientation - The orientation of the avatar's body. + * @property {Quat} headOrientation - The orientation of the avatar's head. + * @property {number} headPitch - The pitch of the avatar's head relative to the body, in degrees. + * @property {number} headYaw - The yaw of the avatar's head relative to the body, in degrees. + * @property {number} headRoll - The roll of the avatar's head relative to the body, in degrees. + * + * @property {Vec3} velocity - The linear velocity of the avatar. + * @property {Vec3} angularVelocity - The angular velocity of the avatar. + * + * @property {Uuid} sessionUUID - The avatar's session ID. + * @property {string} displayName - The avatar's display name. + * @property {string} sessionDisplayName - The avatar's display name, sanitized and versioned, as defined by the avatar mixer. + * It is unique among all avatars present in the domain at the time. + * @property {boolean} isReplicated - Deprecated: This property is deprecated and will be + * removed. + * @property {boolean} lookAtSnappingEnabled - true if the avatar's eyes snap to look at another avatar's eyes + * when the other avatar is in the line of sight and also has lookAtSnappingEnabled == true. + * + * @property {string} skeletonModelURL - The avatar's FST file. + * @property {AttachmentData[]} attachmentData - Information on the avatar's attachments. + *

Deprecated: This property is deprecated and will be removed. Use avatar entities instead.

+ * @property {string[]} jointNames - The list of joints in the avatar model. + * + * @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the + * domain. + * @property {number} audioAverageLoudness - The rolling average loudness of the audio input that the avatar is injecting into + * the domain. + * + * @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the + * avatar's size, orientation, and position in the virtual world. + * @property {Mat4} controllerLeftHandMatrix - The rotation and translation of the left hand controller relative to the avatar. + * @property {Mat4} controllerRightHandMatrix - The rotation and translation of the right hand controller relative to the + * avatar. + * + * @property {Vec3} skeletonOffset - The rendering offset of the avatar. + */ class ScriptAvatar : public ScriptAvatarData { Q_OBJECT @@ -26,27 +84,138 @@ public: public slots: + /**jsdoc + * Gets the default rotation of a joint in the avatar relative to its parent. + *

For information on the joint hierarchy used, see + * Avatar Standards.

+ * @function ScriptAvatar.getDefaultJointRotation + * @param {number} index - The joint index. + * @returns {Quat} The default rotation of the joint if avatar data are available and the joint index is valid, otherwise + * {@link Quat(0)|Quat.IDENTITY}. + */ glm::quat getDefaultJointRotation(int index) const; + + /**jsdoc + * Gets the default translation of a joint in the avatar relative to its parent, in model coordinates. + *

Warning: These coordinates are not necessarily in meters.

+ *

For information on the joint hierarchy used, see + * Avatar Standards.

+ * @function ScriptAvatar.getDefaultJointTranslation + * @param {number} index - The joint index. + * @returns {Vec3} The default translation of the joint (in model coordinates) if avatar data are available and the joint + * index is valid, otherwise {@link Vec3(0)|Vec3.ZERO}. + */ glm::vec3 getDefaultJointTranslation(int index) const; + + /**jsdoc + * Gets the offset applied to the avatar for rendering. + * @function ScriptAvatar.getSkeletonOffset + * @returns {Vec3} The skeleton offset if avatar data are available, otherwise {@link Vec3(0)|Vec3.ZERO}. + */ glm::vec3 getSkeletonOffset() const; + + /**jsdoc + * Gets the position of a joint in the avatar. + * @function ScriptAvatar.getJointPosition + * @param {number} index - The index of the joint. + * @returns {Vec3} The position of the joint in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't + * available. + */ glm::vec3 getJointPosition(int index) const; + + /**jsdoc + * Gets the position of a joint in the current avatar. + * @function ScriptAvatar.getJointPosition + * @param {string} name - The name of the joint. + * @returns {Vec3} The position of the joint in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't + * available. + */ glm::vec3 getJointPosition(const QString& name) const; + + /**jsdoc + * Gets the position of the current avatar's neck in world coordinates. + * @function ScriptAvatar.getNeckPosition + * @returns {Vec3} The position of the neck in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't + * available. + */ glm::vec3 getNeckPosition() const; + + /**jsdoc + * Gets the current acceleration of the avatar. + * @function ScriptAvatar.getAcceleration + * @returns {Vec3} The current acceleration of the avatar, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't available.. + */ glm::vec3 getAcceleration() const; + + /**jsdoc + * Gets the ID of the entity or avatar that the avatar is parented to. + * @function ScriptAvatar.getParentID + * @returns {Uuid} The ID of the entity or avatar that the avatar is parented to. {@link Uuid(0)|Uuid.NULL} if not parented + * or avatar data aren't available. + */ QUuid getParentID() const; + + /**jsdoc + * Gets the joint of the entity or avatar that the avatar is parented to. + * @function ScriptAvatar.getParentJointIndex + * @returns {number} The joint of the entity or avatar that the avatar is parented to. 65535 or + * -1 if parented to the entity or avatar's position and orientation rather than a joint, or avatar data + * aren't available. + */ quint16 getParentJointIndex() const; + + /**jsdoc + * Gets information on all the joints in the avatar's skeleton. + * @function ScriptAvatar.getSkeleton + * @returns {SkeletonJoint[]} Information about each joint in the avatar's skeleton. + */ QVariantList getSkeleton() const; + + /**jsdoc + * @function ScriptAvatar.getSimulationRate + * @param {AvatarSimulationRate} [rateName=""] - Rate name. + * @returns {number} Simulation rate in Hz, or 0.0 if avatar data aren't available. + * @deprecated This function is deprecated and will be removed. + */ float getSimulationRate(const QString& rateName = QString("")) const; + + /**jsdoc + * Gets the position of the left palm in world coordinates. + * @function ScriptAvatar.getLeftPalmPosition + * @returns {Vec3} The position of the left palm in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't + * available. + */ glm::vec3 getLeftPalmPosition() const; + + /**jsdoc + * Gets the rotation of the left palm in world coordinates. + * @function ScriptAvatar.getLeftPalmRotation + * @returns {Quat} The rotation of the left palm in world coordinates, or {@link Quat(0)|Quat.IDENTITY} if the avatar data + * aren't available. + */ glm::quat getLeftPalmRotation() const; + + /**jsdoc + * Gets the position of the right palm in world coordinates. + * @function ScriptAvatar.getLeftPalmPosition + * @returns {Vec3} The position of the right palm in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't + * available. + */ glm::vec3 getRightPalmPosition() const; + + /**jsdoc + * Gets the rotation of the right palm in world coordinates. + * @function ScriptAvatar.getLeftPalmRotation + * @returns {Quat} The rotation of the right palm in world coordinates, or {@link Quat(0)|Quat.IDENTITY} if the avatar data + * aren't available. + */ glm::quat getRightPalmRotation() const; private: diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index ccc87c28f3..e9e26ca716 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -171,7 +171,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem, // but Avatars don't get updates in the same way - if (!_texturesLoaded && getGeometry() && getGeometry()->areTexturesLoaded()) { + if (!_texturesLoaded && getNetworkModel() && getNetworkModel()->areTexturesLoaded()) { _texturesLoaded = true; updateRenderItems(); } @@ -326,7 +326,7 @@ void SkeletonModel::computeBoundingShape() { } const HFMModel& hfmModel = getHFMModel(); - if (hfmModel.joints.isEmpty() || _rig.indexOfJoint("Hips") == -1) { + if (hfmModel.joints.empty() || _rig.indexOfJoint("Hips") == -1) { // rootJointIndex == -1 if the avatar model has no skeleton return; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 93850197af..adb7222ee3 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -376,7 +376,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent tranlationChangedSince(lastSentTime) || parentInfoChangedSince(lastSentTime)); hasHandControllers = _controllerLeftHandMatrixCache.isValid() || _controllerRightHandMatrixCache.isValid(); - hasFaceTrackerInfo = !dropFaceTracking && getHasScriptedBlendshapes() && + hasFaceTrackerInfo = !dropFaceTracking && (getHasScriptedBlendshapes() || _headData->_hasInputDrivenBlendshapes) && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = !sendMinimum; hasJointDefaultPoseFlags = hasJointData; @@ -3033,15 +3033,12 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent } void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { - + // NOTE: requiresRemovalFromTree is unused bool removedEntity = false; - _avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] { removedEntity = _packedAvatarEntityData.remove(entityID); }); - insertRemovedEntityID(entityID); - if (removedEntity && _clientTraitsHandler) { // we have a client traits handler, so we need to mark this removed instance trait as deleted // so that changes are sent next frame @@ -3055,6 +3052,12 @@ AvatarEntityMap AvatarData::getAvatarEntityData() const { return AvatarEntityMap(); } +AvatarEntityMap AvatarData::getAvatarEntityDataNonDefault() const { + // overridden where needed + // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs + return AvatarEntityMap(); +} + void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { // overridden where needed // avatarEntityData is expected to be a map of QByteArrays diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index cc2dd0b7da..d6a4497ee7 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -532,19 +532,22 @@ class AvatarData : public QObject, public SpatiallyNestable { * size in the virtual world. Read-only. * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. * Read-only. - * @property {boolean} hasScriptedBlendshapes=false - Set this to true before using the {@link MyAvatar.setBlendshape} method, - * after you no longer want scripted control over the blendshapes set to back to false.
NOTE: this property will - * automatically become true if the Controller system has valid facial blendshape actions. - * @property {boolean} hasProceduralBlinkFaceMovement=true - By default avatars will blink automatically by animating facial - * blendshapes. Set this property to false to disable this automatic blinking. This can be useful if you - * wish to fully control the blink facial blendshapes via the {@link MyAvatar.setBlendshape} method. - * @property {boolean} hasProceduralEyeFaceMovement=true - By default the avatar eye facial blendshapes will be adjusted - * automatically as the eyes move. This will prevent the iris is never obscured by the upper or lower lids. Set this - * property to false to disable this automatic movement. This can be useful if you wish to fully control - * the eye blendshapes via the {@link MyAvatar.setBlendshape} method. - * @property {boolean} hasAudioEnabledFaceMovement=true - By default the avatar mouth blendshapes will animate based on - * the microphone audio. Set this property to false to disable that animaiton. This can be useful if you - * wish to fully control the blink facial blendshapes via the {@link MyAvatar.setBlendshape} method. + * @property {boolean} hasScriptedBlendshapes=false - true if blend shapes are controlled by scripted actions, + * otherwise false. Set this to true before using the {@link Avatar.setBlendshape} method, + * and set back to false after you no longer want scripted control over the blend shapes. + *

Note: This property will automatically be set to true if the controller system has + * valid facial blend shape actions.

+ * @property {boolean} hasProceduralBlinkFaceMovement=true - true if avatars blink automatically by animating + * facial blend shapes, false if automatic blinking is disabled. Set to false to fully control + * the blink facial blend shapes via the {@link Avatar.setBlendshape} method. + * @property {boolean} hasProceduralEyeFaceMovement=true - true if the facial blend shapes for an avatar's eyes + * adjust automatically as the eyes move, false if this automatic movement is disabled. Set this property + * to true to prevent the iris from being obscured by the upper or lower lids. Set to false to + * fully control the eye blend shapes via the {@link Avatar.setBlendshape} method. + * @property {boolean} hasAudioEnabledFaceMovement=true - true if the avatar's mouth blend shapes animate + * automatically based on detected microphone input, false if this automatic movement is disabled. Set + * this property to false to fully control the mouth facial blend shapes via the + * {@link Avatar.setBlendshape} method. */ Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale) @@ -1130,9 +1133,9 @@ public: /**jsdoc - * Sets the value of a blendshape to animate your avatar's face. To enable other users to see the resulting animation of - * your avatar's face, set {@link Avatar.hasScriptedBlendshapes} to true while using this API and back to false when your - * animation is complete. + * Sets the value of a blend shape to animate your avatar's face. In order for other users to see the resulting animations + * on your avatar's face, set hasScriptedBlendshapes to true. When you are done using this API, + * set hasScriptedBlendshapes back to false when the animation is complete. * @function Avatar.setBlendshape * @param {string} name - The name of the blendshape, per the * {@link https://docs.projectathena.dev/create/avatars/avatar-standards.html#blendshapes Avatar Standards}. @@ -1171,7 +1174,7 @@ public: /**jsdoc * @function Avatar.updateAvatarEntity * @param {Uuid} entityID - The entity ID. - * @param {Array.} entityData - Entity data. + * @param {ArrayBuffer} entityData - Entity data. * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE virtual void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); @@ -1179,18 +1182,18 @@ public: /**jsdoc * @function Avatar.clearAvatarEntity * @param {Uuid} entityID - The entity ID. - * @param {boolean} [requiresRemovalFromTree=true] - Requires removal from tree. + * @param {boolean} [requiresRemovalFromTree=true] - unused * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); /**jsdoc - *

Deprecated: This method is deprecated and will be removed.

- * Use Avatar.hasScriptedBlendshapes property instead. - * Enables blendshapes set using {@link Avatar.setBlendshape} or {@link MyAvatar.setBlendshape} to be transmitted to other + * Enables blend shapes set using {@link Avatar.setBlendshape} or {@link MyAvatar.setBlendshape} to be transmitted to other * users so that they can see the animation of your avatar's face. + *

Deprecated: This method is deprecated and will be removed. Use the + * Avatar.hasScriptedBlendshapes or MyAvatar.hasScriptedBlendshapes property instead.

* @function Avatar.setForceFaceTrackerConnected - * @param {boolean} connected - true to enable blendshape changes to be transmitted to other users, + * @param {boolean} connected - true to enable blend shape changes to be transmitted to other users, * false to disable. */ Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { setHasScriptedBlendshapes(connected); } @@ -1271,7 +1274,7 @@ public: * null to remove all attachments. * @deprecated This function is deprecated and will be removed. Use avatar entities instead. * @example Remove a hat attachment if your avatar is wearing it. - * var hatURL = "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx"; + * var hatURL = "https://apidocs.vircadia.dev/models/cowboy-hat.fbx"; * var attachments = MyAvatar.getAttachmentData(); * * for (var i = 0; i < attachments.length; i++) { @@ -1308,7 +1311,7 @@ public: * @deprecated This function is deprecated and will be removed. Use avatar entities instead. * @example Attach a cowboy hat to your avatar's head. * var attachment = { - * modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx", + * modelURL: "https://apidocs.vircadia.dev/models/cowboy-hat.fbx", * jointName: "Head", * translation: {"x": 0, "y": 0.25, "z": 0}, * rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, @@ -1387,7 +1390,11 @@ public: /**jsdoc * @comment Documented in derived classes' JSDoc because implementations are different. */ - Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const; + // Get avatar entity data with all property values. Used in API. + Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const; + + // Get avatar entity data with non-default property values. Used internally. + virtual AvatarEntityMap getAvatarEntityDataNonDefault() const; /**jsdoc * @comment Documented in derived classes' JSDoc because implementations are different. @@ -1535,7 +1542,7 @@ signals: void sessionDisplayNameChanged(); /**jsdoc - * Triggered when the avatar's model (i.e., skeletonModelURL property value) is changed. + * Triggered when the avatar's model (i.e., skeletonModelURL property value) changes. * @function Avatar.skeletonModelURLChanged * @returns {Signal} * @example Report when your avatar's skeleton model changes. @@ -1957,6 +1964,7 @@ Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +// No JSDoc because it's not provided as a type to the script engine. class ParabolaToAvatarIntersectionResult { public: bool intersects { false }; diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index c474353451..e12e5b4649 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -111,7 +111,7 @@ public: * Gets information about an avatar. * @function AvatarList.getAvatar * @param {Uuid} avatarID - The ID of the avatar. - * @returns {AvatarData} Information about the avatar. + * @returns {ScriptAvatar} Information about the avatar. */ // Null/Default-constructed QUuids will return MyAvatar Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); } diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index 51f843b83c..e133f178df 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -171,8 +171,6 @@ void ClientTraitsHandler::processTraitOverride(QSharedPointer m return; } - // AJT: DON'T CHECK THIS IN, disable model URL overrides. - /* // only accept an override if this is for a trait type we override // and the version matches what we last sent for skeleton if (traitType == AvatarTraits::SkeletonModelURL @@ -194,7 +192,6 @@ void ClientTraitsHandler::processTraitOverride(QSharedPointer m } else { message->seek(message->getPosition() + traitBinarySize); } - */ } } } diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 18717c8ca3..a67af18c40 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -278,7 +278,7 @@ AvatarEntityMap ScriptAvatarData::getAvatarEntities() const { AvatarEntityMap scriptEntityData; if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { - return sharedAvatarData->getAvatarEntityData(); + return sharedAvatarData->getAvatarEntityDataNonDefault(); } return scriptEntityData; diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 9c5c2c6918..290e8c178a 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -16,53 +16,6 @@ #include "AvatarData.h" -/**jsdoc - * Information about an avatar. - * @typedef {object} AvatarData - * @property {Vec3} position - The avatar's position. - * @property {number} scale - The target scale of the avatar without any restrictions on permissible values imposed by the - * domain. - * @property {Vec3} handPosition - A user-defined hand position, in world coordinates. The position moves with the avatar but - * is otherwise not used or changed by Interface. - * @property {number} bodyPitch - The pitch of the avatar's body, in degrees. - * @property {number} bodyYaw - The yaw of the avatar's body, in degrees. - * @property {number} bodyRoll - The roll of the avatar's body, in degrees. - * @property {Quat} orientation - The orientation of the avatar's body. - * @property {Quat} headOrientation - The orientation of the avatar's head. - * @property {number} headPitch - The pitch of the avatar's head relative to the body, in degrees. - * @property {number} headYaw - The yaw of the avatar's head relative to the body, in degrees. - * @property {number} headRoll - The roll of the avatar's head relative to the body, in degrees. - * - * @property {Vec3} velocity - The linear velocity of the avatar. - * @property {Vec3} angularVelocity - The angular velocity of the avatar. - * - * @property {Uuid} sessionUUID - The avatar's session ID. - * @property {string} displayName - The avatar's display name. - * @property {string} sessionDisplayName - The avatar's display name, sanitized and versioned, as defined by the avatar mixer. - * It is unique among all avatars present in the domain at the time. - * @property {boolean} isReplicated - Deprecated: This property is deprecated and will be - * removed. - * @property {boolean} lookAtSnappingEnabled - true if the avatar's eyes snap to look at another avatar's eyes - * when the other avatar is in the line of sight and also has lookAtSnappingEnabled == true. - * - * @property {string} skeletonModelURL - The avatar's FST file. - * @property {AttachmentData[]} attachmentData - Information on the avatar's attachments. - *

Deprecated: This property is deprecated and will be removed. Use avatar entities instead.

- * @property {string[]} jointNames - The list of joints in the current avatar model. - * - * @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the - * domain. - * @property {number} audioAverageLoudness - The rolling average loudness of the audio input that the avatar is injecting into - * the domain. - * - * @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the - * avatar's size, orientation, and position in the virtual world. - * @property {Mat4} controllerLeftHandMatrix - The rotation and translation of the left hand controller relative to the avatar. - * @property {Mat4} controllerRightHandMatrix - The rotation and translation of the right hand controller relative to the - * avatar. - * - * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. - */ class ScriptAvatarData : public QObject { Q_OBJECT @@ -153,16 +106,110 @@ public: // ATTACHMENT AND JOINT PROPERTIES // QString getSkeletonModelURLFromScript() const; + + /**jsdoc + * Gets the pointing state of the hands to control where the laser emanates from. If the right index finger is pointing, the + * laser emanates from the tip of that finger, otherwise it emanates from the palm. + * @function ScriptAvatar.getHandState + * @returns {HandState|number} The pointing state of the hand, or -1 if the avatar data aren't available. + */ Q_INVOKABLE char getHandState() const; + + /**jsdoc + * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see + * Avatar Standards. + * @function ScriptAvatar.getJointRotation + * @param {number} index - The index of the joint. + * @returns {Quat} The rotation of the joint relative to its parent, or {@link Quat(0)|Quat.IDENTITY} if the avatar data + * aren't available. + */ Q_INVOKABLE glm::quat getJointRotation(int index) const; + + /**jsdoc + * Gets the translation of a joint relative to its parent, in model coordinates. + *

Warning: These coordinates are not necessarily in meters.

+ *

For information on the joint hierarchy used, see + * Avatar Standards.

+ * @function ScriptAvatar.getJointTranslation + * @param {number} index - The index of the joint. + * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates, or {@link Vec3(0)|Vec3.ZERO} + * if the avatar data aren't available. + */ Q_INVOKABLE glm::vec3 getJointTranslation(int index) const; + + /**jsdoc + * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see + * Avatar Standards. + * @function ScriptAvatar.getJointRotation + * @param {string} name - The name of the joint. + * @returns {Quat} The rotation of the joint relative to its parent, or {@link Quat(0)|Quat.IDENTITY} if the avatar data + * aren't available. + */ Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; + + /**jsdoc + * Gets the translation of a joint relative to its parent, in model coordinates. + *

Warning: These coordinates are not necessarily in meters.

+ *

For information on the joint hierarchy used, see + * Avatar Standards.

+ * @function ScriptAvatar.getJointTranslation + * @param {number} name - The name of the joint. + * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates, or {@link Vec3(0)|Vec3.ZERO} + * if the avatar data aren't available. + */ Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const; + + /**jsdoc + * Gets the rotations of all joints in the avatar. Each joint's rotation is relative to its parent joint. + * @function ScriptAvatar.getJointRotations + * @returns {Quat[]} The rotations of all joints relative to each's parent, or [] if the avatar data aren't + * available. The values are in the same order as the array returned by {@link ScriptAvatar.getJointNames}. + */ Q_INVOKABLE QVector getJointRotations() const; + + /**jsdoc + * Gets the translations of all joints in the avatar. Each joint's translation is relative to its parent joint, in + * model coordinates. + *

Warning: These coordinates are not necessarily in meters.

+ * @function ScriptAvatar.getJointTranslations + * @returns {Vec3[]} The translations of all joints relative to each's parent, in model coordinates, or [] if + * the avatar data aren't available. The values are in the same order as the array returned by + * {@link ScriptAvatar.getJointNames}. + */ Q_INVOKABLE QVector getJointTranslations() const; + + /**jsdoc + * Checks that the data for a joint are valid. + * @function ScriptAvatar.isJointDataValid + * @param {number} index - The index of the joint. + * @returns {boolean} true if the joint data are valid, false if not or the avatar data aren't + * available. + */ Q_INVOKABLE bool isJointDataValid(const QString& name) const; + + /**jsdoc + * Gets the joint index for a named joint. The joint index value is the position of the joint in the array returned by + * {@linkScriptAvatar.getJointNames}. + * @function ScriptAvatar.getJointIndex + * @param {string} name - The name of the joint. + * @returns {number} The index of the joint if valid and avatar data are available, otherwise -1. + */ Q_INVOKABLE int getJointIndex(const QString& name) const; + + /**jsdoc + * Gets the names of all the joints in the avatar. + * @function ScriptAvatar.getJointNames + * @returns {string[]} The joint names, or [] if the avatar data aren't available. + */ Q_INVOKABLE QStringList getJointNames() const; + + /**jsdoc + * Gets information about the models currently attached to the avatar. + * @function ScriptAvatar.getAttachmentData + * @returns {AttachmentData[]} Information about all models attached to the avatar, or [] if the avatar data + * aren't available. + * @deprecated This function is deprecated and will be removed. Use avatar entities instead. + */ Q_INVOKABLE QVector getAttachmentData() const; #if DEV_BUILD || PR_BUILD @@ -185,13 +232,54 @@ public: bool getHasPriority() const; signals: + + /**jsdoc + * Triggered when the avatar's displayName property value changes. + * @function ScriptAvatar.displayNameChanged + * @returns {Signal} + */ void displayNameChanged(); + + /**jsdoc + * Triggered when the avatar's sessionDisplayName property value changes. + * @function ScriptAvatar.sessionDisplayNameChanged + * @returns {Signal} + */ void sessionDisplayNameChanged(); + + /**jsdoc + * Triggered when the avatar's model (i.e., skeletonModelURL property value) changes. + * @function ScriptAvatar.skeletonModelURLChanged + * @returns {Signal} + */ void skeletonModelURLChanged(); + + /**jsdoc + * Triggered when the avatar's lookAtSnappingEnabled property value changes. + * @function ScriptAvatar.lookAtSnappingChanged + * @param {boolean} enabled - true if look-at snapping is enabled, false if not. + * @returns {Signal} + */ void lookAtSnappingChanged(bool enabled); public slots: + + /**jsdoc + * Gets the rotation of a joint relative to the avatar. + * @function ScriptAvatar.getAbsoluteJointRotationInObjectFrame + * @param {number} index - The index of the joint. + * @returns {Quat} The rotation of the joint relative to the avatar, or {@link Quat(0)|Quat.IDENTITY} if the avatar data + * aren't available. + */ glm::quat getAbsoluteJointRotationInObjectFrame(int index) const; + + /**jsdoc + * Gets the translation of a joint relative to the avatar. + * @function ScriptAvatar.getAbsoluteJointTranslationInObjectFrame + * @param {number} index - The index of the joint. + * @returns {Vec3} The translation of the joint relative to the avatar, or {@link Vec3(0)|Vec3.ZERO} if the avatar data + * aren't available. + */ glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const; protected: diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index eb02ac2241..7f508dfe15 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -90,11 +90,11 @@ void FBXBaker::replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dra } } -void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { +void FBXBaker::rewriteAndBakeSceneModels(const std::vector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { std::vector meshIndexToRuntimeOrder; - auto meshCount = (int)meshes.size(); + auto meshCount = (uint32_t)meshes.size(); meshIndexToRuntimeOrder.resize(meshCount); - for (int i = 0; i < meshCount; i++) { + for (uint32_t i = 0; i < meshCount; i++) { meshIndexToRuntimeOrder[meshes[i].meshIndex] = i; } diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index a528de512d..6ac05e36e9 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -33,7 +33,7 @@ protected: virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override; private: - void rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists); + void rewriteAndBakeSceneModels(const std::vector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists); void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList); }; diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index 9a1b1b2d24..fbb17f0d01 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -258,9 +258,9 @@ void MaterialBaker::addTexture(const QString& materialName, image::TextureUsage: } }; -void MaterialBaker::setMaterials(const QHash& materials, const QString& baseURL) { +void MaterialBaker::setMaterials(const std::vector& materials, const QString& baseURL) { _materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); - for (auto& material : materials) { + for (const auto& material : materials) { _materialResource->parsedMaterials.names.push_back(material.name.toStdString()); _materialResource->parsedMaterials.networkMaterials[material.name.toStdString()] = std::make_shared(material, baseURL); diff --git a/libraries/baking/src/MaterialBaker.h b/libraries/baking/src/MaterialBaker.h index 33123cfc73..68efcfbd7e 100644 --- a/libraries/baking/src/MaterialBaker.h +++ b/libraries/baking/src/MaterialBaker.h @@ -32,7 +32,7 @@ public: bool isURL() const { return _isURL; } QString getBakedMaterialData() const { return _bakedMaterialData; } - void setMaterials(const QHash& materials, const QString& baseURL); + void setMaterials(const std::vector& materials, const QString& baseURL); void setMaterials(const NetworkMaterialResourcePointer& materialResource); NetworkMaterialResourcePointer getNetworkMaterialResource() const { return _materialResource; } diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 70290fe283..4e76dbb2d5 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -265,7 +265,7 @@ void ModelBaker::bakeSourceCopy() { return; } - if (!_hfmModel->materials.isEmpty()) { + if (!_hfmModel->materials.empty()) { _materialBaker = QSharedPointer( new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir), &MaterialBaker::deleteLater diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp index a2d0ab1094..d726dee897 100644 --- a/libraries/baking/src/OBJBaker.cpp +++ b/libraries/baking/src/OBJBaker.cpp @@ -37,10 +37,10 @@ const QByteArray MESH = "Mesh"; void OBJBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { // Write OBJ Data as FBX tree nodes - createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0]); + createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0], dracoMaterialLists[0]); } -void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh) { +void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh, const std::vector& dracoMaterialList) { // Make all generated nodes children of rootNode rootNode.children = { FBXNode(), FBXNode(), FBXNode() }; FBXNode& globalSettingsNode = rootNode.children[0]; @@ -100,19 +100,22 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h } // Generating Objects node's child - Material node - auto& meshParts = hfmModel->meshes[0].parts; - for (auto& meshPart : meshParts) { + + // Each material ID should only appear once thanks to deduplication in BuildDracoMeshTask, but we want to make sure they are created in the right order + std::unordered_map materialIDToIndex; + for (uint32_t materialIndex = 0; materialIndex < hfmModel->materials.size(); ++materialIndex) { + const auto& material = hfmModel->materials[materialIndex]; + materialIDToIndex[material.materialID] = materialIndex; + } + + // Create nodes for each material in the material list + for (const auto& dracoMaterial : dracoMaterialList) { + const QString materialID = QString(dracoMaterial); + const uint32_t materialIndex = materialIDToIndex[materialID]; + const auto& material = hfmModel->materials[materialIndex]; FBXNode materialNode; materialNode.name = MATERIAL_NODE_NAME; - if (hfmModel->materials.size() == 1) { - // case when no material information is provided, OBJSerializer considers it as a single default material - for (auto& materialID : hfmModel->materials.keys()) { - setMaterialNodeProperties(materialNode, materialID, hfmModel); - } - } else { - setMaterialNodeProperties(materialNode, meshPart.materialID, hfmModel); - } - + setMaterialNodeProperties(materialNode, material.materialID, material, hfmModel); objectNode.children.append(materialNode); } @@ -153,12 +156,10 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h } // Set properties for material nodes -void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel) { +void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, const QString& materialName, const hfm::Material& material, const hfm::Model::Pointer& hfmModel) { auto materialID = nextNodeID(); _materialIDs.push_back(materialID); - materialNode.properties = { materialID, material, MESH }; - - HFMMaterial currentMaterial = hfmModel->materials[material]; + materialNode.properties = { materialID, materialName, MESH }; // Setting the hierarchy: Material -> Properties70 -> P -> Properties FBXNode properties70Node; @@ -170,7 +171,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material pNodeDiffuseColor.name = P_NODE_NAME; pNodeDiffuseColor.properties.append({ "DiffuseColor", "Color", "", "A", - currentMaterial.diffuseColor[0], currentMaterial.diffuseColor[1], currentMaterial.diffuseColor[2] + material.diffuseColor[0], material.diffuseColor[1], material.diffuseColor[2] }); } properties70Node.children.append(pNodeDiffuseColor); @@ -181,7 +182,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material pNodeSpecularColor.name = P_NODE_NAME; pNodeSpecularColor.properties.append({ "SpecularColor", "Color", "", "A", - currentMaterial.specularColor[0], currentMaterial.specularColor[1], currentMaterial.specularColor[2] + material.specularColor[0], material.specularColor[1], material.specularColor[2] }); } properties70Node.children.append(pNodeSpecularColor); @@ -192,7 +193,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material pNodeShininess.name = P_NODE_NAME; pNodeShininess.properties.append({ "Shininess", "Number", "", "A", - currentMaterial.shininess + material.shininess }); } properties70Node.children.append(pNodeShininess); @@ -203,7 +204,7 @@ void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material pNodeOpacity.name = P_NODE_NAME; pNodeOpacity.properties.append({ "Opacity", "Number", "", "A", - currentMaterial.opacity + material.opacity }); } properties70Node.children.append(pNodeOpacity); diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h index 55adec5786..778b4da341 100644 --- a/libraries/baking/src/OBJBaker.h +++ b/libraries/baking/src/OBJBaker.h @@ -27,8 +27,8 @@ protected: virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override; private: - void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh); - void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel); + void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh, const std::vector& dracoMaterialList); + void setMaterialNodeProperties(FBXNode& materialNode, const QString& materialName, const hfm::Material& material, const hfm::Model::Pointer& hfmModel); NodeID nextNodeID() { return _nodeID++; } NodeID _nodeID { 0 }; diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 30f4181b43..2edd1adfeb 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -176,8 +176,8 @@ namespace controller { * person view. * BoomOutnumbernumberZoom camera out from first person to third * person view. - * CycleCameranumbernumberCycle the camera view from first person, to - * third person, to full screen mirror, then back to first person and repeat. + * CycleCameranumbernumberCycle the camera view from first person look + * at, to (third person) look at, to selfie if in desktop mode, then back to first person and repeat. * ContextMenunumbernumberShow/hide the tablet. * ToggleMutenumbernumberToggle the microphone mute. * TogglePushToTalknumbernumberToggle push to talk. diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 05e246deaa..a1875c7fe8 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -282,7 +282,7 @@ namespace controller { * Enables or disables a controller mapping. When enabled, the routes in the mapping have effect. * @function Controller.enableMapping * @param {string} mappingName - The name of the mapping. - * @param {boolean} [[enable=true] - If true then the mapping is enabled, otherwise it is disabled. + * @param {boolean} [enable=true] - If true then the mapping is enabled, otherwise it is disabled. */ Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 5a8fd3083d..9dafc03f1f 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -54,6 +54,7 @@ class UserInputMapper; * * * @class MappingObject + * @hideconstructor * * @hifi-interface * @hifi-client-entity diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index f1b36cfec5..38b18346a8 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -36,6 +36,7 @@ class ScriptingInterface; * types.

* * @class RouteObject + * @hideconstructor * * @hifi-interface * @hifi-client-entity diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 5e05ebf5c9..3695ed4b0b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -226,7 +226,7 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem && !entityItem->getScript().isEmpty()) { - if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { + if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) { if (_currentEntitiesInside.contains(entityID)) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } @@ -240,7 +240,6 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { stopDomainAndNonOwnedEntities(); - auto sessionUUID = getTree()->getMyAvatarSessionUUID(); std::unordered_map savedEntities; std::unordered_set savedRenderables; // remove all entities from the scene @@ -249,7 +248,7 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const EntityItemPointer& entityItem = renderer->getEntity(); - if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == sessionUUID))) { + if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) { fadeOutRenderable(renderer); } else { savedEntities[entry.first] = entry.second; @@ -261,7 +260,7 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { _renderablesToUpdate = savedRenderables; _entitiesInScene = savedEntities; - if (_layeredZones.clearDomainAndNonOwnedZones(sessionUUID)) { + if (_layeredZones.clearDomainAndNonOwnedZones()) { applyLayeredZones(); } @@ -686,7 +685,7 @@ void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { QSet currentEntitiesInsideToSave; foreach (const EntityItemID& entityID, _currentEntitiesInside) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); - if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { + if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) { emit leaveEntity(entityID); if (_entitiesScriptEngine) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); @@ -1218,13 +1217,13 @@ void EntityTreeRenderer::updateZone(const EntityItemID& id) { } } -bool EntityTreeRenderer::LayeredZones::clearDomainAndNonOwnedZones(const QUuid& sessionUUID) { +bool EntityTreeRenderer::LayeredZones::clearDomainAndNonOwnedZones() { bool zonesChanged = false; auto it = begin(); while (it != end()) { auto zone = it->zone.lock(); - if (!zone || !(zone->isLocalEntity() || (zone->isAvatarEntity() && zone->getOwningAvatarID() == sessionUUID))) { + if (!zone || !(zone->isLocalEntity() || zone->isMyAvatarEntity())) { zonesChanged = true; it = erase(it); } else { @@ -1365,6 +1364,10 @@ EntityItemPointer EntityTreeRenderer::getEntity(const EntityItemID& id) { return result; } +void EntityTreeRenderer::deleteEntity(const EntityItemID& id) const { + DependencyManager::get()->deleteEntity(id); +} + void EntityTreeRenderer::onEntityChanged(const EntityItemID& id) { _changedEntitiesGuard.withWriteLock([&] { _changedEntities.insert(id); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 36ec94c464..149b23702f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -118,6 +118,7 @@ public: void setProxyWindow(const EntityItemID& id, QWindow* proxyWindow); void setCollisionSound(const EntityItemID& id, const SharedSoundPointer& sound); EntityItemPointer getEntity(const EntityItemID& id); + void deleteEntity(const EntityItemID& id) const; void onEntityChanged(const EntityItemID& id); // Access the workload Space @@ -232,7 +233,7 @@ private: class LayeredZones : public std::vector { public: - bool clearDomainAndNonOwnedZones(const QUuid& sessionUUID); + bool clearDomainAndNonOwnedZones(); void sort() { std::sort(begin(), end(), std::less()); } bool equals(const LayeredZones& other) const; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 79ee861c95..7e853f617a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -43,6 +43,7 @@ const Transform& EntityRenderer::getModelTransform() const { void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::Status::Getters& statusGetters) { auto nodeList = DependencyManager::get(); + // DANGER: nodeList->getSessionUUID() will return null id when not connected to domain. const QUuid& myNodeID = nodeList->getSessionUUID(); statusGetters.push_back([entity]() -> render::Item::Status::Value { @@ -103,9 +104,9 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St (unsigned char)render::Item::Status::Icon::HAS_ACTIONS); }); - statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { + statusGetters.push_back([entity] () -> render::Item::Status::Value { if (entity->isAvatarEntity()) { - if (entity->getOwningAvatarID() == myNodeID) { + if (entity->isMyAvatarEntity()) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, (unsigned char)render::Item::Status::Icon::ENTITY_HOST_TYPE); } else { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c70220506e..b505a16e1e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -282,7 +282,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3 } void RenderableModelEntityItem::fetchCollisionGeometryResource() { - _collisionGeometryResource = DependencyManager::get()->getCollisionGeometryResource(getCollisionShapeURL()); + _collisionGeometryResource = DependencyManager::get()->getCollisionModelResource(getCollisionShapeURL()); } bool RenderableModelEntityItem::unableToLoadCollisionShape() { @@ -357,7 +357,6 @@ bool RenderableModelEntityItem::isReadyToComputeShape() const { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { const uint32_t TRIANGLE_STRIDE = 3; - const uint32_t QUAD_STRIDE = 4; ShapeType type = getShapeType(); @@ -380,59 +379,35 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); - uint32_t i = 0; + + size_t numParts = 0; + for (const HFMMesh& mesh : collisionGeometry.meshes) { + numParts += mesh.triangleListMesh.parts.size(); + } + pointCollection.reserve(numParts); // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const HFMMesh& mesh, collisionGeometry.meshes) { + for (const HFMMesh& mesh : collisionGeometry.meshes) { + const hfm::TriangleListMesh& triangleListMesh = mesh.triangleListMesh; // each meshPart is a convex hull - foreach (const HFMMeshPart &meshPart, mesh.parts) { - pointCollection.push_back(QVector()); - ShapeInfo::PointList& pointsInPart = pointCollection[i]; - + for (const glm::ivec2& part : triangleListMesh.parts) { // run through all the triangles and (uniquely) add each point to the hull - uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + + pointCollection.emplace_back(); + ShapeInfo::PointList& pointsInPart = pointCollection.back(); + + uint32_t numIndices = (uint32_t)part.y; // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { - glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; - glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; - glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; - if (!pointsInPart.contains(p0)) { - pointsInPart << p0; - } - if (!pointsInPart.contains(p1)) { - pointsInPart << p1; - } - if (!pointsInPart.contains(p2)) { - pointsInPart << p2; - } - } - - // run through all the quads and (uniquely) add each point to the hull - numIndices = (uint32_t)meshPart.quadIndices.size(); - // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up - //assert(numIndices % QUAD_STRIDE == 0); - numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { - glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; - glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; - glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; - glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; - if (!pointsInPart.contains(p0)) { - pointsInPart << p0; - } - if (!pointsInPart.contains(p1)) { - pointsInPart << p1; - } - if (!pointsInPart.contains(p2)) { - pointsInPart << p2; - } - if (!pointsInPart.contains(p3)) { - pointsInPart << p3; + uint32_t indexStart = (uint32_t)part.x; + uint32_t indexEnd = indexStart + numIndices; + for (uint32_t j = indexStart; j < indexEnd; ++j) { + // NOTE: It seems odd to skip vertices when initializing a btConvexHullShape, but let's keep the behavior similar to the old behavior for now + glm::vec3 point = triangleListMesh.vertices[triangleListMesh.indices[j]]; + if (std::find(pointsInPart.cbegin(), pointsInPart.cend(), point) == pointsInPart.cend()) { + pointsInPart.push_back(point); } } @@ -441,7 +416,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { pointCollection.pop_back(); continue; } - ++i; } } @@ -456,8 +430,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); - for (int32_t i = 0; i < pointCollection.size(); i++) { - for (int32_t j = 0; j < pointCollection[i].size(); j++) { + for (size_t i = 0; i < pointCollection.size(); i++) { + for (size_t j = 0; j < pointCollection[i].size(); j++) { // back compensate for registration so we can apply that offset to the shapeInfo later pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset; } @@ -471,46 +445,63 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { model->updateGeometry(); // compute meshPart local transforms - QVector localTransforms; const HFMModel& hfmModel = model->getHFMModel(); - int numHFMMeshes = hfmModel.meshes.size(); - int totalNumVertices = 0; glm::vec3 dimensions = getScaledDimensions(); glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); - for (int i = 0; i < numHFMMeshes; i++) { - const HFMMesh& mesh = hfmModel.meshes.at(i); - if (mesh.clusters.size() > 0) { - const HFMCluster& cluster = mesh.clusters.at(0); - auto jointMatrix = model->getRig().getJointTransform(cluster.jointIndex); - // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later - localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix); - } else { - localTransforms.push_back(invRegistraionOffset); - } - totalNumVertices += mesh.vertices.size(); + + ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); + triangleIndices.clear(); + + Extents extents; + int32_t shapeCount = 0; + int32_t instanceIndex = 0; + + // NOTE: Each pointCollection corresponds to a mesh. Therefore, we should have one pointCollection per mesh instance + // A mesh instance is a unique combination of mesh/transform. For every mesh instance, there are as many shapes as there are parts for that mesh. + // We assume the shapes are grouped by mesh instance, and the group contains one of each mesh part. + uint32_t numInstances = 0; + std::vector>> shapesPerInstancePerMesh; + shapesPerInstancePerMesh.resize(hfmModel.meshes.size()); + for (uint32_t shapeIndex = 0; shapeIndex < hfmModel.shapes.size();) { + const auto& shape = hfmModel.shapes[shapeIndex]; + uint32_t meshIndex = shape.mesh; + const auto& mesh = hfmModel.meshes[meshIndex]; + uint32_t numMeshParts = (uint32_t)mesh.parts.size(); + assert(numMeshParts != 0); + + auto& shapesPerInstance = shapesPerInstancePerMesh[meshIndex]; + shapesPerInstance.emplace_back(); + + auto& shapes = shapesPerInstance.back(); + shapes.resize(numMeshParts); + std::iota(shapes.begin(), shapes.end(), shapeIndex); + + shapeIndex += numMeshParts; + ++numInstances; } - const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; - if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { - qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; + + const uint32_t MAX_ALLOWED_MESH_COUNT = 1000; + if (numInstances > MAX_ALLOWED_MESH_COUNT) { + // too many will cause the deadlock timer to throw... + qWarning() << "model" << getModelURL() << "has too many collision meshes" << numInstances << "and will collide as a box."; shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); return; } - std::vector> meshes; - if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - auto& hfmMeshes = _collisionGeometryResource->getHFMModel().meshes; - meshes.reserve(hfmMeshes.size()); - for (auto& hfmMesh : hfmMeshes) { - meshes.push_back(hfmMesh._mesh); + size_t totalNumVertices = 0; + for (const auto& shapesPerInstance : shapesPerInstancePerMesh) { + for (const auto& instanceShapes : shapesPerInstance) { + const uint32_t firstShapeIndex = instanceShapes.front(); + const auto& firstShape = hfmModel.shapes[firstShapeIndex]; + const auto& mesh = hfmModel.meshes[firstShape.mesh]; + const auto& triangleListMesh = mesh.triangleListMesh; + // Added once per instance per mesh + totalNumVertices += triangleListMesh.vertices.size(); } - } else { - meshes = model->getGeometry()->getMeshes(); } - int32_t numMeshes = (int32_t)(meshes.size()); - - const int MAX_ALLOWED_MESH_COUNT = 1000; - if (numMeshes > MAX_ALLOWED_MESH_COUNT) { - // too many will cause the deadlock timer to throw... + const size_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); return; } @@ -518,169 +509,118 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - pointCollection.resize(numMeshes); + pointCollection.resize(numInstances); } else { pointCollection.resize(1); } - ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); - triangleIndices.clear(); + for (uint32_t meshIndex = 0; meshIndex < hfmModel.meshes.size(); ++meshIndex) { + const auto& mesh = hfmModel.meshes[meshIndex]; + const auto& triangleListMesh = mesh.triangleListMesh; + const auto& vertices = triangleListMesh.vertices; + const auto& indices = triangleListMesh.indices; + const std::vector& parts = triangleListMesh.parts; - Extents extents; - int32_t meshCount = 0; - int32_t pointListIndex = 0; - for (auto& mesh : meshes) { - if (!mesh) { - continue; - } - const gpu::BufferView& vertices = mesh->getVertexBuffer(); - const gpu::BufferView& indices = mesh->getIndexBuffer(); - const gpu::BufferView& parts = mesh->getPartBuffer(); + const auto& shapesPerInstance = shapesPerInstancePerMesh[meshIndex]; + for (const std::vector& instanceShapes : shapesPerInstance) { + ShapeInfo::PointList& points = pointCollection[instanceIndex]; - ShapeInfo::PointList& points = pointCollection[pointListIndex]; + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.size()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each instance + instanceIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)((gpu::Size)points.size()); + } + points.reserve(sizeToReserve); + + // get mesh instance transform + const uint32_t meshIndexOffset = (uint32_t)points.size(); + const uint32_t instanceShapeIndexForTransform = instanceShapes.front(); + const auto& instanceShapeForTransform = hfmModel.shapes[instanceShapeIndexForTransform]; + glm::mat4 localTransform; + if (instanceShapeForTransform.joint != hfm::UNDEFINED_KEY) { + auto jointMatrix = model->getRig().getJointTransform(instanceShapeForTransform.joint); + // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later + if (instanceShapeForTransform.skinDeformer != hfm::UNDEFINED_KEY) { + const auto& skinDeformer = hfmModel.skinDeformers[instanceShapeForTransform.skinDeformer]; + glm::mat4 inverseBindMatrix; + if (!skinDeformer.clusters.empty()) { + const auto& cluster = skinDeformer.clusters.back(); + inverseBindMatrix = cluster.inverseBindMatrix; + } + localTransform = invRegistraionOffset * jointMatrix * inverseBindMatrix; + } else { + localTransform = invRegistraionOffset * jointMatrix; + } + } else { + localTransform = invRegistraionOffset; + } - // reserve room - int32_t sizeToReserve = (int32_t)(vertices.getNumElements()); - if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - // a list of points for each mesh - pointListIndex++; - } else { - // only one list of points - sizeToReserve += (int32_t)((gpu::Size)points.size()); - } - points.reserve(sizeToReserve); + // copy points + auto vertexItr = vertices.cbegin(); + while (vertexItr != vertices.cend()) { + glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); + points.push_back(point); + ++vertexItr; + } + for (const auto& instanceShapeIndex : instanceShapes) { + const auto& instanceShape = hfmModel.shapes[instanceShapeIndex]; + extents.addExtents(instanceShape.transformedExtents); + } - // copy points - uint32_t meshIndexOffset = (uint32_t)points.size(); - const glm::mat4& localTransform = localTransforms[meshCount]; - gpu::BufferView::Iterator vertexItr = vertices.cbegin(); - while (vertexItr != vertices.cend()) { - glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); - points.push_back(point); - extents.addPoint(point); - ++vertexItr; - } - - if (type == SHAPE_TYPE_STATIC_MESH) { - // copy into triangleIndices - triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); - gpu::BufferView::Iterator partItr = parts.cbegin(); - while (partItr != parts.cend()) { - auto numIndices = partItr->_numIndices; - if (partItr->_topology == graphics::Mesh::TRIANGLES) { + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.size())); + auto partItr = parts.cbegin(); + while (partItr != parts.cend()) { + auto numIndices = partItr->y; // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexItr = indices.cbegin() + partItr->x; auto indexEnd = indexItr + numIndices; while (indexItr != indexEnd) { triangleIndices.push_back(*indexItr + meshIndexOffset); ++indexItr; } - } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { - // TODO: resurrect assert after we start sanitizing HFMMesh higher up - //assert(numIndices > 2); - - uint32_t approxNumIndices = TRIANGLE_STRIDE * numIndices; - if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) { - // we underestimated the final size of triangleIndices so we pre-emptively expand it - triangleIndices.reserve(triangleIndices.size() + approxNumIndices); - } - - auto indexItr = indices.cbegin() + partItr->_startIndex; - auto indexEnd = indexItr + (numIndices - 2); - - // first triangle uses the first three indices - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); - - // the rest use previous and next index - uint32_t triangleCount = 1; - while (indexItr != indexEnd) { - if ((*indexItr) != graphics::Mesh::PRIMITIVE_RESTART_INDEX) { - if (triangleCount % 2 == 0) { - // even triangles use first two indices in order - triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); - triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); - } else { - // odd triangles swap order of first two indices - triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); - triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); - } - triangleIndices.push_back(*indexItr + meshIndexOffset); - ++triangleCount; - } - ++indexItr; - } + ++partItr; } - ++partItr; - } - } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - // for each mesh copy unique part indices, separated by special bogus (flag) index values - gpu::BufferView::Iterator partItr = parts.cbegin(); - while (partItr != parts.cend()) { - // collect unique list of indices for this part - std::set uniqueIndices; - auto numIndices = partItr->_numIndices; - if (partItr->_topology == graphics::Mesh::TRIANGLES) { + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + auto partItr = parts.cbegin(); + while (partItr != parts.cend()) { + // collect unique list of indices for this part + std::set uniqueIndices; + auto numIndices = partItr->y; // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices% TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXSerializer - - auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexItr = indices.cbegin() + partItr->x; auto indexEnd = indexItr + numIndices; while (indexItr != indexEnd) { uniqueIndices.insert(*indexItr); ++indexItr; } - } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { - // TODO: resurrect assert after we start sanitizing HFMMesh higher up - //assert(numIndices > TRIANGLE_STRIDE - 1); - auto indexItr = indices.cbegin() + partItr->_startIndex; - auto indexEnd = indexItr + (numIndices - 2); - - // first triangle uses the first three indices - uniqueIndices.insert(*(indexItr++)); - uniqueIndices.insert(*(indexItr++)); - uniqueIndices.insert(*(indexItr++)); - - // the rest use previous and next index - uint32_t triangleCount = 1; - while (indexItr != indexEnd) { - if ((*indexItr) != graphics::Mesh::PRIMITIVE_RESTART_INDEX) { - if (triangleCount % 2 == 0) { - // EVEN triangles use first two indices in order - uniqueIndices.insert(*(indexItr - 2)); - uniqueIndices.insert(*(indexItr - 1)); - } else { - // ODD triangles swap order of first two indices - uniqueIndices.insert(*(indexItr - 1)); - uniqueIndices.insert(*(indexItr - 2)); - } - uniqueIndices.insert(*indexItr); - ++triangleCount; - } - ++indexItr; + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); } - } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); - // store uniqueIndices in triangleIndices - triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); - for (auto index : uniqueIndices) { - triangleIndices.push_back(index); + ++partItr; } - // flag end of part - triangleIndices.push_back(END_OF_MESH_PART); - - ++partItr; + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); } - // flag end of mesh - triangleIndices.push_back(END_OF_MESH); } - ++meshCount; + + ++shapeCount; } // scale and shift @@ -692,7 +632,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } } for (auto points : pointCollection) { - for (int32_t i = 0; i < points.size(); ++i) { + for (size_t i = 0; i < points.size(); ++i) { points[i] = (points[i] * scaleToFit); } } @@ -1433,7 +1373,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } } - if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + if (!_texturesLoaded && model->getNetworkModel() && model->getNetworkModel()->areTexturesLoaded()) { withWriteLock([&] { _texturesLoaded = true; }); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 7c219422a6..7e5bcb0593 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -120,7 +120,7 @@ private: bool readyToAnimate() const; void fetchCollisionGeometryResource(); - GeometryResource::Pointer _collisionGeometryResource; + ModelResource::Pointer _collisionGeometryResource; std::vector _jointMap; QVariantMap _originalTextures; bool _jointMapCompleted { false }; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 5ffb395302..b08ef8111d 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -220,7 +220,7 @@ float importanceSample3DDimension(float startDim) { } ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, - const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const ShapeType& shapeType, const ModelResource::Pointer& geometryResource, const TriangleInfo& triangleInfo) { CpuParticle particle; @@ -405,7 +405,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { particle::Properties particleProperties; ShapeType shapeType; - GeometryResource::Pointer geometryResource; + ModelResource::Pointer geometryResource; withReadLock([&] { particleProperties = _particleProperties; shapeType = _shapeType; @@ -508,7 +508,7 @@ void ParticleEffectEntityRenderer::fetchGeometryResource() { if (hullURL.isEmpty()) { _geometryResource.reset(); } else { - _geometryResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); + _geometryResource = DependencyManager::get()->getCollisionModelResource(hullURL); } } @@ -516,7 +516,7 @@ void ParticleEffectEntityRenderer::fetchGeometryResource() { void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) { PROFILE_RANGE(render, __FUNCTION__); - int numberOfMeshes = hfmModel.meshes.size(); + uint32_t numberOfMeshes = (uint32_t)hfmModel.meshes.size(); _hasComputedTriangles = true; _triangleInfo.triangles.clear(); @@ -526,11 +526,11 @@ void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) float minArea = FLT_MAX; AABox bounds; - for (int i = 0; i < numberOfMeshes; i++) { + for (uint32_t i = 0; i < numberOfMeshes; i++) { const HFMMesh& mesh = hfmModel.meshes.at(i); - const int numberOfParts = mesh.parts.size(); - for (int j = 0; j < numberOfParts; j++) { + const uint32_t numberOfParts = (uint32_t)mesh.parts.size(); + for (uint32_t j = 0; j < numberOfParts; j++) { const HFMMeshPart& part = mesh.parts.at(j); const int INDICES_PER_TRIANGLE = 3; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index a89ab804fc..9e73892486 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -90,7 +90,7 @@ private: } _triangleInfo; static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, - const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const ShapeType& shapeType, const ModelResource::Pointer& geometryResource, const TriangleInfo& triangleInfo); void stepSimulation(); @@ -109,7 +109,7 @@ private: QString _compoundShapeURL; void fetchGeometryResource(); - GeometryResource::Pointer _geometryResource; + ModelResource::Pointer _geometryResource; NetworkTexturePointer _networkTexture; bool _textureLoaded { false }; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 1555604604..5370c17275 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1429,14 +1429,13 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { auto polyVoxEntity = std::static_pointer_cast(entity); - QVector> pointCollection; + ShapeInfo::PointCollection pointCollection; AABox box; glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { // pull each triangle in the mesh into a polyhedron which can be collided with - unsigned int i = 0; const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); @@ -1465,19 +1464,16 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { box += p2Model; box += p3Model; - QVector pointsInPart; - pointsInPart << p0Model; - pointsInPart << p1Model; - pointsInPart << p2Model; - pointsInPart << p3Model; - // add next convex hull - QVector newMeshPoints; - pointCollection << newMeshPoints; - // add points to the new convex hull - pointCollection[i++] << pointsInPart; + ShapeInfo::PointList pointsInPart; + pointsInPart.push_back(p0Model); + pointsInPart.push_back(p1Model); + pointsInPart.push_back(p2Model); + pointsInPart.push_back(p3Model); + + // add points to a new convex hull + pointCollection.push_back(pointsInPart); } } else { - unsigned int i = 0; polyVoxEntity->forEachVoxelValue(voxelVolumeSize, [&](const ivec3& v, uint8_t value) { if (value > 0) { const auto& x = v.x; @@ -1496,7 +1492,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { return; } - QVector pointsInPart; + ShapeInfo::PointList pointsInPart; float offL = -0.5f; float offH = 0.5f; @@ -1523,20 +1519,17 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { box += p110; box += p111; - pointsInPart << p000; - pointsInPart << p001; - pointsInPart << p010; - pointsInPart << p011; - pointsInPart << p100; - pointsInPart << p101; - pointsInPart << p110; - pointsInPart << p111; + pointsInPart.push_back(p000); + pointsInPart.push_back(p001); + pointsInPart.push_back(p010); + pointsInPart.push_back(p011); + pointsInPart.push_back(p100); + pointsInPart.push_back(p101); + pointsInPart.push_back(p110); + pointsInPart.push_back(p111); - // add next convex hull - QVector newMeshPoints; - pointCollection << newMeshPoints; - // add points to the new convex hull - pointCollection[i++] << pointsInPart; + // add points to a new convex hull + pointCollection.push_back(pointsInPart); } }); } @@ -1546,7 +1539,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) { // this catches the payload from computeShapeInfoWorker - if (pointCollection.isEmpty()) { + if (pointCollection.empty()) { EntityItem::computeShapeInfo(_shapeInfo); withWriteLock([&] { _shapeReady = true; diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index 1dca171ae3..a4ecb532e5 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -53,6 +53,15 @@ void DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEnt } } +void DeleteEntityOperator::addEntityToDeleteList(const EntityItemPointer& entity) { + assert(entity && entity->getElement()); + EntityToDeleteDetails details; + details.entity = entity; + details.containingElement = entity->getElement(); + details.cube = details.containingElement->getAACube(); + _entitiesToDelete << details; + _lookingCount++; +} // does this entity tree element contain the old entity bool DeleteEntityOperator::subTreeContainsSomeEntitiesToDelete(const OctreeElementPointer& element) { diff --git a/libraries/entities/src/DeleteEntityOperator.h b/libraries/entities/src/DeleteEntityOperator.h index 3b3ee2a868..1449e2caad 100644 --- a/libraries/entities/src/DeleteEntityOperator.h +++ b/libraries/entities/src/DeleteEntityOperator.h @@ -42,6 +42,7 @@ public: ~DeleteEntityOperator(); void addEntityIDToDeleteList(const EntityItemID& searchEntityID); + void addEntityToDeleteList(const EntityItemPointer& entity); virtual bool preRecursion(const OctreeElementPointer& element) override; virtual bool postRecursion(const OctreeElementPointer& element) override; diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index a222ca8216..16dace0fc8 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -43,7 +43,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { } bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, - bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, EntityItemPointer& existingEntity) { + bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, const EntityItemPointer& existingEntity) { // get the ids of all the zones (plus the global entity edit filter) that the position // lies within diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index cb99c97762..69fd920998 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -55,7 +55,7 @@ public: void removeFilter(EntityItemID entityID); bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, - EntityTree::FilterType filterType, EntityItemID& entityID, EntityItemPointer& existingEntity); + EntityTree::FilterType filterType, EntityItemID& entityID, const EntityItemPointer& existingEntity); signals: void filterAdded(EntityItemID id, bool success); diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index dbb3ab076e..aaaf7d645a 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -73,8 +73,12 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, if (properties.getEntityHostType() == entity::HostType::AVATAR) { if (!_myAvatar) { qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar"; - } else if (properties.getOwningAvatarID() == _myAvatar->getID()) { - // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server + } else if (properties.getOwningAvatarID() == _myAvatar->getID() || properties.getOwningAvatarID() == AVATAR_SELF_ID) { + // this is a local avatar-entity --> update our avatar-data rather than sending to the entity-server + // Note: we store AVATAR_SELF_ID in EntityItem::_owningAvatarID and we usually + // store the actual sessionUUID in EntityItemProperties::_owningAvatarID. + // However at this context we check for both cases just in case. Really we just want to know + // where to route the data: entity-server or avatar-mixer. queueEditAvatarEntityMessage(entityTree, entityItemID); } else { qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar"; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index a48b7377df..b075239caf 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1344,7 +1344,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(created, getCreated); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy); COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityHostType, getEntityHostType); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarIDForProperties); COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube); COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera); @@ -2704,10 +2704,12 @@ quint64 EntityItem::getLastEdited() const { } void EntityItem::setLastEdited(quint64 lastEdited) { - withWriteLock([&] { - _lastEdited = _lastUpdated = lastEdited; - _changedOnServer = glm::max(lastEdited, _changedOnServer); - }); + if (lastEdited == 0 || lastEdited > _lastEdited) { + withWriteLock([&] { + _lastEdited = _lastUpdated = lastEdited; + _changedOnServer = glm::max(lastEdited, _changedOnServer); + }); + } } void EntityItem::markAsChangedOnServer() { @@ -3028,7 +3030,11 @@ void EntityItem::setCanCastShadow(bool value) { } bool EntityItem::getCullWithParent() const { - return _cullWithParent; + bool result; + withReadLock([&] { + result = _cullWithParent; + }); + return result; } void EntityItem::setCullWithParent(bool value) { @@ -3221,6 +3227,7 @@ void EntityItem::somethingChangedNotification() { }); } +// static void EntityItem::retrieveMarketplacePublicKey() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; @@ -3252,6 +3259,23 @@ void EntityItem::retrieveMarketplacePublicKey() { }); } +void EntityItem::collectChildrenForDelete(std::vector& entitiesToDelete, const QUuid& sessionID) const { + // Deleting an entity has consequences for its children, however there are rules dictating what can be deleted. + // This method helps enforce those rules: not for this entity, but for its children. + for (SpatiallyNestablePointer child : getChildren()) { + if (child && child->getNestableType() == NestableType::Entity) { + EntityItemPointer childEntity = std::static_pointer_cast(child); + // NOTE: null sessionID means "collect ALL known children", else we only collect: local-entities and myAvatar-entities + if (sessionID.isNull() || childEntity->isLocalEntity() || childEntity->isMyAvatarEntity()) { + if (std::find(entitiesToDelete.begin(), entitiesToDelete.end(), childEntity) == entitiesToDelete.end()) { + entitiesToDelete.push_back(childEntity); + childEntity->collectChildrenForDelete(entitiesToDelete, sessionID); + } + } + } + } +} + void EntityItem::setSpaceIndex(int32_t index) { assert(_spaceIndex == -1); _spaceIndex = index; @@ -3416,6 +3440,7 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti properties.setSimulationOwner(Physics::getSessionUUID(), priority); setPendingOwnershipPriority(priority); + // TODO: figure out if it would be OK to NOT bother set these properties here properties.setEntityHostType(getEntityHostType()); properties.setOwningAvatarID(getOwningAvatarID()); setLastBroadcast(now); // for debug/physics status icons @@ -3427,9 +3452,27 @@ bool EntityItem::isWearable() const { } bool EntityItem::isMyAvatarEntity() const { - return _hostType == entity::HostType::AVATAR && Physics::getSessionUUID() == _owningAvatarID; + return _hostType == entity::HostType::AVATAR && AVATAR_SELF_ID == _owningAvatarID; }; +QUuid EntityItem::getOwningAvatarIDForProperties() const { + if (isMyAvatarEntity()) { + // NOTE: we always store AVATAR_SELF_ID for MyAvatar's avatar entities, + // however for EntityItemProperties to be consumed by outside contexts (e.g. JS) + // we use the actual "sessionUUID" which is conveniently cached in the Physics namespace + return Physics::getSessionUUID(); + } + return _owningAvatarID; +} + +void EntityItem::setOwningAvatarID(const QUuid& owningAvatarID) { + if (!owningAvatarID.isNull() && owningAvatarID == Physics::getSessionUUID()) { + _owningAvatarID = AVATAR_SELF_ID; + } else { + _owningAvatarID = owningAvatarID; + } +} + void EntityItem::addGrab(GrabPointer grab) { enableNoBootstrap(); SpatiallyNestable::addGrab(grab); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5b0f1ad44c..cb0beb601b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -18,6 +18,7 @@ #include #include +#include #include // for EncodeBitstreamParams class #include // for OctreeElement::AppendState @@ -49,6 +50,7 @@ typedef std::shared_ptr EntityTreePointer; typedef std::shared_ptr EntityDynamicPointer; typedef std::shared_ptr EntityTreeElementPointer; using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr; +using SetOfEntities = QSet; #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { }; @@ -517,7 +519,8 @@ public: // if this entity is an avatar entity, which avatar is it associated with? QUuid getOwningAvatarID() const { return _owningAvatarID; } - virtual void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + QUuid getOwningAvatarIDForProperties() const; + void setOwningAvatarID(const QUuid& owningAvatarID); virtual bool wantsHandControllerPointerEvents() const { return false; } virtual bool wantsKeyboardFocus() const { return false; } @@ -543,6 +546,8 @@ public: static QString _marketplacePublicKey; static void retrieveMarketplacePublicKey(); + void collectChildrenForDelete(std::vector& entitiesToDelete, const QUuid& sessionID) const; + float getBoundingRadius() const { return _boundingRadius; } void setSpaceIndex(int32_t index); int32_t getSpaceIndex() const { return _spaceIndex; } @@ -766,7 +771,7 @@ protected: QHash _grabActions; bool _cullWithParent { false }; - + mutable bool _needsRenderUpdate { false }; private: diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 9afaa5e85b..c81cd2a70a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -299,6 +299,16 @@ void EntityItemProperties::setAvatarPriorityFromString(const QString& mode) { } } +QString EntityItemProperties::getScreenshareAsString() const { return getComponentModeAsString(_screenshare); } +void EntityItemProperties::setScreenshareFromString(const QString& mode) { + auto modeItr = stringToComponentMode.find(mode.toLower()); + if (modeItr != stringToComponentMode.end()) { + _screenshare = modeItr.value(); + _screenshareChanged = true; + } +} + + inline void addTextEffect(QHash& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; } const QHash stringToTextEffectLookup = [] { QHash toReturn; @@ -566,6 +576,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode); CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority); + CHECK_PROPERTY_CHANGE(PROP_SCREENSHARE, screenshare); // Polyvox CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -1025,8 +1036,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * 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 }, + * modelURL: "https://apidocs.vircadia.dev/models/cowboy-hat.fbx", + * dimensions: { x: 0.8569, y: 0.3960, z: 1.0744 }, * lifetime: 300 // Delete after 5 minutes. * }); */ @@ -1429,6 +1440,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.AvatarPriorityMode} avatarPriority="inherit" - Configures the priority of updates from avatars in the * zone to other clients. * + * @property {Entities.ScreenshareMode} screenshare="inherit" - Configures a zone for screen-sharing. + * * @example Create a zone that casts a red key light along the x-axis. * var zone = Entities.addEntity({ * type: "Zone", @@ -1779,6 +1792,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AVATAR_PRIORITY, avatarPriority, getAvatarPriorityAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SCREENSHARE, screenshare, getScreenshareAsString()); } // Web only @@ -2150,6 +2164,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(avatarPriority, AvatarPriority); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(screenshare, Screenshare); // Polyvox COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); @@ -2438,6 +2453,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(hazeMode); COPY_PROPERTY_IF_CHANGED(bloomMode); COPY_PROPERTY_IF_CHANGED(avatarPriority); + COPY_PROPERTY_IF_CHANGED(screenshare); // Polyvox COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -2834,6 +2850,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_SCREENSHARE, Screenshare, screenshare, uint32_t); // Polyvox ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3); @@ -3252,6 +3269,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, (uint32_t)properties.getAvatarPriority()); + APPEND_ENTITY_PROPERTY(PROP_SCREENSHARE, (uint32_t)properties.getScreenshare()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -3726,6 +3744,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCREENSHARE, uint32_t, setScreenshare); } if (properties.getType() == EntityTypes::PolyVox) { @@ -3936,7 +3955,7 @@ bool EntityItemProperties::decodeCloneEntityMessage(const QByteArray& buffer, in processedBytes = 0; if (NUM_BYTES_RFC4122_UUID * 2 > packetLength) { - qCDebug(entities) << "EntityItemProperties::processEraseMessageDetails().... bailing because not enough bytes in buffer"; + qCDebug(entities) << "EntityItemProperties::decodeCloneEntityMessage().... bailing because not enough bytes in buffer"; return false; // bail to prevent buffer overflow } @@ -4117,6 +4136,7 @@ void EntityItemProperties::markAllChanged() { _hazeModeChanged = true; _bloomModeChanged = true; _avatarPriorityChanged = true; + _screenshareChanged = true; // Polyvox _voxelVolumeSizeChanged = true; @@ -4739,6 +4759,9 @@ QList EntityItemProperties::listChangedProperties() { if (avatarPriorityChanged()) { out += "avatarPriority"; } + if (screenshareChanged()) { + out += "screenshare"; + } // Polyvox if (voxelVolumeSizeChanged()) { @@ -5071,8 +5094,9 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID setEntityHostType(entity::HostType::LOCAL); setCollisionless(true); } - setCreated(usecTimestampNow()); - setLastEdited(usecTimestampNow()); + uint64_t now = usecTimestampNow(); + setCreated(now); + setLastEdited(now); setCloneable(ENTITY_ITEM_DEFAULT_CLONEABLE); setCloneLifetime(ENTITY_ITEM_DEFAULT_CLONE_LIFETIME); setCloneLimit(ENTITY_ITEM_DEFAULT_CLONE_LIMIT); @@ -5096,10 +5120,13 @@ bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const Q return true; } -void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob) { +void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, + const EntityItemProperties& properties, QByteArray& blob, bool allProperties) { // DANGER: this method is NOT efficient. // begin recipe for extracting unfortunately-formatted-binary-blob from EntityItem - QScriptValue scriptValue = EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties); + QScriptValue scriptValue = allProperties + ? EntityItemPropertiesToScriptValue(&scriptEngine, properties) + : EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties); QVariant variantProperties = scriptValue.toVariant(); QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 7ee6742e7f..264fc3881b 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -118,7 +118,8 @@ class EntityItemProperties { friend class MaterialEntityItem; public: static bool blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties); - static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob); + static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, + QByteArray& blob, bool allProperties = false); EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); EntityItemProperties(const EntityItemProperties&) = default; @@ -339,6 +340,7 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_SCREENSHARE, Screenshare, screenshare, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); // Polyvox DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); @@ -701,6 +703,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, AvatarPriority, avatarPriority, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Screenshare, screenshare, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d5af337a7d..1ffd8fdc3c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -161,6 +161,7 @@ enum EntityPropertyList { PROP_DERIVED_31, PROP_DERIVED_32, PROP_DERIVED_33, + PROP_DERIVED_34, PROP_AFTER_LAST_ITEM, @@ -290,6 +291,8 @@ enum EntityPropertyList { PROP_BLOOM_MODE = PROP_DERIVED_32, // Avatar priority PROP_AVATAR_PRIORITY = PROP_DERIVED_33, + // Screen-sharing + PROP_SCREENSHARE = PROP_DERIVED_34, // Polyvox PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0, diff --git a/libraries/entities/src/EntityScriptServerLogClient.h b/libraries/entities/src/EntityScriptServerLogClient.h index 22245e2a4e..6d3dec16e2 100644 --- a/libraries/entities/src/EntityScriptServerLogClient.h +++ b/libraries/entities/src/EntityScriptServerLogClient.h @@ -16,6 +16,16 @@ #include +/**jsdoc + * The EntityScriptServerLog API makes server log file output written by server entity scripts available to client + * scripts. + * + * @namespace EntityScriptServerLog + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + */ class EntityScriptServerLogClient : public QObject, public Dependency { Q_OBJECT @@ -23,6 +33,21 @@ public: EntityScriptServerLogClient(); signals: + + /**jsdoc + * Triggered when one or more lines are written to the server log by server entity scripts. + * @function EntityScriptServerLog.receivedNewLogLines + * @param {string} logLines - The server log lines written by server entity scripts. If there are multiple lines they are + * separated by "\n"s. + * @example Echo server entity script program log output to Interface's program log. + * EntityScriptServerLog.receivedNewLogLines.connect(function (logLines) { + * print("Log lines from server entity scripts:", logLines); + * }); + * @example A server entity script to test with. Copy the code into an entity's "Server Script" property. + * (function () { + * print("Hello from a server entity script!"); + * }) + */ void receivedNewLogLines(QString logLines); protected: diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 3305d9ba00..d68747b96c 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -480,17 +480,11 @@ QUuid EntityScriptingInterface::addEntityInternal(const EntityItemProperties& pr _activityTracking.addedEntityCount++; - auto nodeList = DependencyManager::get(); - auto sessionID = nodeList->getSessionUUID(); - EntityItemProperties propertiesWithSimID = properties; propertiesWithSimID.setEntityHostType(entityHostType); if (entityHostType == entity::HostType::AVATAR) { - if (sessionID.isNull()) { - // null sessionID is unacceptable in this case - sessionID = AVATAR_SELF_ID; - } - propertiesWithSimID.setOwningAvatarID(sessionID); + // only allow adding our own avatar entities from script + propertiesWithSimID.setOwningAvatarID(AVATAR_SELF_ID); } else if (entityHostType == entity::HostType::LOCAL) { // For now, local entities are always collisionless // TODO: create a separate, local physics simulation that just handles local entities (and MyAvatar?) @@ -498,6 +492,8 @@ QUuid EntityScriptingInterface::addEntityInternal(const EntityItemProperties& pr } // the created time will be set in EntityTree::addEntity by recordCreationTime() + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); propertiesWithSimID.setLastEditedBy(sessionID); bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); @@ -805,7 +801,7 @@ QUuid EntityScriptingInterface::editEntity(const QUuid& id, const EntityItemProp return; } - if (entity->isAvatarEntity() && entity->getOwningAvatarID() != sessionID && entity->getOwningAvatarID() != AVATAR_SELF_ID) { + if (entity->isAvatarEntity() && !entity->isMyAvatarEntity()) { // don't edit other avatar's avatarEntities properties = EntityItemProperties(); return; @@ -825,7 +821,7 @@ QUuid EntityScriptingInterface::editEntity(const QUuid& id, const EntityItemProp // flag for simulation ownership, or upgrade existing ownership priority // (actual bids for simulation ownership are sent by the PhysicalEntitySimulation) entity->upgradeScriptSimulationPriority(properties.computeSimulationBidPriority()); - if (simulationOwner.getID() == sessionID) { + if (entity->isLocalEntity() || entity->isMyAvatarEntity() || simulationOwner.getID() == sessionID) { // we own the simulation --> copy ALL restricted properties properties.copySimulationRestrictedProperties(entity); } else { @@ -970,43 +966,43 @@ void EntityScriptingInterface::deleteEntity(const QUuid& id) { _activityTracking.deletedEntityCount++; - EntityItemID entityID(id); - bool shouldSendDeleteToServer = true; - - // If we have a local entity tree set, then also update it. - if (_entityTree) { - _entityTree->withWriteLock([&] { - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); - if (entity) { - - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); - if (entity->isAvatarEntity() && entity->getOwningAvatarID() != myNodeID) { - // don't delete other avatar's avatarEntities - shouldSendDeleteToServer = false; - return; - } - - if (entity->getLocked()) { - shouldSendDeleteToServer = false; - } else { - // only delete local entities, server entities will round trip through the server filters - if (!entity->isDomainEntity() || _entityTree->isServerlessMode()) { - shouldSendDeleteToServer = false; - _entityTree->deleteEntity(entityID); - - if (entity->isAvatarEntity() && getEntityPacketSender()->getMyAvatar()) { - getEntityPacketSender()->getMyAvatar()->clearAvatarEntity(entityID, false); - } - } - } - } - }); + if (!_entityTree) { + return; } - // if at this point, we know the id, and we should still delete the entity, send the update to the entity server - if (shouldSendDeleteToServer) { - getEntityPacketSender()->queueEraseEntityMessage(entityID); + EntityItemID entityID(id); + + // If we have a local entity tree set, then also update it. + std::vector entitiesToDeleteImmediately; + _entityTree->withWriteLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (entity) { + if (entity->isAvatarEntity() && !entity->isMyAvatarEntity()) { + // don't delete other avatar's avatarEntities + return; + } + if (entity->getLocked()) { + return; + } + + // Deleting an entity has consequences for linked children: some can be deleted but others can't. + // Local- and my-avatar-entities can be deleted immediately, but other-avatar-entities can't be deleted + // by this context, and a domain-entity must rountrip through the entity-server for authorization. + if (entity->isDomainEntity()) { + getEntityPacketSender()->queueEraseEntityMessage(id); + } else { + entitiesToDeleteImmediately.push_back(entity); + const auto sessionID = DependencyManager::get()->getSessionUUID(); + entity->collectChildrenForDelete(entitiesToDeleteImmediately, sessionID); + _entityTree->deleteEntitiesByPointer(entitiesToDeleteImmediately); + } + } + }); + + for (auto entity : entitiesToDeleteImmediately) { + if (entity->isMyAvatarEntity()) { + getEntityPacketSender()->getMyAvatar()->clearAvatarEntity(entity->getID(), false); + } } } @@ -1653,12 +1649,9 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return false; } - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); - EntityItemPointer entity; bool doTransmit = false; - _entityTree->withWriteLock([this, &entity, entityID, myNodeID, &doTransmit, actor] { + _entityTree->withWriteLock([this, &entity, entityID, &doTransmit, actor] { EntitySimulationPointer simulation = _entityTree->getSimulation(); entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { @@ -1671,7 +1664,7 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return; } - if (entity->isAvatarEntity() && entity->getOwningAvatarID() != myNodeID) { + if (entity->isAvatarEntity() && !entity->isMyAvatarEntity()) { return; } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ae2c9ed746..e0f14b47e7 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1871,7 +1871,8 @@ public slots: * @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 This function is deprecated and will be removed. Use the {@link Graphics} API instead. + * @deprecated This function is deprecated and will be removed. It no longer works for Model entities. Use the + * {@link Graphics} API instead. */ /**jsdoc * Called when a {@link Entities.getMeshes} call is complete. @@ -1880,7 +1881,8 @@ public slots: * 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 This function is deprecated and will be removed. Use the {@link Graphics} API instead. + * @deprecated This function is deprecated and will be removed. It no longer works for Model entities. Use the + * {@link Graphics} API instead. */ // FIXME move to a renderable entity interface Q_INVOKABLE void getMeshes(const QUuid& entityID, QScriptValue callback); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 9f81572a4a..c7eb906efb 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -19,41 +19,37 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { if (_entityTree && _entityTree != tree) { - _mortalEntities.clear(); - _nextExpiry = std::numeric_limits::max(); - _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); + _changedEntities.clear(); + _entitiesToUpdate.clear(); + _mortalEntities.clear(); + _nextExpiry = std::numeric_limits::max(); } _entityTree = tree; } void EntitySimulation::updateEntities() { + PerformanceTimer perfTimer("EntitySimulation::updateEntities"); QMutexLocker lock(&_mutex); uint64_t now = usecTimestampNow(); - PerformanceTimer perfTimer("EntitySimulation::updateEntities"); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); callUpdateOnEntitiesThatNeedIt(now); moveSimpleKinematics(now); - updateEntitiesInternal(now); sortEntitiesThatMoved(); + processDeadEntities(); } -void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) { - QMutexLocker lock(&_mutex); - entitiesToDelete.swap(_deadEntities); - _deadEntities.clear(); -} - -void EntitySimulation::removeEntityInternal(EntityItemPointer entity) { - // remove from all internal lists except _deadEntities - _mortalEntities.remove(entity); - _entitiesToUpdate.remove(entity); +void EntitySimulation::removeEntityFromInternalLists(EntityItemPointer entity) { + // protected: _mutex lock is guaranteed + // remove from all internal lists except _deadEntitiesToRemoveFromTree _entitiesToSort.remove(entity); _simpleKinematicEntities.remove(entity); _allEntities.remove(entity); + _entitiesToUpdate.remove(entity); + _mortalEntities.remove(entity); entity->setSimulated(false); } @@ -62,10 +58,9 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { assert(entity->isDead()); if (entity->isSimulated()) { QMutexLocker lock(&_mutex); - entity->clearActions(getThisPointer()); - removeEntityInternal(entity); + removeEntityFromInternalLists(entity); if (entity->getElement()) { - _deadEntities.insert(entity); + _deadEntitiesToRemoveFromTree.insert(entity); _entityTree->cleanupCloneIDs(entity->getEntityItemID()); } } @@ -149,10 +144,8 @@ void EntitySimulation::sortEntitiesThatMoved() { _entitiesToSort.clear(); } -void EntitySimulation::addEntity(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - assert(entity); - entity->deserializeActions(); +void EntitySimulation::addEntityToInternalLists(EntityItemPointer entity) { + // protected: _mutex lock is guaranteed if (entity->isMortal()) { _mortalEntities.insert(entity); uint64_t expiry = entity->getExpiry(); @@ -163,10 +156,14 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { if (entity->needsToCallUpdate()) { _entitiesToUpdate.insert(entity); } - addEntityInternal(entity); - _allEntities.insert(entity); entity->setSimulated(true); +} + +void EntitySimulation::addEntity(EntityItemPointer entity) { + QMutexLocker lock(&_mutex); + assert(entity); + addEntityToInternalLists(entity); // DirtyFlags are used to signal changes to entities that have already been added, // so we can clear them for this entity which has just been added. @@ -218,16 +215,14 @@ void EntitySimulation::processChangedEntity(const EntityItemPointer& entity) { void EntitySimulation::clearEntities() { QMutexLocker lock(&_mutex); - _mortalEntities.clear(); - _nextExpiry = std::numeric_limits::max(); - _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); - - clearEntitiesInternal(); - + _changedEntities.clear(); _allEntities.clear(); - _deadEntities.clear(); + _deadEntitiesToRemoveFromTree.clear(); + _entitiesToUpdate.clear(); + _mortalEntities.clear(); + _nextExpiry = std::numeric_limits::max(); } void EntitySimulation::moveSimpleKinematics(uint64_t now) { @@ -263,25 +258,19 @@ void EntitySimulation::moveSimpleKinematics(uint64_t now) { } } -void EntitySimulation::addDynamic(EntityDynamicPointer dynamic) { - QMutexLocker lock(&_dynamicsMutex); - _dynamicsToAdd += dynamic; -} - -void EntitySimulation::removeDynamic(const QUuid dynamicID) { - QMutexLocker lock(&_dynamicsMutex); - _dynamicsToRemove += dynamicID; -} - -void EntitySimulation::removeDynamics(QList dynamicIDsToRemove) { - QMutexLocker lock(&_dynamicsMutex); - foreach(QUuid uuid, dynamicIDsToRemove) { - _dynamicsToRemove.insert(uuid); +void EntitySimulation::processDeadEntities() { + if (_deadEntitiesToRemoveFromTree.empty()) { + return; } -} - -void EntitySimulation::applyDynamicChanges() { - QMutexLocker lock(&_dynamicsMutex); - _dynamicsToAdd.clear(); - _dynamicsToRemove.clear(); + std::vector entitiesToDeleteImmediately; + entitiesToDeleteImmediately.reserve(_deadEntitiesToRemoveFromTree.size()); + QUuid nullSessionID; + foreach (auto entity, _deadEntitiesToRemoveFromTree) { + entitiesToDeleteImmediately.push_back(entity); + entity->collectChildrenForDelete(entitiesToDeleteImmediately, nullSessionID); + } + if (_entityTree) { + _entityTree->deleteEntitiesByPointer(entitiesToDeleteImmediately); + } + _deadEntitiesToRemoveFromTree.clear(); } diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 1dd0369561..5b7b38e447 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -16,17 +16,14 @@ #include #include -#include #include #include -#include "EntityDynamicInterface.h" #include "EntityItem.h" #include "EntityTree.h" using EntitySimulationPointer = std::shared_ptr; -using SetOfEntities = QSet; using VectorOfEntities = QVector; // the EntitySimulation needs to know when these things change on an entity, @@ -47,8 +44,8 @@ const int DIRTY_SIMULATION_FLAGS = class EntitySimulation : public QObject, public std::enable_shared_from_this { public: - EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits::max()) { } - virtual ~EntitySimulation() { setEntityTree(NULL); } + EntitySimulation() : _mutex(QMutex::Recursive), _nextExpiry(std::numeric_limits::max()), _entityTree(nullptr) { } + virtual ~EntitySimulation() { setEntityTree(nullptr); } inline EntitySimulationPointer getThisPointer() const { return std::const_pointer_cast(shared_from_this()); @@ -57,12 +54,12 @@ public: /// \param tree pointer to EntityTree which is stored internally void setEntityTree(EntityTreePointer tree); - void updateEntities(); + virtual void updateEntities(); - virtual void addDynamic(EntityDynamicPointer dynamic); - virtual void removeDynamic(const QUuid dynamicID); - virtual void removeDynamics(QList dynamicIDsToRemove); - virtual void applyDynamicChanges(); + // FIXME: remove these + virtual void addDynamic(EntityDynamicPointer dynamic) {} + virtual void removeDynamic(const QUuid dynamicID) {} + virtual void applyDynamicChanges() {}; /// \param entity pointer to EntityItem to be added /// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked @@ -72,27 +69,22 @@ public: /// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself) void changeEntity(EntityItemPointer entity); - void clearEntities(); + virtual void clearEntities(); void moveSimpleKinematics(uint64_t now); EntityTreePointer getEntityTree() { return _entityTree; } - 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); void processChangedEntities(); + virtual void queueEraseDomainEntity(const QUuid& id) const { } 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(uint64_t now) = 0; - virtual void addEntityInternal(EntityItemPointer entity) = 0; - virtual void removeEntityInternal(EntityItemPointer entity); + virtual void addEntityToInternalLists(EntityItemPointer entity); + virtual void removeEntityFromInternalLists(EntityItemPointer entity); virtual void processChangedEntity(const EntityItemPointer& entity); - virtual void clearEntitiesInternal() = 0; + virtual void processDeadEntities(); void expireMortalEntities(uint64_t now); void callUpdateOnEntitiesThatNeedIt(uint64_t now); @@ -102,27 +94,21 @@ protected: SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree) SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion - QList _dynamicsToAdd; - QSet _dynamicsToRemove; - QMutex _dynamicsMutex { QMutex::Recursive }; - -protected: - SetOfEntities _deadEntities; // dead entities that might still be in the _entityTree + SetOfEntities _deadEntitiesToRemoveFromTree; private: void moveSimpleKinematics(); - // back pointer to EntityTree structure - EntityTreePointer _entityTree; - // We maintain multiple lists, each for its distinct purpose. // An entity may be in more than one list. std::unordered_set _changedEntities; // all changes this frame SetOfEntities _allEntities; // tracks all entities added the simulation + SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() SetOfEntities _mortalEntities; // entities that have an expiry uint64_t _nextExpiry; - SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() + // back pointer to EntityTree structure + EntityTreePointer _entityTree; }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index dbcfcb3d07..11cd6309da 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -85,7 +85,7 @@ void EntityTree::eraseDomainAndNonOwnedEntities() { emit clearingEntities(); if (_simulation) { - // local entities are not in the simulation, so we clear ALL + // local-entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } @@ -99,14 +99,15 @@ void EntityTree::eraseDomainAndNonOwnedEntities() { if (element) { element->cleanupDomainAndNonOwnedEntities(); } - - if (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID())) { - savedEntities[entity->getEntityItemID()] = entity; - } else { - int32_t spaceIndex = entity->getSpaceIndex(); - if (spaceIndex != -1) { - // stale spaceIndices will be freed later - _staleProxies.push_back(spaceIndex); + if (!getIsServer()) { + if (entity->isLocalEntity() || entity->isMyAvatarEntity()) { + savedEntities[entity->getEntityItemID()] = entity; + } else { + int32_t spaceIndex = entity->getSpaceIndex(); + if (spaceIndex != -1) { + // stale spaceIndices will be freed later + _staleProxies.push_back(spaceIndex); + } } } } @@ -122,7 +123,7 @@ void EntityTree::eraseDomainAndNonOwnedEntities() { foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { auto entity = entityItem.lock(); - if (entity && (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID()))) { + if (entity && (entity->isLocalEntity() || entity->isMyAvatarEntity())) { needParentFixup.push_back(entityItem); } } @@ -145,10 +146,12 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { if (element) { element->cleanupEntities(); } - int32_t spaceIndex = entity->getSpaceIndex(); - if (spaceIndex != -1) { - // assume stale spaceIndices will be freed later - _staleProxies.push_back(spaceIndex); + if (!getIsServer()) { + int32_t spaceIndex = entity->getSpaceIndex(); + if (spaceIndex != -1) { + // assume stale spaceIndices will be freed later + _staleProxies.push_back(spaceIndex); + } } } }); @@ -606,61 +609,21 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) { } void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) { - EntityTreeElementPointer containingElement = getContainingElement(entityID); - if (!containingElement) { - if (!ignoreWarnings) { - qCWarning(entities) << "EntityTree::deleteEntity() on non-existent entityID=" << entityID; - } - return; - } - - EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); - if (!existingEntity) { - if (!ignoreWarnings) { - qCWarning(entities) << "EntityTree::deleteEntity() on non-existant entity item with entityID=" << entityID; - } - return; - } - - if (existingEntity->getLocked() && !force) { - if (!ignoreWarnings) { - qCDebug(entities) << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID; - } - return; - } - - cleanupCloneIDs(entityID); - unhookChildAvatar(entityID); - emit deletingEntity(entityID); - emit deletingEntityPointer(existingEntity.get()); - - // NOTE: callers must lock the tree before using this method - DeleteEntityOperator theOperator(getThisPointer(), entityID); - - existingEntity->forEachDescendant([&](SpatiallyNestablePointer descendant) { - auto descendantID = descendant->getID(); - theOperator.addEntityIDToDeleteList(descendantID); - emit deletingEntity(descendantID); - EntityItemPointer descendantEntity = std::dynamic_pointer_cast(descendant); - if (descendantEntity) { - emit deletingEntityPointer(descendantEntity.get()); - } - }); - - recurseTreeWithOperator(&theOperator); - processRemovedEntities(theOperator); - _isDirty = true; + // NOTE: can be called without lock because deleteEntitiesByID() will lock + std::vector ids; + ids.push_back(entityID); + deleteEntitiesByID(ids, force, ignoreWarnings); } void EntityTree::unhookChildAvatar(const EntityItemID entityID) { - - EntityItemPointer entity = findEntityByEntityItemID(entityID); - - entity->forEachDescendant([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Avatar) { - child->setParentID(nullptr); - } - }); + if (!getIsServer()) { + EntityItemPointer entity = findEntityByEntityItemID(entityID); + entity->forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Avatar) { + child->setParentID(nullptr); + } + }); + } } void EntityTree::cleanupCloneIDs(const EntityItemID& entityID) { @@ -685,39 +648,104 @@ void EntityTree::cleanupCloneIDs(const EntityItemID& entityID) { } } -void EntityTree::deleteEntities(QSet entityIDs, bool force, bool ignoreWarnings) { - // NOTE: callers must lock the tree before using this method +void EntityTree::recursivelyFilterAndCollectForDelete(const EntityItemPointer& entity, std::vector& entitiesToDelete, bool force) const { + // tree must be read-locked before calling this method + //TODO: assert(treeIsLocked); + assert(entity); + if (entity->getElement() && (std::find(entitiesToDelete.begin(), entitiesToDelete.end(), entity) == entitiesToDelete.end())) { + // filter + bool allowed = force; + if (!allowed) { + bool wasChanged = false; + auto startFilter = usecTimestampNow(); + EntityItemProperties dummyProperties; + allowed = filterProperties(entity, dummyProperties, dummyProperties, wasChanged, FilterType::Delete); + auto endFilter = usecTimestampNow(); + _totalFilterTime += endFilter - startFilter; + } + if (allowed) { + entitiesToDelete.push_back(entity); + for (SpatiallyNestablePointer child : entity->getChildren()) { + if (child && child->getNestableType() == NestableType::Entity) { + EntityItemPointer childEntity = std::static_pointer_cast(child); + recursivelyFilterAndCollectForDelete(childEntity, entitiesToDelete, force); + } + } + } + } +} + +void EntityTree::deleteEntitiesByID(const std::vector& ids, bool force, bool ignoreWarnings) { + // this method has two paths: + // (a) entity-server: applies delete filter + // (b) interface-client: deletes local- and my-avatar-entities immediately, submits domainEntity deletes to the entity-server + if (getIsServer()) { + withWriteLock([&] { + std::vector entitiesToDelete; + entitiesToDelete.reserve(ids.size()); + for (auto id : ids) { + EntityItemPointer entity; + { + QReadLocker locker(&_entityMapLock); + entity = _entityMap.value(id); + } + if (entity) { + recursivelyFilterAndCollectForDelete(entity, entitiesToDelete, force); + } + } + if (!entitiesToDelete.empty()) { + deleteEntitiesByPointer(entitiesToDelete); + } + }); + } else { + std::vector domainEntitiesIDs; + std::vector entitiesToDelete; + entitiesToDelete.reserve(ids.size()); + QUuid sessionID = DependencyManager::get()->getSessionUUID(); + withWriteLock([&] { + for (auto id : ids) { + EntityItemPointer entity; + { + QReadLocker locker(&_entityMapLock); + entity = _entityMap.value(id); + } + if (entity) { + if (entity->isDomainEntity()) { + // domain-entity deletes must round-trip through entity-server + domainEntitiesIDs.push_back(id); + } else if (force || entity->isLocalEntity() || entity->isMyAvatarEntity()) { + entitiesToDelete.push_back(entity); + entity->collectChildrenForDelete(entitiesToDelete, sessionID); + } + } + } + if (!entitiesToDelete.empty()) { + deleteEntitiesByPointer(entitiesToDelete); + } + }); + if (!domainEntitiesIDs.empty() && _simulation) { + for (auto id : domainEntitiesIDs) { + _simulation->queueEraseDomainEntity(id); + } + } + } +} + +void EntityTree::deleteEntitiesByPointer(const std::vector& entities) { + // tree must be write-locked before calling this method + //TODO: assert(treeIsLocked); + // NOTE: there is no entity validation (i.e. is entity in tree?) nor snarfing of children beyond this point. + // Get those done BEFORE calling this method. + for (auto entity : entities) { + cleanupCloneIDs(entity->getID()); + } DeleteEntityOperator theOperator(getThisPointer()); - foreach(const EntityItemID& entityID, entityIDs) { - EntityTreeElementPointer containingElement = getContainingElement(entityID); - if (!containingElement) { - if (!ignoreWarnings) { - qCWarning(entities) << "EntityTree::deleteEntities() on non-existent entityID=" << entityID; - } - continue; + for (auto entity : entities) { + if (entity->getElement()) { + theOperator.addEntityToDeleteList(entity); + emit deletingEntity(entity->getID()); + emit deletingEntityPointer(entity.get()); } - - EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); - if (!existingEntity) { - if (!ignoreWarnings) { - qCWarning(entities) << "EntityTree::deleteEntities() on non-existent entity item with entityID=" << entityID; - } - continue; - } - - if (existingEntity->getLocked() && !force) { - if (!ignoreWarnings) { - qCDebug(entities) << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID; - } - continue; - } - - // tell our delete operator about this entityID - cleanupCloneIDs(entityID); - unhookChildAvatar(entityID); - theOperator.addEntityIDToDeleteList(entityID); - emit deletingEntity(entityID); - emit deletingEntityPointer(existingEntity.get()); } if (!theOperator.getEntities().empty()) { @@ -728,23 +756,11 @@ void EntityTree::deleteEntities(QSet entityIDs, bool force, bool i } void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) { + // NOTE: assume tree already write-locked because this method only called in deleteEntitiesByPointer() quint64 deletedAt = usecTimestampNow(); const RemovedEntities& entities = theOperator.getEntities(); foreach(const EntityToDeleteDetails& details, entities) { EntityItemPointer theEntity = details.entity; - - if (getIsServer()) { - QSet childrenIDs; - theEntity->forEachChild([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Entity) { - childrenIDs += child->getID(); - } - }); - deleteEntities(childrenIDs, true, true); - } - - theEntity->die(); - if (getIsServer()) { removeCertifiedEntityOnServer(theEntity); @@ -752,19 +768,24 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock); _recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID()); } else { + theEntity->forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Avatar) { + child->setParentID(nullptr); + } + }); + // on the client side, we also remember that we deleted this entity, we don't care about the time trackDeletedEntity(theEntity->getEntityItemID()); - } + int32_t spaceIndex = theEntity->getSpaceIndex(); + if (spaceIndex != -1) { + // stale spaceIndices will be freed later + _staleProxies.push_back(spaceIndex); + } + } if (theEntity->isSimulated()) { _simulation->prepareEntityForDelete(theEntity); } - - int32_t spaceIndex = theEntity->getSpaceIndex(); - if (spaceIndex != -1) { - // stale spaceIndices will be freed later - _staleProxies.push_back(spaceIndex); - } } } @@ -1370,7 +1391,7 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList(); if (entityEditFilters) { @@ -1750,9 +1771,9 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const } } +// NOTE: Caller must lock the tree before calling this. int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { - if (!getIsServer()) { qCWarning(entities) << "EntityTree::processEditPacketData() should only be called on a server tree."; return 0; @@ -1922,6 +1943,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c FilterType filterType = isPhysics ? FilterType::Physics : (isAdd ? FilterType::Add : FilterType::Edit); bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(existingEntity, properties, properties, wasChanged, filterType); if (!allowed) { + // the update failed and we need to convey that fact to the sender + // our method is to re-assert the current properties and bump the lastEdited timestamp auto timestamp = properties.getLastEdited(); properties = EntityItemProperties(); properties.setLastEdited(timestamp); @@ -2217,9 +2240,19 @@ void EntityTree::fixupNeedsParentFixups() { } void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) { - if (_childrenOfAvatars.contains(avatarID)) { - deleteEntities(_childrenOfAvatars[avatarID]); - _childrenOfAvatars.remove(avatarID); + QHash>::const_iterator itr = _childrenOfAvatars.constFind(avatarID); + if (itr != _childrenOfAvatars.end()) { + if (!itr.value().empty()) { + std::vector ids; + ids.reserve(itr.value().size()); + for (const auto id : itr.value()) { + ids.push_back(id); + } + bool force = true; + bool ignoreWarnings = true; + deleteEntitiesByID(ids, force, ignoreWarnings); + } + _childrenOfAvatars.erase(itr); } } @@ -2250,22 +2283,6 @@ void EntityTree::update(bool simulate) { if (simulate && _simulation) { withWriteLock([&] { _simulation->updateEntities(); - { - PROFILE_RANGE(simulation_physics, "Deletes"); - SetOfEntities deadEntities; - _simulation->takeDeadEntities(deadEntities); - if (!deadEntities.empty()) { - // translate into list of ID's - QSet idsToDelete; - - for (auto entity : deadEntities) { - idsToDelete.insert(entity->getEntityItemID()); - } - - // delete these things the roundabout way - deleteEntities(idsToDelete, true); - } - } }); } } @@ -2354,9 +2371,13 @@ bool EntityTree::shouldEraseEntity(EntityItemID entityID, const SharedNodePointe return allowed; } - -// TODO: consider consolidating processEraseMessageDetails() and processEraseMessage() int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + // NOTE: this is only called by the interface-client on receipt of deleteEntity message from entity-server. + // Which means this is a state synchronization message from the the entity-server. It is saying + // "The following domain-entities have already been deleted". While need to perform sanity checking + // (e.g. verify these are domain entities) permissions need NOT checked for the domain-entities. + assert(!getIsServer()); + // TODO: remove this stuff out of EntityTree:: and into interface-client code. #ifdef EXTRA_ERASE_DEBUGGING qCDebug(entities) << "EntityTree::processEraseMessage()"; #endif @@ -2367,10 +2388,9 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo message.readPrimitive(&numberOfIDs); if (numberOfIDs > 0) { - QSet entityItemIDsToDelete; + QSet idsToDelete; for (size_t i = 0; i < numberOfIDs; i++) { - if (NUM_BYTES_RFC4122_UUID > message.getBytesLeftToRead()) { qCDebug(entities) << "EntityTree::processEraseMessage().... bailing because not enough bytes in buffer"; break; // bail to prevent buffer overflow @@ -2382,64 +2402,87 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo #endif EntityItemID entityItemID(entityID); + idsToDelete << entityItemID; + } - if (shouldEraseEntity(entityID, sourceNode)) { - entityItemIDsToDelete << entityItemID; - cleanupCloneIDs(entityItemID); + // domain-entity deletion can trigger deletion of other entities the entity-server doesn't know about + // so we must recurse down the children and collect consequential deletes however + // we must first identify all domain-entities in idsToDelete so as to not overstep entity-server's authority + std::vector domainEntities; + domainEntities.reserve(idsToDelete.size()); + for (auto id : idsToDelete) { + EntityItemPointer entity = _entityMap.value(id); + if (entity && entity->isDomainEntity()) { + domainEntities.push_back(entity); } } - deleteEntities(entityItemIDsToDelete, true, true); + // now we recurse domain-entities children and snarf consequential entities + // which are nomally just local-entities and myAvatar-entities + auto nodeList = DependencyManager::get(); + QUuid sessionID = nodeList->getSessionUUID(); + // NOTE: normally a null sessionID would be bad, as that would cause the collectDhildrenForDelete() method below + // to snarf domain-entities for which the interface-client is not authorized to delete without explicit instructions + // from the entity-server, however it is ok here because that would mean: + // (a) interface-client is not connected to a domain which means... + // (b) we should never get here (since this would correspond to a message from the entity-server) but... + // (c) who cares? When not connected to a domain the interface-client can do whatever it wants. + std::vector entitiesToDelete; + entitiesToDelete.reserve(domainEntities.size()); + for (auto entity : domainEntities) { + entitiesToDelete.push_back(entity); + entity->collectChildrenForDelete(entitiesToDelete, sessionID); + } + + if (!entitiesToDelete.empty()) { + deleteEntitiesByPointer(entitiesToDelete); + } } }); return message.getPosition(); } // This version skips over the header -// NOTE: Caller must lock the tree before calling this. -// TODO: consider consolidating processEraseMessageDetails() and processEraseMessage() +// NOTE: Caller must write-lock the tree before calling this. int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { + // NOTE: this is called on entity-server when receiving a delete request from an interface-client or agent + //TODO: assert(treeIsLocked); + assert(getIsServer()); #ifdef EXTRA_ERASE_DEBUGGING qCDebug(entities) << "EntityTree::processEraseMessageDetails()"; #endif - const unsigned char* packetData = (const unsigned char*)dataByteArray.constData(); - const unsigned char* dataAt = packetData; size_t packetLength = dataByteArray.size(); size_t processedBytes = 0; uint16_t numberOfIds = 0; // placeholder for now - memcpy(&numberOfIds, dataAt, sizeof(numberOfIds)); - dataAt += sizeof(numberOfIds); + memcpy(&numberOfIds, dataByteArray.constData(), sizeof(numberOfIds)); processedBytes += sizeof(numberOfIds); if (numberOfIds > 0) { - QSet entityItemIDsToDelete; + std::vector ids; + ids.reserve(numberOfIds); + // extract ids from packet for (size_t i = 0; i < numberOfIds; i++) { - - if (processedBytes + NUM_BYTES_RFC4122_UUID > packetLength) { qCDebug(entities) << "EntityTree::processEraseMessageDetails().... bailing because not enough bytes in buffer"; break; // bail to prevent buffer overflow } QByteArray encodedID = dataByteArray.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID); - QUuid entityID = QUuid::fromRfc4122(encodedID); - dataAt += encodedID.size(); + QUuid id = QUuid::fromRfc4122(encodedID); processedBytes += encodedID.size(); #ifdef EXTRA_ERASE_DEBUGGING - qCDebug(entities) << " ---- EntityTree::processEraseMessageDetails() contains id:" << entityID; + qCDebug(entities) << " ---- EntityTree::processEraseMessageDetails() contains id:" << id; #endif - EntityItemID entityItemID(entityID); - - if (shouldEraseEntity(entityID, sourceNode)) { - entityItemIDsToDelete << entityItemID; - cleanupCloneIDs(entityItemID); - } - + EntityItemID entityID(id); + ids.push_back(entityID); } - deleteEntities(entityItemIDsToDelete, true, true); + + bool force = sourceNode->isAllowedEditor(); + bool ignoreWarnings = true; + deleteEntitiesByID(ids, force, ignoreWarnings); } return (int)processedBytes; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index a0fcbd3244..3662f6acf0 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -126,7 +126,9 @@ public: void unhookChildAvatar(const EntityItemID entityID); void cleanupCloneIDs(const EntityItemID& entityID); void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = true); - void deleteEntities(QSet entityIDs, bool force = false, bool ignoreWarnings = true); + + void deleteEntitiesByID(const std::vector& entityIDs, bool force = false, bool ignoreWarnings = true); + void deleteEntitiesByPointer(const std::vector& entities); EntityItemPointer findEntityByID(const QUuid& id) const; EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID) const; @@ -294,6 +296,7 @@ signals: protected: + void recursivelyFilterAndCollectForDelete(const EntityItemPointer& entity, std::vector& entitiesToDelete, bool force) const; void processRemovedEntities(const DeleteEntityOperator& theOperator); bool updateEntity(EntityItemPointer entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); @@ -342,12 +345,12 @@ protected: int _totalEditMessages = 0; int _totalUpdates = 0; int _totalCreates = 0; - quint64 _totalDecodeTime = 0; - quint64 _totalLookupTime = 0; - quint64 _totalUpdateTime = 0; - quint64 _totalCreateTime = 0; - quint64 _totalLoggingTime = 0; - quint64 _totalFilterTime = 0; + mutable quint64 _totalDecodeTime = 0; + mutable quint64 _totalLookupTime = 0; + mutable quint64 _totalUpdateTime = 0; + mutable quint64 _totalCreateTime = 0; + mutable quint64 _totalLoggingTime = 0; + mutable quint64 _totalFilterTime = 0; // these performance statistics are only used in the client void resetClientEditStats(); @@ -367,7 +370,7 @@ protected: float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME }; - bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType); + bool filterProperties(const EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) const; bool _hasEntityEditFilter{ false }; QStringList _entityScriptSourceWhitelist; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 60eaafc0dd..9af0bbfdb6 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -197,7 +197,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori // only called if we do intersect our bounding cube, but find if we actually intersect with entities... EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { - if (entity->getIgnorePickIntersection()) { + if (entity->getIgnorePickIntersection() && !searchFilter.bypassIgnore()) { return; } @@ -341,7 +341,7 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3 // only called if we do intersect our bounding cube, but find if we actually intersect with entities... EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { - if (entity->getIgnorePickIntersection()) { + if (entity->getIgnorePickIntersection() && !searchFilter.bypassIgnore()) { return; } @@ -705,7 +705,7 @@ void EntityTreeElement::cleanupDomainAndNonOwnedEntities() { withWriteLock([&] { EntityItems savedEntities; foreach(EntityItemPointer entity, _entityItems) { - if (!(entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { + if (!(entity->isLocalEntity() || entity->isMyAvatarEntity())) { entity->preDelete(); entity->_element = NULL; } else { diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index b8e3df2d03..d64efdf87f 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -47,14 +47,17 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { } } -void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) { +void SimpleEntitySimulation::updateEntities() { + EntitySimulation::updateEntities(); + QMutexLocker lock(&_mutex); + uint64_t now = usecTimestampNow(); expireStaleOwnerships(now); stopOwnerlessEntities(now); } -void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { +void SimpleEntitySimulation::addEntityToInternalLists(EntityItemPointer entity) { + EntitySimulation::addEntityToInternalLists(entity); if (entity->getSimulatorID().isNull()) { - QMutexLocker lock(&_mutex); if (entity->getDynamic()) { // we don't allow dynamic objects to move without an owner so nothing to do here } else if (entity->isMovingRelativeToParent()) { @@ -65,7 +68,6 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { } } } else { - QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); @@ -79,10 +81,10 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { } } -void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { - EntitySimulation::removeEntityInternal(entity); +void SimpleEntitySimulation::removeEntityFromInternalLists(EntityItemPointer entity) { _entitiesWithSimulationOwner.remove(entity); _entitiesThatNeedSimulationOwner.remove(entity); + EntitySimulation::removeEntityFromInternalLists(entity); } void SimpleEntitySimulation::processChangedEntity(const EntityItemPointer& entity) { @@ -135,10 +137,11 @@ void SimpleEntitySimulation::processChangedEntity(const EntityItemPointer& entit entity->clearDirtyFlags(); } -void SimpleEntitySimulation::clearEntitiesInternal() { +void SimpleEntitySimulation::clearEntities() { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.clear(); _entitiesThatNeedSimulationOwner.clear(); + EntitySimulation::clearEntities(); } void SimpleEntitySimulation::sortEntitiesThatMoved() { diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 1b240a8bf0..e984b72ed4 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -23,16 +23,16 @@ using SimpleEntitySimulationPointer = std::shared_ptr; class SimpleEntitySimulation : public EntitySimulation { public: SimpleEntitySimulation() : EntitySimulation() { } - ~SimpleEntitySimulation() { clearEntitiesInternal(); } + ~SimpleEntitySimulation() { clearEntities(); } void clearOwnership(const QUuid& ownerID); + void clearEntities() override; + void updateEntities() override; protected: - void updateEntitiesInternal(uint64_t now) override; - void addEntityInternal(EntityItemPointer entity) override; - void removeEntityInternal(EntityItemPointer entity) override; + void addEntityToInternalLists(EntityItemPointer entity) override; + void removeEntityFromInternalLists(EntityItemPointer entity) override; void processChangedEntity(const EntityItemPointer& entity) override; - void clearEntitiesInternal() override; void sortEntitiesThatMoved() override; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index dc18c05ef3..6fbf764459 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -71,6 +71,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(screenshare, getScreenshare); return properties; } @@ -119,6 +120,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(screenshare, setScreenshare); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; @@ -195,6 +197,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority); + READ_ENTITY_PROPERTY(PROP_SCREENSHARE, uint32_t, setScreenshare); return bytesRead; } @@ -215,6 +218,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_GHOSTING_ALLOWED; requestedProperties += PROP_FILTER_URL; requestedProperties += PROP_AVATAR_PRIORITY; + requestedProperties += PROP_SCREENSHARE; requestedProperties += PROP_KEY_LIGHT_MODE; requestedProperties += PROP_AMBIENT_LIGHT_MODE; @@ -261,6 +265,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority()); + APPEND_ENTITY_PROPERTY(PROP_SCREENSHARE, getScreenshare()); } void ZoneEntityItem::debugDump() const { @@ -346,7 +351,7 @@ bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c } bool ZoneEntityItem::contains(const glm::vec3& point) const { - GeometryResource::Pointer resource = _shapeResource; + ModelResource::Pointer resource = _shapeResource; if (_shapeType == SHAPE_TYPE_COMPOUND && resource) { if (resource->isLoaded()) { const HFMModel& hfmModel = resource->getHFMModel(); @@ -463,7 +468,7 @@ void ZoneEntityItem::fetchCollisionGeometryResource() { if (hullURL.isEmpty()) { _shapeResource.reset(); } else { - _shapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); + _shapeResource = DependencyManager::get()->getCollisionModelResource(hullURL); } } @@ -472,9 +477,9 @@ bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority"; - // If set ignore only priority-inherit zones: + // If set match zones of interest to avatar mixer: if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY) && jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool() - && _avatarPriority != COMPONENT_MODE_INHERIT) { + && (_avatarPriority != COMPONENT_MODE_INHERIT || _screenshare != COMPONENT_MODE_INHERIT)) { return true; } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 34ad47f095..295727d657 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,6 +102,9 @@ public: uint32_t getAvatarPriority() const { return _avatarPriority; } void setAvatarPriority(uint32_t value) { _avatarPriority = value; } + uint32_t getScreenshare() const { return _screenshare; } + void setScreenshare(uint32_t value) { _screenshare = value; } + bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } @@ -156,6 +159,9 @@ protected: // Avatar-updates priority uint32_t _avatarPriority { COMPONENT_MODE_INHERIT }; + // Screen-sharing zone + uint32_t _screenshare { COMPONENT_MODE_INHERIT }; + // Dirty flags turn true when either keylight properties is changing values. bool _keyLightPropertiesChanged { false }; bool _ambientLightPropertiesChanged { false }; @@ -167,7 +173,7 @@ protected: static bool _zonesArePickable; void fetchCollisionGeometryResource(); - GeometryResource::Pointer _shapeResource; + ModelResource::Pointer _shapeResource; }; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 100f6ee98e..d7c05e29de 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -20,6 +20,7 @@ #include #include +#include // TOOL: Uncomment the following line to enable the filtering of all the unkwnon fields of a node so we can break point easily while loading a model with problems... //#define DEBUG_FBXSERIALIZER @@ -145,8 +146,9 @@ public: bool isLimbNode; // is this FBXModel transform is a "LimbNode" i.e. a joint }; + glm::mat4 getGlobalTransform(const QMultiMap& _connectionParentMap, - const QHash& fbxModels, QString nodeID, bool mixamoHack, const QString& url) { + const QHash& fbxModels, QString nodeID, bool mixamoHack, const QString& url) { glm::mat4 globalTransform; QVector visitedNodes; // Used to prevent following a cycle while (!nodeID.isNull()) { @@ -166,12 +168,11 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen } QList parentIDs = _connectionParentMap.values(nodeID); nodeID = QString(); - foreach (const QString& parentID, parentIDs) { + foreach(const QString& parentID, parentIDs) { if (visitedNodes.contains(parentID)) { qCWarning(modelformat) << "Ignoring loop detected in FBX connection map for" << url; continue; } - if (fbxModels.contains(parentID)) { nodeID = parentID; break; @@ -181,6 +182,21 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen return globalTransform; } +std::vector getModelIDsForMeshID(const QString& meshID, const QHash& fbxModels, const QMultiMap& _connectionParentMap) { + std::vector modelsForMesh; + if (fbxModels.contains(meshID)) { + modelsForMesh.push_back(meshID); + } else { + // This mesh may have more than one parent model, with different material and transform, representing a different instance of the mesh + for (const auto& parentID : _connectionParentMap.values(meshID)) { + if (fbxModels.contains(parentID)) { + modelsForMesh.push_back(parentID); + } + } + } + return modelsForMesh; +} + class ExtractedBlendshape { public: QString id; @@ -404,7 +420,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QVector blendshapes; QHash fbxModels; - QHash clusters; + QHash fbxClusters; QHash animationCurves; QHash typeFlags; @@ -515,8 +531,8 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const if (object.properties.at(2) == "Mesh") { meshes.insert(getID(object.properties), extractMesh(object, meshIndex, deduplicateIndices)); } else { // object.properties.at(2) == "Shape" - ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) }; - blendshapes.append(extracted); + ExtractedBlendshape blendshape = { getID(object.properties), extractBlendshape(object) }; + blendshapes.append(blendshape); } } else if (object.name == "Model") { QString name = getModelName(object.properties); @@ -690,8 +706,8 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const // add the blendshapes included in the model, if any if (mesh) { - foreach (const ExtractedBlendshape& extracted, blendshapes) { - addBlendshapes(extracted, blendshapeIndices.values(extracted.id.toLatin1()), *mesh); + foreach (const ExtractedBlendshape& blendshape, blendshapes) { + addBlendshapes(blendshape, blendshapeIndices.values(blendshape.id.toLatin1()), *mesh); } } @@ -1058,9 +1074,9 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } - // skip empty clusters + // skip empty fbxClusters if (cluster.indices.size() > 0 && cluster.weights.size() > 0) { - clusters.insert(getID(object.properties), cluster); + fbxClusters.insert(getID(object.properties), cluster); } } else if (object.properties.last() == "BlendShapeChannel") { @@ -1214,11 +1230,11 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } // assign the blendshapes to their corresponding meshes - foreach (const ExtractedBlendshape& extracted, blendshapes) { - QString blendshapeChannelID = _connectionParentMap.value(extracted.id); + foreach (const ExtractedBlendshape& blendshape, blendshapes) { + QString blendshapeChannelID = _connectionParentMap.value(blendshape.id); QString blendshapeID = _connectionParentMap.value(blendshapeChannelID); QString meshID = _connectionParentMap.value(blendshapeID); - addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); + addBlendshapes(blendshape, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); } // get offset transform from mapping @@ -1233,13 +1249,13 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QVector modelIDs; QSet remainingFBXModels; for (QHash::const_iterator fbxModel = fbxModels.constBegin(); fbxModel != fbxModels.constEnd(); fbxModel++) { - // models with clusters must be parented to the cluster top + // models with fbxClusters must be parented to the cluster top // Unless the model is a root node. bool isARootNode = !modelIDs.contains(_connectionParentMap.value(fbxModel.key())); if (!isARootNode) { foreach(const QString& deformerID, _connectionChildMap.values(fbxModel.key())) { foreach(const QString& clusterID, _connectionChildMap.values(deformerID)) { - if (!clusters.contains(clusterID)) { + if (!fbxClusters.contains(clusterID)) { continue; } QString topID = getTopModelID(_connectionParentMap, fbxModels, _connectionChildMap.value(clusterID), url); @@ -1283,12 +1299,18 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const // convert the models to joints hfmModel.hasSkeletonJoints = false; + + bool needMixamoHack = hfmModel.applicationName == "mixamo.com"; - foreach (const QString& modelID, modelIDs) { + std::vector transformForClusters; + transformForClusters.reserve((size_t)modelIDs.size()); + for (const QString& modelID : modelIDs) { const FBXModel& fbxModel = fbxModels[modelID]; HFMJoint joint; joint.parentIndex = fbxModel.parentIndex; - int jointIndex = hfmModel.joints.size(); + uint32_t jointIndex = (uint32_t)hfmModel.joints.size(); + + // Copy default joint parameters from model joint.translation = fbxModel.translation; // these are usually in centimeters joint.preTransform = fbxModel.preTransform; @@ -1299,35 +1321,62 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const joint.rotationMin = fbxModel.rotationMin; joint.rotationMax = fbxModel.rotationMax; - joint.hasGeometricOffset = fbxModel.hasGeometricOffset; - joint.geometricTranslation = fbxModel.geometricTranslation; - joint.geometricRotation = fbxModel.geometricRotation; - joint.geometricScaling = fbxModel.geometricScaling; + if (fbxModel.hasGeometricOffset) { + joint.geometricOffset = createMatFromScaleQuatAndPos(fbxModel.geometricScaling, fbxModel.geometricRotation, fbxModel.geometricTranslation); + } joint.isSkeletonJoint = fbxModel.isLimbNode; hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); + + joint.name = fbxModel.name; + + joint.bindTransformFoundInCluster = false; + + // With the basic joint information, we can start to calculate compound transform information + // modelIDs is ordered from parent to children, so we can safely get parent transforms from earlier joints as we iterate + + // Make adjustments to the static joint properties, and pre-calculate static transforms + if (applyUpAxisZRotation && joint.parentIndex == -1) { joint.rotation *= upAxisZRotation; joint.translation = upAxisZRotation * joint.translation; } + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + joint.localTransform = glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + if (joint.parentIndex == -1) { - joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; + joint.transform = joint.localTransform; + joint.globalTransform = hfmModel.offset * joint.localTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation); joint.distanceToParent = 0.0f; - } else { const HFMJoint& parentJoint = hfmModel.joints.at(joint.parentIndex); - joint.transform = parentJoint.transform * glm::translate(joint.translation) * - joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + joint.transform = parentJoint.transform * joint.localTransform; + joint.globalTransform = parentJoint.globalTransform * joint.localTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation; - joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform), - extractTranslation(joint.transform)); + joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform), extractTranslation(joint.transform)); } joint.inverseBindRotation = joint.inverseDefaultRotation; - joint.name = fbxModel.name; - joint.bindTransformFoundInCluster = false; + // If needed, separately calculate the FBX-specific transform used for inverse bind transform calculations + + glm::mat4 transformForCluster; + if (applyUpAxisZRotation) { + const glm::quat jointBindCombinedRotation = fbxModel.preRotation * fbxModel.rotation * fbxModel.postRotation; + const glm::mat4 localTransformForCluster = glm::translate(fbxModel.translation) * fbxModel.preTransform * glm::mat4_cast(jointBindCombinedRotation) * fbxModel.postTransform; + if (fbxModel.parentIndex != -1 && fbxModel.parentIndex < (int)jointIndex && !needMixamoHack) { + const glm::mat4& parenttransformForCluster = transformForClusters[fbxModel.parentIndex]; + transformForCluster = parenttransformForCluster * localTransformForCluster; + } else { + transformForCluster = localTransformForCluster; + } + } else { + transformForCluster = joint.transform; + } + transformForClusters.push_back(transformForCluster); + + // Initialize animation information next + // And also get the joint poses from the first frame of the animation, if present QString rotationID = localRotations.value(modelID); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); @@ -1355,13 +1404,10 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const joint.translation = hfmModel.animationFrames[i].translations[jointIndex]; joint.rotation = hfmModel.animationFrames[i].rotations[jointIndex]; } - } - hfmModel.joints.append(joint); - } - // NOTE: shapeVertices are in joint-frame - hfmModel.shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); + hfmModel.joints.push_back(joint); + } hfmModel.bindExtents.reset(); hfmModel.meshExtents.reset(); @@ -1400,233 +1446,202 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } #endif - hfmModel.materials = _hfmMaterials; + + std::unordered_map materialNameToID; + for (auto materialIt = _hfmMaterials.cbegin(); materialIt != _hfmMaterials.cend(); ++materialIt) { + materialNameToID[materialIt.key().toStdString()] = (uint32_t)hfmModel.materials.size(); + hfmModel.materials.push_back(materialIt.value()); + } // see if any materials have texture children bool materialsHaveTextures = checkMaterialsHaveTextures(_hfmMaterials, _textureFilenames, _connectionChildMap); for (QMap::iterator it = meshes.begin(); it != meshes.end(); it++) { - ExtractedMesh& extracted = it.value(); + const QString& meshID = it.key(); + const ExtractedMesh& extracted = it.value(); + const auto& partMaterialTextures = extracted.partMaterialTextures; - extracted.mesh.meshExtents.reset(); + uint32_t meshIndex = (uint32_t)hfmModel.meshes.size(); + meshIDsToMeshIndices.insert(meshID, meshIndex); + hfmModel.meshes.push_back(extracted.mesh); + hfm::Mesh& mesh = hfmModel.meshes.back(); - // accumulate local transforms - QString modelID = fbxModels.contains(it.key()) ? it.key() : _connectionParentMap.value(it.key()); - glm::mat4 modelTransform = getGlobalTransform(_connectionParentMap, fbxModels, modelID, hfmModel.applicationName == "mixamo.com", url); - - // compute the mesh extents from the transformed vertices - foreach (const glm::vec3& vertex, extracted.mesh.vertices) { - glm::vec3 transformedVertex = glm::vec3(modelTransform * glm::vec4(vertex, 1.0f)); - hfmModel.meshExtents.minimum = glm::min(hfmModel.meshExtents.minimum, transformedVertex); - hfmModel.meshExtents.maximum = glm::max(hfmModel.meshExtents.maximum, transformedVertex); - - extracted.mesh.meshExtents.minimum = glm::min(extracted.mesh.meshExtents.minimum, transformedVertex); - extracted.mesh.meshExtents.maximum = glm::max(extracted.mesh.meshExtents.maximum, transformedVertex); - extracted.mesh.modelTransform = modelTransform; - } - - // look for textures, material properties - // allocate the Part material library - // NOTE: extracted.partMaterialTextures is empty for FBX_DRACO_MESH_VERSION >= 2. In that case, the mesh part's materialID string is already defined. - int materialIndex = 0; - int textureIndex = 0; - QList children = _connectionChildMap.values(modelID); - for (int i = children.size() - 1; i >= 0; i--) { - - const QString& childID = children.at(i); - if (_hfmMaterials.contains(childID)) { - // the pure material associated with this part - HFMMaterial material = _hfmMaterials.value(childID); - - for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { - if (extracted.partMaterialTextures.at(j).first == materialIndex) { - HFMMeshPart& part = extracted.mesh.parts[j]; - part.materialID = material.materialID; - } - } - - materialIndex++; - } else if (_textureFilenames.contains(childID)) { - // NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale") - // I'm leaving the second parameter blank right now as this code may never be used. - HFMTexture texture = getTexture(childID, ""); - for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { - int partTexture = extracted.partMaterialTextures.at(j).second; - if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { - // TODO: DO something here that replaces this legacy code - // Maybe create a material just for this part with the correct textures? - // extracted.mesh.parts[j].diffuseTexture = texture; - } - } - textureIndex++; - } - } - - // find the clusters with which the mesh is associated - QVector clusterIDs; - foreach (const QString& childID, _connectionChildMap.values(it.key())) { - foreach (const QString& clusterID, _connectionChildMap.values(childID)) { - if (!clusters.contains(clusterID)) { - continue; - } - HFMCluster hfmCluster; - const Cluster& cluster = clusters[clusterID]; - clusterIDs.append(clusterID); - - // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion - // of skinning information in FBX - QString jointID = _connectionChildMap.value(clusterID); - hfmCluster.jointIndex = modelIDs.indexOf(jointID); - if (hfmCluster.jointIndex == -1) { - qCDebug(modelformat) << "Joint not in model list: " << jointID; - hfmCluster.jointIndex = 0; - } - - hfmCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; - - // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and - // sometimes floating point fuzz can be introduced after the inverse. - hfmCluster.inverseBindMatrix[0][3] = 0.0f; - hfmCluster.inverseBindMatrix[1][3] = 0.0f; - hfmCluster.inverseBindMatrix[2][3] = 0.0f; - hfmCluster.inverseBindMatrix[3][3] = 1.0f; - - hfmCluster.inverseBindTransform = Transform(hfmCluster.inverseBindMatrix); - - extracted.mesh.clusters.append(hfmCluster); - - // override the bind rotation with the transform link - HFMJoint& joint = hfmModel.joints[hfmCluster.jointIndex]; - joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink)); - joint.bindTransform = cluster.transformLink; - joint.bindTransformFoundInCluster = true; - - // update the bind pose extents - glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * joint.bindTransform); - hfmModel.bindExtents.addPoint(bindTranslation); - } - } - - // the last cluster is the root cluster - { - HFMCluster cluster; - cluster.jointIndex = modelIDs.indexOf(modelID); - if (cluster.jointIndex == -1) { + std::vector instanceModelIDs = getModelIDsForMeshID(meshID, fbxModels, _connectionParentMap); + // meshShapes will be added to hfmModel at the very end + std::vector meshShapes; + meshShapes.reserve(instanceModelIDs.size() * mesh.parts.size()); + for (const QString& modelID : instanceModelIDs) { + // The transform node has the same indexing order as the joints + int indexOfModelID = modelIDs.indexOf(modelID); + if (indexOfModelID == -1) { qCDebug(modelformat) << "Model not in model list: " << modelID; - cluster.jointIndex = 0; } - extracted.mesh.clusters.append(cluster); - } + const uint32_t transformIndex = (indexOfModelID == -1) ? 0 : (uint32_t)indexOfModelID; - // whether we're skinned depends on how many clusters are attached - if (clusterIDs.size() > 1) { - // this is a multi-mesh joint - const int WEIGHTS_PER_VERTEX = 4; - int numClusterIndices = extracted.mesh.vertices.size() * WEIGHTS_PER_VERTEX; - extracted.mesh.clusterIndices.fill(extracted.mesh.clusters.size() - 1, numClusterIndices); - QVector weightAccumulators; - weightAccumulators.fill(0.0f, numClusterIndices); + // partShapes will be added to meshShapes at the very end + std::vector partShapes { mesh.parts.size() }; + for (uint32_t i = 0; i < (uint32_t)partShapes.size(); ++i) { + hfm::Shape& shape = partShapes[i]; + shape.mesh = meshIndex; + shape.meshPart = i; + shape.joint = transformIndex; + } - for (int i = 0; i < clusterIDs.size(); i++) { - QString clusterID = clusterIDs.at(i); - const Cluster& cluster = clusters[clusterID]; - const HFMCluster& hfmCluster = extracted.mesh.clusters.at(i); - int jointIndex = hfmCluster.jointIndex; - HFMJoint& joint = hfmModel.joints[jointIndex]; + // For FBX_DRACO_MESH_VERSION < 2, or unbaked models, get materials from the partMaterialTextures + if (!partMaterialTextures.empty()) { + int materialIndex = 0; + int textureIndex = 0; + QList children = _connectionChildMap.values(modelID); + for (int i = children.size() - 1; i >= 0; i--) { + const QString& childID = children.at(i); + if (_hfmMaterials.contains(childID)) { + // the pure material associated with this part + const HFMMaterial& material = _hfmMaterials.value(childID); + for (int j = 0; j < partMaterialTextures.size(); j++) { + if (partMaterialTextures.at(j).first == materialIndex) { + hfm::Shape& shape = partShapes[j]; + shape.material = materialNameToID[material.materialID.toStdString()]; + } + } + materialIndex++; + } else if (_textureFilenames.contains(childID)) { + // NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale") + // I'm leaving the second parameter blank right now as this code may never be used. + HFMTexture texture = getTexture(childID, ""); + for (int j = 0; j < partMaterialTextures.size(); j++) { + int partTexture = partMaterialTextures.at(j).second; + if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { + // TODO: DO something here that replaces this legacy code + // Maybe create a material just for this part with the correct textures? + // material.albedoTexture = texture; + // partShapes[j].material = materialIndex; + } + } + textureIndex++; + } + } + } + // For baked models with FBX_DRACO_MESH_VERSION >= 2, get materials from extracted.materialIDPerMeshPart + if (!extracted.materialIDPerMeshPart.empty()) { + assert(partShapes.size() == extracted.materialIDPerMeshPart.size()); + for (uint32_t i = 0; i < (uint32_t)extracted.materialIDPerMeshPart.size(); ++i) { + hfm::Shape& shape = partShapes[i]; + const std::string& materialID = extracted.materialIDPerMeshPart[i]; + auto materialIt = materialNameToID.find(materialID); + if (materialIt != materialNameToID.end()) { + shape.material = materialIt->second; + } + } + } - glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; - ShapeVertices& points = hfmModel.shapeVertices.at(jointIndex); + // find the clusters with which the mesh is associated + QVector clusterIDs; + for (const QString& childID : _connectionChildMap.values(meshID)) { + for (const QString& clusterID : _connectionChildMap.values(childID)) { + if (!fbxClusters.contains(clusterID)) { + continue; + } + clusterIDs.append(clusterID); + } + } - for (int j = 0; j < cluster.indices.size(); j++) { - int oldIndex = cluster.indices.at(j); - float weight = cluster.weights.at(j); - for (QMultiHash::const_iterator it = extracted.newIndices.constFind(oldIndex); + // whether we're skinned depends on how many clusters are attached + if (clusterIDs.size() > 0) { + hfm::SkinDeformer skinDeformer; + auto& clusters = skinDeformer.clusters; + for (const auto& clusterID : clusterIDs) { + HFMCluster hfmCluster; + const Cluster& fbxCluster = fbxClusters[clusterID]; + + // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion + // of skinning information in FBX + QString jointID = _connectionChildMap.value(clusterID); + int indexOfJointID = modelIDs.indexOf(jointID); + if (indexOfJointID == -1) { + qCDebug(modelformat) << "Joint not in model list: " << jointID; + hfmCluster.jointIndex = 0; + } else { + hfmCluster.jointIndex = (uint32_t)indexOfJointID; + } + + const glm::mat4& transformForCluster = transformForClusters[transformIndex]; + hfmCluster.inverseBindMatrix = glm::inverse(fbxCluster.transformLink) * transformForCluster; + + // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and + // sometimes floating point fuzz can be introduced after the inverse. + hfmCluster.inverseBindMatrix[0][3] = 0.0f; + hfmCluster.inverseBindMatrix[1][3] = 0.0f; + hfmCluster.inverseBindMatrix[2][3] = 0.0f; + hfmCluster.inverseBindMatrix[3][3] = 1.0f; + + hfmCluster.inverseBindTransform = Transform(hfmCluster.inverseBindMatrix); + + clusters.push_back(hfmCluster); + + // override the bind rotation with the transform link + HFMJoint& joint = hfmModel.joints[hfmCluster.jointIndex]; + joint.inverseBindRotation = glm::inverse(extractRotation(fbxCluster.transformLink)); + joint.bindTransform = fbxCluster.transformLink; + joint.bindTransformFoundInCluster = true; + + // update the bind pose extents + glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * joint.bindTransform); + hfmModel.bindExtents.addPoint(bindTranslation); + } + + // the last cluster is the root cluster + HFMCluster cluster; + cluster.jointIndex = transformIndex; + clusters.push_back(cluster); + + // Skinned mesh instances have an hfm::SkinDeformer + std::vector skinClusters; + for (const auto& clusterID : clusterIDs) { + const Cluster& fbxCluster = fbxClusters[clusterID]; + skinClusters.emplace_back(); + hfm::SkinCluster& skinCluster = skinClusters.back(); + size_t indexWeightPairs = (size_t)std::min(fbxCluster.indices.size(), fbxCluster.weights.size()); + skinCluster.indices.reserve(indexWeightPairs); + skinCluster.weights.reserve(indexWeightPairs); + + for (int j = 0; j < fbxCluster.indices.size(); j++) { + int oldIndex = fbxCluster.indices.at(j); + float weight = fbxCluster.weights.at(j); + for (QMultiHash::const_iterator it = extracted.newIndices.constFind(oldIndex); it != extracted.newIndices.end() && it.key() == oldIndex; it++) { - int newIndex = it.value(); + int newIndex = it.value(); - // remember vertices with at least 1/4 weight - // FIXME: vertices with no weightpainting won't get recorded here - const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; - if (weight >= EXPANSION_WEIGHT_THRESHOLD) { - // transform to joint-frame and save for later - const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex)); - points.push_back(extractTranslation(vertexTransform)); - } - - // look for an unused slot in the weights vector - int weightIndex = newIndex * WEIGHTS_PER_VERTEX; - int lowestIndex = -1; - float lowestWeight = FLT_MAX; - int k = 0; - for (; k < WEIGHTS_PER_VERTEX; k++) { - if (weightAccumulators[weightIndex + k] == 0.0f) { - extracted.mesh.clusterIndices[weightIndex + k] = i; - weightAccumulators[weightIndex + k] = weight; - break; - } - if (weightAccumulators[weightIndex + k] < lowestWeight) { - lowestIndex = k; - lowestWeight = weightAccumulators[weightIndex + k]; - } - } - if (k == WEIGHTS_PER_VERTEX && weight > lowestWeight) { - // no space for an additional weight; we must replace the lowest - weightAccumulators[weightIndex + lowestIndex] = weight; - extracted.mesh.clusterIndices[weightIndex + lowestIndex] = i; + skinCluster.indices.push_back(newIndex); + skinCluster.weights.push_back(weight); } } } - } - - // now that we've accumulated the most relevant weights for each vertex - // normalize and compress to 16-bits - extracted.mesh.clusterWeights.fill(0, numClusterIndices); - int numVertices = extracted.mesh.vertices.size(); - for (int i = 0; i < numVertices; ++i) { - int j = i * WEIGHTS_PER_VERTEX; - - // normalize weights into uint16_t - float totalWeight = 0.0f; - for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - totalWeight += weightAccumulators[k]; - } - - const float ALMOST_HALF = 0.499f; - if (totalWeight > 0.0f) { - float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; - for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - extracted.mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); + // It seems odd that this mesh-related code should be inside of the for loop for instanced model IDs. + // However, in practice, skinned FBX models appear to not be instanced, as the skinning includes both the weights and joints. + { + hfm::ReweightedDeformers reweightedDeformers = hfm::getReweightedDeformers(mesh.vertices.size(), skinClusters); + if (reweightedDeformers.trimmedToMatch) { + qDebug(modelformat) << "FBXSerializer -- The number of indices and weights for a skinning deformer had different sizes and have been trimmed to match"; } - } else { - extracted.mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + mesh.clusterIndices = std::move(reweightedDeformers.indices); + mesh.clusterWeights = std::move(reweightedDeformers.weights); + mesh.clusterWeightsPerVertex = reweightedDeformers.weightsPerVertex; + } + + // Store the model's dynamic transform, and put its ID in the shapes + uint32_t skinDeformerID = (uint32_t)hfmModel.skinDeformers.size(); + hfmModel.skinDeformers.push_back(skinDeformer); + for (hfm::Shape& shape : partShapes) { + shape.skinDeformer = skinDeformerID; } } - } else { - // this is a single-joint mesh - const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); - int jointIndex = firstHFMCluster.jointIndex; - HFMJoint& joint = hfmModel.joints[jointIndex]; - // transform cluster vertices to joint-frame and save for later - glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; - ShapeVertices& points = hfmModel.shapeVertices.at(jointIndex); - foreach (const glm::vec3& vertex, extracted.mesh.vertices) { - const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); - points.push_back(extractTranslation(vertexTransform)); - } - - // Apply geometric offset, if present, by transforming the vertices directly - if (joint.hasGeometricOffset) { - glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(joint.geometricScaling, joint.geometricRotation, joint.geometricTranslation); - for (int i = 0; i < extracted.mesh.vertices.size(); i++) { - extracted.mesh.vertices[i] = transformPoint(geometricOffset, extracted.mesh.vertices[i]); - } - } + // Store the parts for this mesh (or instance of this mesh, as the case may be) + meshShapes.insert(meshShapes.cend(), partShapes.cbegin(), partShapes.cend()); } - hfmModel.meshes.append(extracted.mesh); - int meshIndex = hfmModel.meshes.size() - 1; - meshIDsToMeshIndices.insert(it.key(), meshIndex); + // Store the shapes for the mesh (or multiple instances of the mesh, as the case may be) + hfmModel.shapes.insert(hfmModel.shapes.cend(), meshShapes.cbegin(), meshShapes.cend()); } // attempt to map any meshes to a named model @@ -1645,14 +1660,6 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } - if (applyUpAxisZRotation) { - hfmModelPtr->meshExtents.transform(glm::mat4_cast(upAxisZRotation)); - hfmModelPtr->bindExtents.transform(glm::mat4_cast(upAxisZRotation)); - for (auto &mesh : hfmModelPtr->meshes) { - mesh.modelTransform *= glm::mat4_cast(upAxisZRotation); - mesh.meshExtents.transform(glm::mat4_cast(upAxisZRotation)); - } - } return hfmModelPtr; } diff --git a/libraries/fbx/src/FBXSerializer.h b/libraries/fbx/src/FBXSerializer.h index 7d41f98444..2044d82710 100644 --- a/libraries/fbx/src/FBXSerializer.h +++ b/libraries/fbx/src/FBXSerializer.h @@ -100,7 +100,15 @@ public: {} }; -class ExtractedMesh; +class ExtractedMesh { +public: + hfm::Mesh mesh; + std::vector materialIDPerMeshPart; + QMultiHash newIndices; + QVector > blendshapeIndexMaps; + QVector > partMaterialTextures; + QHash texcoordSetMap; +}; class FBXSerializer : public HFMSerializer { public: diff --git a/libraries/fbx/src/FBXSerializer_Mesh.cpp b/libraries/fbx/src/FBXSerializer_Mesh.cpp index 802db4b428..e687f5e9f2 100644 --- a/libraries/fbx/src/FBXSerializer_Mesh.cpp +++ b/libraries/fbx/src/FBXSerializer_Mesh.cpp @@ -355,7 +355,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me // Check for additional metadata unsigned int dracoMeshNodeVersion = 1; - std::vector dracoMaterialList; + std::vector dracoMaterialList; for (const auto& dracoChild : child.children) { if (dracoChild.name == "FBXDracoMeshVersion") { if (!dracoChild.properties.isEmpty()) { @@ -364,7 +364,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me } else if (dracoChild.name == "MaterialList") { dracoMaterialList.reserve(dracoChild.properties.size()); for (const auto& materialID : dracoChild.properties) { - dracoMaterialList.push_back(materialID.toString()); + dracoMaterialList.push_back(materialID.toString().toStdString()); } } } @@ -486,21 +486,20 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me // grab or setup the HFMMeshPart for the part this face belongs to int& partIndexPlusOne = materialTextureParts[materialTexture]; if (partIndexPlusOne == 0) { - data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1); - HFMMeshPart& part = data.extracted.mesh.parts.back(); + data.extracted.mesh.parts.emplace_back(); - // Figure out what material this part is + // Figure out if this is the older way of defining the per-part material for baked FBX if (dracoMeshNodeVersion >= 2) { - // Define the materialID now - if (materialID < dracoMaterialList.size()) { - part.materialID = dracoMaterialList[materialID]; - } + // Define the materialID for this mesh part index + uint16_t safeMaterialID = materialID < dracoMaterialList.size() ? materialID : 0; + data.extracted.materialIDPerMeshPart.push_back(dracoMaterialList[safeMaterialID].c_str()); } else { // Define the materialID later, based on the order of first appearance of the materials in the _connectionChildMap data.extracted.partMaterialTextures.append(materialTexture); } + // in dracoMeshNodeVersion >= 2, fbx meshes have their per-part materials already defined in data.extracted.materialIDPerMeshPart - partIndexPlusOne = data.extracted.mesh.parts.size(); + partIndexPlusOne = (int)data.extracted.mesh.parts.size(); } // give the mesh part this index @@ -535,7 +534,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me if (partIndex == 0) { data.extracted.partMaterialTextures.append(materialTexture); data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1); - partIndex = data.extracted.mesh.parts.size(); + partIndex = (int)data.extracted.mesh.parts.size(); } HFMMeshPart& part = data.extracted.mesh.parts[partIndex - 1]; diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp index b6f109c217..5f5b7cf637 100644 --- a/libraries/fbx/src/FST.cpp +++ b/libraries/fbx/src/FST.cpp @@ -77,7 +77,7 @@ FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePat mapping.insert(JOINT_FIELD, joints); QVariantHash jointIndices; - for (int i = 0; i < hfmModel.joints.size(); i++) { + for (size_t i = 0; i < (size_t)hfmModel.joints.size(); i++) { jointIndices.insert(hfmModel.joints.at(i).name, QString::number(i)); } mapping.insert(JOINT_INDEX_FIELD, jointIndices); diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index e117e1f211..9d9d16d733 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -25,9 +25,13 @@ #include #include +#include +#include #include #include +#include + #include #include #include @@ -37,30 +41,57 @@ #include "FBXSerializer.h" -#define GLTF_GET_INDICIES(accCount) int index1 = (indices[n + 0] * accCount); int index2 = (indices[n + 1] * accCount); int index3 = (indices[n + 2] * accCount); +#define GLTF_GET_INDICIES(accCount) \ + int index1 = (indices[n + 0] * accCount); \ + int index2 = (indices[n + 1] * accCount); \ + int index3 = (indices[n + 2] * accCount); -#define GLTF_APPEND_ARRAY_1(newArray, oldArray) GLTF_GET_INDICIES(1) \ -newArray.append(oldArray[index1]); \ -newArray.append(oldArray[index2]); \ -newArray.append(oldArray[index3]); +#define GLTF_APPEND_ARRAY_1(newArray, oldArray) \ + GLTF_GET_INDICIES(1) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index3]); -#define GLTF_APPEND_ARRAY_2(newArray, oldArray) GLTF_GET_INDICIES(2) \ -newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); \ -newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); \ -newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); +#define GLTF_APPEND_ARRAY_2(newArray, oldArray) \ + GLTF_GET_INDICIES(2) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index1 + 1]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index2 + 1]); \ + newArray.append(oldArray[index3]); \ + newArray.append(oldArray[index3 + 1]); -#define GLTF_APPEND_ARRAY_3(newArray, oldArray) GLTF_GET_INDICIES(3) \ -newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); \ -newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); \ -newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); +#define GLTF_APPEND_ARRAY_3(newArray, oldArray) \ + GLTF_GET_INDICIES(3) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index1 + 1]); \ + newArray.append(oldArray[index1 + 2]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index2 + 1]); \ + newArray.append(oldArray[index2 + 2]); \ + newArray.append(oldArray[index3]); \ + newArray.append(oldArray[index3 + 1]); \ + newArray.append(oldArray[index3 + 2]); -#define GLTF_APPEND_ARRAY_4(newArray, oldArray) GLTF_GET_INDICIES(4) \ -newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); newArray.append(oldArray[index1 + 3]); \ -newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); newArray.append(oldArray[index2 + 3]); \ -newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); newArray.append(oldArray[index3 + 3]); +#define GLTF_APPEND_ARRAY_4(newArray, oldArray) \ + GLTF_GET_INDICIES(4) \ + newArray.append(oldArray[index1]); \ + newArray.append(oldArray[index1 + 1]); \ + newArray.append(oldArray[index1 + 2]); \ + newArray.append(oldArray[index1 + 3]); \ + newArray.append(oldArray[index2]); \ + newArray.append(oldArray[index2 + 1]); \ + newArray.append(oldArray[index2 + 2]); \ + newArray.append(oldArray[index2 + 3]); \ + newArray.append(oldArray[index3]); \ + newArray.append(oldArray[index3 + 1]); \ + newArray.append(oldArray[index3 + 2]); \ + newArray.append(oldArray[index3 + 3]); -bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname, - QString& value, QMap& defined) { +bool GLTFSerializer::getStringVal(const QJsonObject& object, + const QString& fieldname, + QString& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); if (_defined) { value = object[fieldname].toString(); @@ -69,8 +100,10 @@ bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fiel return _defined; } -bool GLTFSerializer::getBoolVal(const QJsonObject& object, const QString& fieldname, - bool& value, QMap& defined) { +bool GLTFSerializer::getBoolVal(const QJsonObject& object, + const QString& fieldname, + bool& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isBool()); if (_defined) { value = object[fieldname].toBool(); @@ -79,8 +112,7 @@ bool GLTFSerializer::getBoolVal(const QJsonObject& object, const QString& fieldn return _defined; } -bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname, - int& value, QMap& defined) { +bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldname, int& value, QMap& defined) { bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); if (_defined) { value = object[fieldname].toInt(); @@ -89,8 +121,10 @@ bool GLTFSerializer::getIntVal(const QJsonObject& object, const QString& fieldna return _defined; } -bool GLTFSerializer::getDoubleVal(const QJsonObject& object, const QString& fieldname, - double& value, QMap& defined) { +bool GLTFSerializer::getDoubleVal(const QJsonObject& object, + const QString& fieldname, + double& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); if (_defined) { value = object[fieldname].toDouble(); @@ -98,8 +132,10 @@ bool GLTFSerializer::getDoubleVal(const QJsonObject& object, const QString& fiel defined.insert(fieldname, _defined); return _defined; } -bool GLTFSerializer::getObjectVal(const QJsonObject& object, const QString& fieldname, - QJsonObject& value, QMap& defined) { +bool GLTFSerializer::getObjectVal(const QJsonObject& object, + const QString& fieldname, + QJsonObject& value, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); if (_defined) { value = object[fieldname].toObject(); @@ -108,12 +144,14 @@ bool GLTFSerializer::getObjectVal(const QJsonObject& object, const QString& fiel return _defined; } -bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, const QString& fieldname, - QVector& values, QMap& defined) { +bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, + const QString& fieldname, + QVector& values, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { QJsonArray arr = object[fieldname].toArray(); - foreach(const QJsonValue & v, arr) { + for (const QJsonValue& v : arr) { if (!v.isNull()) { values.push_back(v.toInt()); } @@ -123,12 +161,14 @@ bool GLTFSerializer::getIntArrayVal(const QJsonObject& object, const QString& fi return _defined; } -bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, - QVector& values, QMap& defined) { +bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, + const QString& fieldname, + QVector& values, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { QJsonArray arr = object[fieldname].toArray(); - foreach(const QJsonValue & v, arr) { + for (const QJsonValue& v : arr) { if (v.isDouble()) { values.push_back(v.toDouble()); } @@ -138,8 +178,10 @@ bool GLTFSerializer::getDoubleArrayVal(const QJsonObject& object, const QString& return _defined; } -bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, - QJsonArray& objects, QMap& defined) { +bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, + const QString& fieldname, + QJsonArray& objects, + QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { objects = object[fieldname].toArray(); @@ -149,7 +191,7 @@ bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& } hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) { - int byte = 4; + int byte = 4; int jsonStart = data.indexOf("JSON", Qt::CaseSensitive); int binStart = data.indexOf("BIN", Qt::CaseSensitive); int jsonLength, binLength; @@ -173,8 +215,7 @@ hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) { return jsonChunk; } -int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) -{ +int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) { if (type == "POINTS") { return GLTFMeshPrimitivesRenderingMode::POINTS; } @@ -199,8 +240,7 @@ int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) return GLTFMeshPrimitivesRenderingMode::TRIANGLES; } -int GLTFSerializer::getAccessorType(const QString& type) -{ +int GLTFSerializer::getAccessorType(const QString& type) { if (type == "SCALAR") { return GLTFAccessorType::SCALAR; } @@ -238,8 +278,7 @@ graphics::MaterialKey::OpacityMapMode GLTFSerializer::getMaterialAlphaMode(const return graphics::MaterialKey::OPACITY_MAP_BLEND; } -int GLTFSerializer::getCameraType(const QString& type) -{ +int GLTFSerializer::getCameraType(const QString& type) { if (type == "orthographic") { return GLTFCameraTypes::ORTHOGRAPHIC; } @@ -249,8 +288,7 @@ int GLTFSerializer::getCameraType(const QString& type) return GLTFCameraTypes::PERSPECTIVE; } -int GLTFSerializer::getImageMimeType(const QString& mime) -{ +int GLTFSerializer::getImageMimeType(const QString& mime) { if (mime == "image/jpeg") { return GLTFImageMimetype::JPEG; } @@ -260,8 +298,7 @@ int GLTFSerializer::getImageMimeType(const QString& mime) return GLTFImageMimetype::JPEG; } -int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation) -{ +int GLTFSerializer::getAnimationSamplerInterpolation(const QString& interpolation) { if (interpolation == "LINEAR") { return GLTFAnimationSamplerInterpolation::LINEAR; } @@ -272,8 +309,7 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) { QJsonObject jsAsset; bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); if (isAssetDefined) { - if (!getStringVal(jsAsset, "version", _file.asset.version, - _file.asset.defined) || _file.asset.version != "2.0") { + if (!getStringVal(jsAsset, "version", _file.asset.version, _file.asset.defined) || _file.asset.version != "2.0") { return false; } getStringVal(jsAsset, "generator", _file.asset.generator, _file.asset.defined); @@ -282,7 +318,8 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) { return isAssetDefined; } -GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices(const QJsonObject& object) { +GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices( + const QJsonObject& object) { GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices accessorSparseIndices; getIntVal(object, "bufferView", accessorSparseIndices.bufferView, accessorSparseIndices.defined); @@ -292,7 +329,8 @@ GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::crea return accessorSparseIndices; } -GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues(const QJsonObject& object) { +GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues( + const QJsonObject& object) { GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues accessorSparseValues; getIntVal(object, "bufferView", accessorSparseValues.bufferView, accessorSparseValues.defined); @@ -319,7 +357,7 @@ GLTFAccessor::GLTFAccessorSparse GLTFSerializer::createAccessorSparse(const QJso bool GLTFSerializer::addAccessor(const QJsonObject& object) { GLTFAccessor accessor; - + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); getIntVal(object, "componentType", accessor.componentType, accessor.defined); @@ -345,10 +383,10 @@ bool GLTFSerializer::addAccessor(const QJsonObject& object) { bool GLTFSerializer::addAnimation(const QJsonObject& object) { GLTFAnimation animation; - + QJsonArray channels; if (getObjectArrayVal(object, "channels", channels, animation.defined)) { - foreach(const QJsonValue & v, channels) { + for (const QJsonValue& v : channels) { if (v.isObject()) { GLTFChannel channel; getIntVal(v.toObject(), "sampler", channel.sampler, channel.defined); @@ -356,14 +394,14 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); - } + } } } } QJsonArray samplers; if (getObjectArrayVal(object, "samplers", samplers, animation.defined)) { - foreach(const QJsonValue & v, samplers) { + for (const QJsonValue& v : samplers) { if (v.isObject()) { GLTFAnimationSampler sampler; getIntVal(v.toObject(), "input", sampler.input, sampler.defined); @@ -375,7 +413,7 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { } } } - + _file.animations.push_back(animation); return true; @@ -383,20 +421,20 @@ bool GLTFSerializer::addAnimation(const QJsonObject& object) { bool GLTFSerializer::addBufferView(const QJsonObject& object) { GLTFBufferView bufferview; - + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); getIntVal(object, "target", bufferview.target, bufferview.defined); - + _file.bufferviews.push_back(bufferview); - + return true; } bool GLTFSerializer::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; - + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); if (_url.toString().endsWith("glb")) { @@ -412,13 +450,13 @@ bool GLTFSerializer::addBuffer(const QJsonObject& object) { } } _file.buffers.push_back(buffer); - + return true; } bool GLTFSerializer::addCamera(const QJsonObject& object) { GLTFCamera camera; - + QJsonObject jsPerspective; QJsonObject jsOrthographic; QString type; @@ -438,15 +476,15 @@ bool GLTFSerializer::addCamera(const QJsonObject& object) { } else if (getStringVal(object, "type", type, camera.defined)) { camera.type = getCameraType(type); } - + _file.cameras.push_back(camera); - + return true; } bool GLTFSerializer::addImage(const QJsonObject& object) { GLTFImage image; - + QString mime; getStringVal(object, "uri", image.uri, image.defined); if (image.uri.contains("data:image/png;base64,")) { @@ -456,16 +494,18 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { } if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); - + _file.images.push_back(image); return true; } -bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, const QString& field, - int& outidx, QMap& defined) { +bool GLTFSerializer::getIndexFromObject(const QJsonObject& object, + const QString& field, + int& outidx, + QMap& defined) { QJsonObject subobject; if (getObjectVal(object, field, subobject, defined)) { QMap tmpdefined = QMap(); @@ -490,23 +530,18 @@ bool GLTFSerializer::addMaterial(const QJsonObject& object) { getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); QJsonObject jsMetallicRoughness; if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { - getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", - material.pbrMetallicRoughness.baseColorFactor, + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", material.pbrMetallicRoughness.baseColorFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "baseColorTexture", - material.pbrMetallicRoughness.baseColorTexture, + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", material.pbrMetallicRoughness.baseColorTexture, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "metallicFactor", - material.pbrMetallicRoughness.metallicFactor, + getDoubleVal(jsMetallicRoughness, "metallicFactor", material.pbrMetallicRoughness.metallicFactor, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "roughnessFactor", - material.pbrMetallicRoughness.roughnessFactor, + getDoubleVal(jsMetallicRoughness, "roughnessFactor", material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", - material.pbrMetallicRoughness.metallicRoughnessTexture, - material.pbrMetallicRoughness.defined); + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, material.pbrMetallicRoughness.defined); } - _file.materials.push_back(material); + _file.materials.push_back(material); return true; } @@ -518,18 +553,18 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { QJsonArray jsPrimitives; object.keys(); if (getObjectArrayVal(object, "primitives", jsPrimitives, mesh.defined)) { - foreach(const QJsonValue & prim, jsPrimitives) { + for (const QJsonValue& prim : jsPrimitives) { if (prim.isObject()) { GLTFMeshPrimitive primitive; QJsonObject jsPrimitive = prim.toObject(); getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); - + QJsonObject jsAttributes; if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { QStringList attrKeys = jsAttributes.keys(); - foreach(const QString & attrKey, attrKeys) { + for (const QString& attrKey : attrKeys) { int attrVal; getIntVal(jsAttributes, attrKey, attrVal, primitive.attributes.defined); primitive.attributes.values.insert(attrKey, attrVal); @@ -537,14 +572,13 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { } QJsonArray jsTargets; - if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) - { - foreach(const QJsonValue & tar, jsTargets) { + if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) { + for (const QJsonValue& tar : jsTargets) { if (tar.isObject()) { QJsonObject jsTarget = tar.toObject(); QStringList tarKeys = jsTarget.keys(); GLTFMeshPrimitiveAttr target; - foreach(const QString & tarKey, tarKeys) { + for (const QString& tarKey : tarKeys) { int tarVal; getIntVal(jsTarget, tarKey, tarVal, target.defined); target.values.insert(tarKey, tarVal); @@ -552,7 +586,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { primitive.targets.push_back(target); } } - } + } mesh.primitives.push_back(primitive); } } @@ -563,9 +597,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { if (getObjectVal(object, "extras", jsExtras, mesh.defined)) { QJsonArray jsTargetNames; if (getObjectArrayVal(jsExtras, "targetNames", jsTargetNames, extras.defined)) { - foreach (const QJsonValue& tarName, jsTargetNames) { - extras.targetNames.push_back(tarName.toString()); - } + foreach (const QJsonValue& tarName, jsTargetNames) { extras.targetNames.push_back(tarName.toString()); } } mesh.extras = extras; } @@ -577,7 +609,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) { bool GLTFSerializer::addNode(const QJsonObject& object) { GLTFNode node; - + getStringVal(object, "name", node.name, node.defined); getIntVal(object, "camera", node.camera, node.defined); getIntVal(object, "mesh", node.mesh, node.defined); @@ -606,7 +638,6 @@ bool GLTFSerializer::addSampler(const QJsonObject& object) { _file.samplers.push_back(sampler); return true; - } bool GLTFSerializer::addScene(const QJsonObject& object) { @@ -632,10 +663,10 @@ bool GLTFSerializer::addSkin(const QJsonObject& object) { } bool GLTFSerializer::addTexture(const QJsonObject& object) { - GLTFTexture texture; + GLTFTexture texture; getIntVal(object, "sampler", texture.sampler, texture.defined); getIntVal(object, "source", texture.source, texture.defined); - + _file.textures.push_back(texture); return true; @@ -648,8 +679,8 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { if (_url.toString().endsWith("glb") && data.indexOf("glTF") == 0 && data.contains("JSON")) { jsonChunk = setGLBChunks(data); - } - + } + QJsonDocument d = QJsonDocument::fromJson(jsonChunk); QJsonObject jsFile = d.object(); @@ -657,7 +688,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { if (success) { QJsonArray accessors; if (getObjectArrayVal(jsFile, "accessors", accessors, _file.defined)) { - foreach(const QJsonValue & accVal, accessors) { + for (const QJsonValue& accVal : accessors) { if (accVal.isObject()) { success = success && addAccessor(accVal.toObject()); } @@ -666,7 +697,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray animations; if (getObjectArrayVal(jsFile, "animations", animations, _file.defined)) { - foreach(const QJsonValue & animVal, accessors) { + for (const QJsonValue& animVal : accessors) { if (animVal.isObject()) { success = success && addAnimation(animVal.toObject()); } @@ -675,7 +706,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray bufferViews; if (getObjectArrayVal(jsFile, "bufferViews", bufferViews, _file.defined)) { - foreach(const QJsonValue & bufviewVal, bufferViews) { + for (const QJsonValue& bufviewVal : bufferViews) { if (bufviewVal.isObject()) { success = success && addBufferView(bufviewVal.toObject()); } @@ -684,7 +715,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray buffers; if (getObjectArrayVal(jsFile, "buffers", buffers, _file.defined)) { - foreach(const QJsonValue & bufVal, buffers) { + for (const QJsonValue& bufVal : buffers) { if (bufVal.isObject()) { success = success && addBuffer(bufVal.toObject()); } @@ -693,7 +724,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray cameras; if (getObjectArrayVal(jsFile, "cameras", cameras, _file.defined)) { - foreach(const QJsonValue & camVal, cameras) { + for (const QJsonValue& camVal : cameras) { if (camVal.isObject()) { success = success && addCamera(camVal.toObject()); } @@ -702,7 +733,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray images; if (getObjectArrayVal(jsFile, "images", images, _file.defined)) { - foreach(const QJsonValue & imgVal, images) { + for (const QJsonValue& imgVal : images) { if (imgVal.isObject()) { success = success && addImage(imgVal.toObject()); } @@ -711,7 +742,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray materials; if (getObjectArrayVal(jsFile, "materials", materials, _file.defined)) { - foreach(const QJsonValue & matVal, materials) { + for (const QJsonValue& matVal : materials) { if (matVal.isObject()) { success = success && addMaterial(matVal.toObject()); } @@ -720,7 +751,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray meshes; if (getObjectArrayVal(jsFile, "meshes", meshes, _file.defined)) { - foreach(const QJsonValue & meshVal, meshes) { + for (const QJsonValue& meshVal : meshes) { if (meshVal.isObject()) { success = success && addMesh(meshVal.toObject()); } @@ -729,7 +760,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray nodes; if (getObjectArrayVal(jsFile, "nodes", nodes, _file.defined)) { - foreach(const QJsonValue & nodeVal, nodes) { + for (const QJsonValue& nodeVal : nodes) { if (nodeVal.isObject()) { success = success && addNode(nodeVal.toObject()); } @@ -738,7 +769,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray samplers; if (getObjectArrayVal(jsFile, "samplers", samplers, _file.defined)) { - foreach(const QJsonValue & samVal, samplers) { + for (const QJsonValue& samVal : samplers) { if (samVal.isObject()) { success = success && addSampler(samVal.toObject()); } @@ -747,7 +778,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray scenes; if (getObjectArrayVal(jsFile, "scenes", scenes, _file.defined)) { - foreach(const QJsonValue & sceneVal, scenes) { + for (const QJsonValue& sceneVal : scenes) { if (sceneVal.isObject()) { success = success && addScene(sceneVal.toObject()); } @@ -756,7 +787,7 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray skins; if (getObjectArrayVal(jsFile, "skins", skins, _file.defined)) { - foreach(const QJsonValue & skinVal, skins) { + for (const QJsonValue& skinVal : skins) { if (skinVal.isObject()) { success = success && addSkin(skinVal.toObject()); } @@ -765,51 +796,22 @@ bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) { QJsonArray textures; if (getObjectArrayVal(jsFile, "textures", textures, _file.defined)) { - foreach(const QJsonValue & texVal, textures) { + for (const QJsonValue& texVal : textures) { if (texVal.isObject()) { success = success && addTexture(texVal.toObject()); } } } - } + } return success; } -glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { - glm::mat4 tmat = glm::mat4(1.0); - - if (node.defined["matrix"] && node.matrix.size() == 16) { - tmat = glm::mat4(node.matrix[0], node.matrix[1], node.matrix[2], node.matrix[3], - node.matrix[4], node.matrix[5], node.matrix[6], node.matrix[7], - node.matrix[8], node.matrix[9], node.matrix[10], node.matrix[11], - node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); - } else { - - if (node.defined["scale"] && node.scale.size() == 3) { - glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); - glm::mat4 s = glm::mat4(1.0); - s = glm::scale(s, scale); - tmat = s * tmat; - } - - if (node.defined["rotation"] && node.rotation.size() == 4) { - //quat(x,y,z,w) to quat(w,x,y,z) - glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); - tmat = glm::mat4_cast(rotquat) * tmat; - } - - if (node.defined["translation"] && node.translation.size() == 3) { - glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); - glm::mat4 t = glm::mat4(1.0); - t = glm::translate(t, trans); - tmat = t * tmat; - } - } - return tmat; +const glm::mat4& GLTFSerializer::getModelTransform(const GLTFNode& node) { + return node.transform; } void GLTFSerializer::getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues) { - for (auto &skin : _file.skins) { + for (auto& skin : _file.skins) { GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices]; QVector matrices; addArrayFromAccessor(indicesAccessor, matrices); @@ -826,111 +828,200 @@ void GLTFSerializer::generateTargetData(int index, float weight, QVector; +ParentIndexMap findParentIndices(const QVector& nodes) { + ParentIndexMap parentIndices; + int numNodes = nodes.size(); + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + auto& gltfNode = nodes[nodeIndex]; + for (const auto& childIndex : gltfNode.children) { + parentIndices[childIndex] = nodeIndex; + } + } + return parentIndices; +} + +bool requiresNodeReordering(const ParentIndexMap& map) { + for (const auto& entry : map) { + if (entry.first < entry.second) { + return true; + } + } + return false; +} + +int findEdgeCount(const ParentIndexMap& parentIndices, int nodeIndex) { + auto parentsEnd = parentIndices.end(); + ParentIndexMap::const_iterator itr; + int result = 0; + while (parentsEnd != (itr = parentIndices.find(nodeIndex))) { + nodeIndex = itr->second; + ++result; + } + return result; +} + +using IndexBag = std::unordered_set; +using EdgeCountMap = std::map; +EdgeCountMap findEdgeCounts(int numNodes, const ParentIndexMap& map) { + EdgeCountMap edgeCounts; + // For each item, determine how many tranversals to a root node + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + // How many steps between this node and a root node? + int edgeCount = findEdgeCount(map, nodeIndex); + // Populate the result map + edgeCounts[edgeCount].insert(nodeIndex); + } + return edgeCounts; +} + +using ReorderMap = std::unordered_map; +ReorderMap buildReorderMap(const EdgeCountMap& map) { + ReorderMap result; + int newIndex = 0; + for (const auto& entry : map) { + const IndexBag& oldIndices = entry.second; + for (const auto& oldIndex : oldIndices) { + result.insert({ oldIndex, newIndex }); + ++newIndex; + } + } + return result; +} + +void reorderNodeIndices(QVector& indices, const ReorderMap& oldToNewIndexMap) { + for (auto& index : indices) { + index = oldToNewIndexMap.at(index); + } +} + +} // namespace gltf + +void GLTFFile::populateMaterialNames() { + // Build material names + QSet usedNames; + for (const auto& material : materials) { + if (!material.name.isEmpty()) { + usedNames.insert(material.name); + } + } + + int ukcount = 0; + const QString unknown{ "Default_%1" }; + for (auto& material : materials) { + QString generatedName = unknown.arg(ukcount++); + while (usedNames.contains(generatedName)) { + generatedName = unknown.arg(ukcount++); + } + material.name = generatedName; + material.defined.insert("name", true); + usedNames.insert(generatedName); + } +} + +void GLTFFile::reorderNodes(const std::unordered_map& oldToNewIndexMap) { + int numNodes = nodes.size(); + assert(numNodes == oldToNewIndexMap.size()); + QVector newNodes; + newNodes.resize(numNodes); + for (int oldIndex = 0; oldIndex < numNodes; ++oldIndex) { + const auto& oldNode = nodes[oldIndex]; + int newIndex = oldToNewIndexMap.at(oldIndex); + auto& newNode = newNodes[newIndex]; + // Write the new node + newNode = oldNode; + // Fixup the child indices + gltf::reorderNodeIndices(newNode.children, oldToNewIndexMap); + } + newNodes.swap(nodes); + + for (auto& subScene : scenes) { + gltf::reorderNodeIndices(subScene.nodes, oldToNewIndexMap); + } +} + +// Ensure that the GLTF nodes are ordered so +void GLTFFile::sortNodes() { + // Find all the parents + auto parentIndices = gltf::findParentIndices(nodes); + // If the nodes are already in a good order, we're done + if (!gltf::requiresNodeReordering(parentIndices)) { + return; + } + + auto edgeCounts = gltf::findEdgeCounts(nodes.size(), parentIndices); + auto oldToNewIndexMap = gltf::buildReorderMap(edgeCounts); + reorderNodes(oldToNewIndexMap); + assert(!gltf::requiresNodeReordering(gltf::findParentIndices(nodes))); +} + +void GLTFNode::normalizeTransform() { + if (defined["matrix"] && matrix.size() == 16) { + transform = glm::make_mat4(matrix.constData()); + } else { + transform = glm::mat4(1.0); + if (defined["scale"] && scale.size() == 3) { + glm::vec3 scaleVec = glm::make_vec3(scale.data()); + transform = glm::scale(transform, scaleVec); + } + + if (defined["rotation"] && rotation.size() == 4) { + glm::quat rotQ = glm::make_quat(rotation.data()); + transform = glm::mat4_cast(rotQ) * transform; + } + + if (defined["translation"] && translation.size() == 3) { + glm::vec3 transV = glm::make_vec3(translation.data()); + transform = glm::translate(glm::mat4(1.0), transV) * transform; + } + } +} + +void GLTFFile::normalizeNodeTransforms() { + for (auto& node : nodes) { + node.normalizeTransform(); + } +} + bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url) { int numNodes = _file.nodes.size(); - - //Build dependencies - QVector parents; - QVector sortedNodes; - parents.fill(-1, numNodes); - sortedNodes.reserve(numNodes); - int nodecount = 0; - foreach(auto &node, _file.nodes) { - foreach(int child, node.children) { - parents[child] = nodecount; - } - sortedNodes.push_back(nodecount); - ++nodecount; - } - - // Build transforms - nodecount = 0; - foreach(auto &node, _file.nodes) { - // collect node transform - _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); - int parentIndex = parents[nodecount]; - while (parentIndex != -1) { - const auto& parentNode = _file.nodes[parentIndex]; - // collect transforms for a node's parents, grandparents, etc. - _file.nodes[nodecount].transforms.push_back(getModelTransform(parentNode)); - parentIndex = parents[parentIndex]; - } - ++nodecount; - } - - - // since parent indices must exist in the sorted list before any of their children, sortedNodes might not be initialized in the correct order - // therefore we need to re-initialize the order in which nodes will be parsed - QVector hasBeenSorted; - hasBeenSorted.fill(false, numNodes); - int i = 0; // initial index - while (i < numNodes) { - int currentNode = sortedNodes[i]; - int parentIndex = parents[currentNode]; - if (parentIndex == -1 || hasBeenSorted[parentIndex]) { - hasBeenSorted[currentNode] = true; - ++i; - } else { - int j = i + 1; // index of node to be sorted - while (j < numNodes) { - int nextNode = sortedNodes[j]; - parentIndex = parents[nextNode]; - if (parentIndex == -1 || hasBeenSorted[parentIndex]) { - // swap with currentNode - hasBeenSorted[nextNode] = true; - sortedNodes[i] = nextNode; - sortedNodes[j] = currentNode; - ++i; - currentNode = sortedNodes[i]; - } - ++j; - } - } - } - - - // Build map from original to new indices - QVector originalToNewNodeIndexMap; - originalToNewNodeIndexMap.fill(-1, numNodes); - for (int i = 0; i < numNodes; ++i) { - originalToNewNodeIndexMap[sortedNodes[i]] = i; - } - - // Build joints - HFMJoint joint; - joint.distanceToParent = 0; hfmModel.jointIndices["x"] = numNodes; - QVector globalTransforms; - globalTransforms.resize(numNodes); - - for (int nodeIndex : sortedNodes) { + auto parentIndices = gltf::findParentIndices(_file.nodes); + const auto parentsEnd = parentIndices.end(); + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + HFMJoint joint; auto& node = _file.nodes[nodeIndex]; - - joint.parentIndex = parents[nodeIndex]; - if (joint.parentIndex != -1) { - joint.parentIndex = originalToNewNodeIndexMap[joint.parentIndex]; + auto parentItr = parentIndices.find(nodeIndex); + if (parentsEnd == parentItr) { + joint.parentIndex = -1; + } else { + joint.parentIndex = parentItr->second; } - joint.transform = node.transforms.first(); + + joint.transform = getModelTransform(node); joint.translation = extractTranslation(joint.transform); joint.rotation = glmExtractRotation(joint.transform); glm::vec3 scale = extractScale(joint.transform); joint.postTransform = glm::scale(glm::mat4(), scale); - joint.parentIndex = parents[nodeIndex]; - globalTransforms[nodeIndex] = joint.transform; - if (joint.parentIndex != -1) { - globalTransforms[nodeIndex] = globalTransforms[joint.parentIndex] * globalTransforms[nodeIndex]; - joint.parentIndex = originalToNewNodeIndexMap[joint.parentIndex]; + joint.globalTransform = joint.transform; + // Nodes are sorted, so we can apply the full transform just by getting the global transform of the already defined parent + if (joint.parentIndex != -1 && joint.parentIndex != nodeIndex) { + const auto& parentJoint = hfmModel.joints[(size_t)joint.parentIndex]; + joint.transform = parentJoint.transform * joint.transform; + joint.globalTransform = joint.globalTransform * parentJoint.globalTransform; + } else { + joint.globalTransform = hfmModel.offset * joint.globalTransform; } joint.name = node.name; joint.isSkeletonJoint = false; hfmModel.joints.push_back(joint); } - hfmModel.shapeVertices.resize(hfmModel.joints.size()); - // get offset transform from mapping float unitScaleFactor = 1.0f; @@ -945,6 +1036,9 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& std::vector globalBindTransforms; jointInverseBindTransforms.resize(numNodes); globalBindTransforms.resize(numNodes); + // Lookup between the GLTF mesh and the skin + std::vector gltfMeshToSkin; + gltfMeshToSkin.resize(_file.meshes.size(), -1); hfmModel.hasSkeletonJoints = !_file.skins.isEmpty(); if (hfmModel.hasSkeletonJoints) { @@ -952,8 +1046,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& getSkinInverseBindMatrices(inverseBindValues); for (int jointIndex = 0; jointIndex < numNodes; ++jointIndex) { - int nodeIndex = sortedNodes[jointIndex]; - auto joint = hfmModel.joints[jointIndex]; + int nodeIndex = jointIndex; + auto& joint = hfmModel.joints[jointIndex]; for (int s = 0; s < _file.skins.size(); ++s) { const auto& skin = _file.skins[s]; @@ -979,642 +1073,490 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * glm::inverse(jointInverseBindTransforms[jointIndex])); hfmModel.bindExtents.addPoint(bindTranslation); } - hfmModel.joints[jointIndex] = joint; - } - } - - - // Build materials - QVector materialIDs; - QString unknown = "Default"; - int ukcount = 0; - foreach(auto material, _file.materials) { - if (!material.defined["name"]) { - QString name = unknown + QString::number(++ukcount); - material.name = name; - material.defined.insert("name", true); } - QString mid = material.name; - materialIDs.push_back(mid); - } - - for (int i = 0; i < materialIDs.size(); ++i) { - QString& matid = materialIDs[i]; - hfmModel.materials[matid] = HFMMaterial(); - HFMMaterial& hfmMaterial = hfmModel.materials[matid]; - hfmMaterial._material = std::make_shared(); - hfmMaterial.name = hfmMaterial.materialID = matid; - setHFMMaterial(hfmMaterial, _file.materials[i]); - } - - - // Build meshes - nodecount = 0; - hfmModel.meshExtents.reset(); - for (int nodeIndex : sortedNodes) { - auto& node = _file.nodes[nodeIndex]; - - if (node.defined["mesh"]) { - - hfmModel.meshes.append(HFMMesh()); - HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; - if (!hfmModel.hasSkeletonJoints) { - HFMCluster cluster; - cluster.jointIndex = nodecount; - cluster.inverseBindMatrix = glm::mat4(); - cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); - mesh.clusters.append(cluster); - } else { // skinned model - for (int j = 0; j < numNodes; ++j) { - HFMCluster cluster; - cluster.jointIndex = j; - cluster.inverseBindMatrix = jointInverseBindTransforms[j]; - cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); - mesh.clusters.append(cluster); + std::vector skinToRootJoint; + skinToRootJoint.resize(_file.skins.size(), 0); + for (int jointIndex = 0; jointIndex < numNodes; ++jointIndex) { + const auto& node = _file.nodes[jointIndex]; + if (node.skin != -1) { + skinToRootJoint[node.skin] = jointIndex; + if (node.mesh != -1) { + gltfMeshToSkin[node.mesh] = node.skin; } } - HFMCluster root; - root.jointIndex = 0; + } + + for (int skinIndex = 0; skinIndex < _file.skins.size(); ++skinIndex) { + const auto& skin = _file.skins[skinIndex]; + hfmModel.skinDeformers.emplace_back(); + auto& skinDeformer = hfmModel.skinDeformers.back(); + + // Add the nodes being referred to for skinning + for (int skinJointIndex : skin.joints) { + hfm::Cluster cluster; + cluster.jointIndex = skinJointIndex; + cluster.inverseBindMatrix = jointInverseBindTransforms[skinJointIndex]; + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + skinDeformer.clusters.push_back(cluster); + } + + // Always append a cluster referring to the root joint at the end + int rootJointIndex = skinToRootJoint[skinIndex]; + hfm::Cluster root; + root.jointIndex = rootJointIndex; root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; root.inverseBindTransform = Transform(root.inverseBindMatrix); - mesh.clusters.append(root); + skinDeformer.clusters.push_back(root); + } + } - QList meshAttributes; - foreach(auto &primitive, _file.meshes[node.mesh].primitives) { - QList keys = primitive.attributes.values.keys(); - foreach (auto &key, keys) { - if (!meshAttributes.contains(key)) { - meshAttributes.push_back(key); + for (const auto& material : _file.materials) { + const QString& matid = material.name; + hfmModel.materials.emplace_back(); + HFMMaterial& hfmMaterial = hfmModel.materials.back(); + hfmMaterial._material = std::make_shared(); + hfmMaterial.materialID = matid; + setHFMMaterial(hfmMaterial, material); + } + + + int gltfMeshCount = _file.meshes.size(); + hfmModel.meshExtents.reset(); + std::vector> templateShapePerPrimPerGLTFMesh; + for (int gltfMeshIndex = 0; gltfMeshIndex < gltfMeshCount; ++gltfMeshIndex) { + const auto& gltfMesh = _file.meshes[gltfMeshIndex]; + hfmModel.meshes.emplace_back(); + // NOTE: The number of hfm meshes may be greater than the number of gltf meshes, if a gltf mesh has primitives with different vertex attributes. In that case, this mesh reference may be reassigned. + hfm::Mesh* meshPtr = &hfmModel.meshes.back(); + const size_t firstMeshIndexForGLTFMesh = hfmModel.meshes.size() - 1; + meshPtr->meshIndex = gltfMeshIndex; + templateShapePerPrimPerGLTFMesh.emplace_back(); + std::vector& templateShapePerPrim = templateShapePerPrimPerGLTFMesh.back(); + + QSet primitiveAttributes; + if (!gltfMesh.primitives.empty()) { + for (const auto& attribute : gltfMesh.primitives[0].attributes.values.keys()) { + primitiveAttributes.insert(attribute); + } + } + std::vector> primitiveAttributeVariants; + + int primCount = (int)gltfMesh.primitives.size(); + size_t hfmMeshIndex = firstMeshIndexForGLTFMesh; + for(int primIndex = 0; primIndex < primCount; ++primIndex) { + auto& primitive = gltfMesh.primitives[primIndex]; + + QList keys = primitive.attributes.values.keys(); + QSet newPrimitiveAttributes; + for (const auto& key : keys) { + newPrimitiveAttributes.insert(key); + } + if (newPrimitiveAttributes != primitiveAttributes) { + assert(primIndex != 0); + + // We need to use a different mesh because the vertex attributes are different + auto attributeVariantIt = std::find(primitiveAttributeVariants.cbegin(), primitiveAttributeVariants.cend(), newPrimitiveAttributes); + if (attributeVariantIt == primitiveAttributeVariants.cend()) { + // Need to allocate a new mesh + hfmModel.meshes.emplace_back(); + meshPtr = &hfmModel.meshes.back(); + hfmMeshIndex = hfmModel.meshes.size() - 1; + meshPtr->meshIndex = gltfMeshIndex; + primitiveAttributeVariants.push_back(newPrimitiveAttributes); + } else { + // An hfm mesh already exists for this gltf mesh with the same vertex attributes. Use it again. + auto variantIndex = (size_t)(attributeVariantIt - primitiveAttributeVariants.cbegin()); + hfmMeshIndex = firstMeshIndexForGLTFMesh + variantIndex; + meshPtr = &hfmModel.meshes[hfmMeshIndex]; + } + primitiveAttributes = newPrimitiveAttributes; + } + // Now, allocate the part for the correct mesh... + hfm::Mesh& mesh = *meshPtr; + mesh.parts.emplace_back(); + hfm::MeshPart& part = mesh.parts.back(); + // ...and keep track of the relationship between the gltf mesh/primitive and the hfm mesh/part + templateShapePerPrim.emplace_back(); + hfm::Shape& templateShape = templateShapePerPrim.back(); + templateShape.mesh = (uint32_t)hfmMeshIndex; + templateShape.meshPart = (uint32_t)(mesh.parts.size() - 1); + templateShape.material = primitive.material; + + int indicesAccessorIdx = primitive.indices; + + GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; + + // Buffers + constexpr int VERTEX_STRIDE = 3; + constexpr int NORMAL_STRIDE = 3; + constexpr int TEX_COORD_STRIDE = 2; + + QVector indices; + QVector vertices; + QVector normals; + QVector tangents; + QVector texcoords; + QVector texcoords2; + QVector colors; + QVector joints; + QVector weights; + + static int tangentStride = 4; + static int colorStride = 3; + static int jointStride = 4; + static int weightStride = 4; + + bool success = addArrayFromAccessor(indicesAccessor, indices); + + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; + continue; + } + + // Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh + int prevMeshVerticesCount = mesh.vertices.count(); + QVector clusterJoints; + QVector clusterWeights; + + for(auto &key : keys) { + int accessorIdx = primitive.attributes.values[key]; + GLTFAccessor& accessor = _file.accessors[accessorIdx]; + const auto vertexAttribute = GLTFVertexAttribute::fromString(key); + switch (vertexAttribute) { + case GLTFVertexAttribute::POSITION: + success = addArrayFromAttribute(vertexAttribute, accessor, vertices); + break; + + case GLTFVertexAttribute::NORMAL: + success = addArrayFromAttribute(vertexAttribute, accessor, normals); + break; + + case GLTFVertexAttribute::TANGENT: + success = addArrayFromAttribute(vertexAttribute, accessor, tangents); + tangentStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + + case GLTFVertexAttribute::TEXCOORD_0: + success = addArrayFromAttribute(vertexAttribute, accessor, texcoords); + break; + + case GLTFVertexAttribute::TEXCOORD_1: + success = addArrayFromAttribute(vertexAttribute, accessor, texcoords2); + break; + + case GLTFVertexAttribute::COLOR_0: + success = addArrayFromAttribute(vertexAttribute, accessor, colors); + colorStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + + case GLTFVertexAttribute::JOINTS_0: + success = addArrayFromAttribute(vertexAttribute, accessor, joints); + jointStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + + case GLTFVertexAttribute::WEIGHTS_0: + success = addArrayFromAttribute(vertexAttribute, accessor, weights); + weightStride = GLTFAccessorType::count((GLTFAccessorType::Value)accessor.type); + break; + + default: + success = false; + break; + } + if (!success) { + continue; + } + } + + // Validation stage + if (indices.count() == 0) { + qWarning(modelformat) << "Missing indices for model " << _url; + continue; + } + if (vertices.count() == 0) { + qWarning(modelformat) << "Missing vertices for model " << _url; + continue; + } + + int partVerticesCount = vertices.size() / 3; + + QVector validatedIndices; + for (int n = 0; n < indices.count(); ++n) { + if (indices[n] < partVerticesCount) { + validatedIndices.push_back(indices[n] + prevMeshVerticesCount); + } else { + validatedIndices = QVector(); + break; + } + } + + if (validatedIndices.size() == 0) { + qWarning(modelformat) << "Indices out of range for model " << _url; + continue; + } + + part.triangleIndices.append(validatedIndices); + + mesh.vertices.reserve(mesh.vertices.size() + partVerticesCount); + for (int n = 0; n < vertices.size(); n = n + VERTEX_STRIDE) { + mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); + } + + mesh.normals.reserve(mesh.normals.size() + partVerticesCount); + for (int n = 0; n < normals.size(); n = n + NORMAL_STRIDE) { + mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); + } + + if (tangents.size() == partVerticesCount * tangentStride) { + mesh.tangents.reserve(mesh.tangents.size() + partVerticesCount); + for (int n = 0; n < tangents.size(); n += tangentStride) { + float tanW = tangentStride == 4 ? tangents[n + 3] : 1; + mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); + } + } + + if (texcoords.size() == partVerticesCount * TEX_COORD_STRIDE) { + mesh.texCoords.reserve(mesh.texCoords.size() + partVerticesCount); + for (int n = 0; n < texcoords.size(); n = n + 2) { + mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + } + } else if (primitiveAttributes.contains("TEXCOORD_0")) { + mesh.texCoords.resize(mesh.texCoords.size() + partVerticesCount); + } + + if (texcoords2.size() == partVerticesCount * TEX_COORD_STRIDE) { + mesh.texCoords1.reserve(mesh.texCoords1.size() + partVerticesCount); + for (int n = 0; n < texcoords2.size(); n = n + 2) { + mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1])); + } + } else if (primitiveAttributes.contains("TEXCOORD_1")) { + mesh.texCoords1.resize(mesh.texCoords1.size() + partVerticesCount); + } + + if (colors.size() == partVerticesCount * colorStride) { + mesh.colors.reserve(mesh.colors.size() + partVerticesCount); + for (int n = 0; n < colors.size(); n += colorStride) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); + } + } else if (primitiveAttributes.contains("COLOR_0")) { + mesh.colors.reserve(mesh.colors.size() + partVerticesCount); + for (int i = 0; i < partVerticesCount; ++i) { + mesh.colors.push_back(glm::vec3(1.0f)); + } + } + + const int WEIGHTS_PER_VERTEX = 4; + + if (weights.size() == partVerticesCount * weightStride) { + for (int n = 0; n < weights.size(); n += weightStride) { + clusterWeights.push_back(weights[n]); + if (weightStride > 1) { + clusterWeights.push_back(weights[n + 1]); + if (weightStride > 2) { + clusterWeights.push_back(weights[n + 2]); + if (weightStride > 3) { + clusterWeights.push_back(weights[n + 3]); + } else { + clusterWeights.push_back(0.0f); + } + } else { + clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + } + } else { + clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + clusterWeights.push_back(0.0f); + } + } + } else if (primitiveAttributes.contains("WEIGHTS_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + clusterWeights.push_back(1.0f); + for (int j = 0; j < WEIGHTS_PER_VERTEX; ++j) { + clusterWeights.push_back(0.0f); } } } - foreach(auto &primitive, _file.meshes[node.mesh].primitives) { - HFMMeshPart part = HFMMeshPart(); - - int indicesAccessorIdx = primitive.indices; - - GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; - - // Buffers - QVector indices; - QVector vertices; - int verticesStride = 3; - QVector normals; - int normalStride = 3; - QVector tangents; - int tangentStride = 4; - QVector texcoords; - int texCoordStride = 2; - QVector texcoords2; - int texCoord2Stride = 2; - QVector colors; - int colorStride = 3; - QVector joints; - int jointStride = 4; - QVector weights; - int weightStride = 4; - - bool success = addArrayFromAccessor(indicesAccessor, indices); - - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; - continue; + // Compress floating point weights to uint16_t for graphics runtime + // TODO: If the GLTF skinning weights are already in integer format, we should just copy the data + if (!clusterWeights.empty()) { + size_t numWeights = 4 * (mesh.vertices.size() - (uint32_t)prevMeshVerticesCount); + size_t newWeightsStart = mesh.clusterWeights.size(); + size_t newWeightsEnd = newWeightsStart + numWeights; + mesh.clusterWeights.reserve(newWeightsEnd); + for (int weightIndex = 0; weightIndex < clusterWeights.size(); ++weightIndex) { + // Per the GLTF specification + uint16_t weight = std::round(clusterWeights[weightIndex] * 65535.0f); + mesh.clusterWeights.push_back(weight); } + mesh.clusterWeightsPerVertex = WEIGHTS_PER_VERTEX; + } - // Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh - int prevMeshVerticesCount = mesh.vertices.count(); - - QList keys = primitive.attributes.values.keys(); - QVector clusterJoints; - QVector clusterWeights; - - foreach(auto &key, keys) { - int accessorIdx = primitive.attributes.values[key]; - - GLTFAccessor& accessor = _file.accessors[accessorIdx]; - - if (key == "POSITION") { - if (accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, vertices); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; - continue; - } - } else if (key == "NORMAL") { - if (accessor.type != GLTFAccessorType::VEC3) { - qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, normals); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; - continue; - } - } else if (key == "TANGENT") { - if (accessor.type == GLTFAccessorType::VEC4) { - tangentStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - tangentStride = 3; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, tangents); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url; - tangentStride = 0; - continue; - } - } else if (key == "TEXCOORD_0") { - success = addArrayFromAccessor(accessor, texcoords); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; - continue; - } - - if (accessor.type != GLTFAccessorType::VEC2) { - qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url; - continue; - } - } else if (key == "TEXCOORD_1") { - success = addArrayFromAccessor(accessor, texcoords2); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; - continue; - } - - if (accessor.type != GLTFAccessorType::VEC2) { - qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url; - continue; - } - } else if (key == "COLOR_0") { - if (accessor.type == GLTFAccessorType::VEC4) { - colorStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - colorStride = 3; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, colors); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; - continue; - } - } else if (key == "JOINTS_0") { - if (accessor.type == GLTFAccessorType::VEC4) { - jointStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - jointStride = 3; - } else if (accessor.type == GLTFAccessorType::VEC2) { - jointStride = 2; - } else if (accessor.type == GLTFAccessorType::SCALAR) { - jointStride = 1; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, joints); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; - continue; - } - } else if (key == "WEIGHTS_0") { - if (accessor.type == GLTFAccessorType::VEC4) { - weightStride = 4; - } else if (accessor.type == GLTFAccessorType::VEC3) { - weightStride = 3; - } else if (accessor.type == GLTFAccessorType::VEC2) { - weightStride = 2; - } else if (accessor.type == GLTFAccessorType::SCALAR) { - weightStride = 1; - } else { - qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url; - continue; - } - - success = addArrayFromAccessor(accessor, weights); - if (!success) { - qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; - continue; - } - } - } - - // Validation stage - if (indices.count() == 0) { - qWarning(modelformat) << "Missing indices for model " << _url; - continue; - } - if (vertices.count() == 0) { - qWarning(modelformat) << "Missing vertices for model " << _url; - continue; - } - - int partVerticesCount = vertices.size() / 3; - - // generate the normals if they don't exist - if (normals.size() == 0) { - QVector newIndices; - QVector newVertices; - QVector newNormals; - QVector newTexcoords; - QVector newTexcoords2; - QVector newColors; - QVector newJoints; - QVector newWeights; - - for (int n = 0; n < indices.size(); n = n + 3) { - int v1_index = (indices[n + 0] * 3); - int v2_index = (indices[n + 1] * 3); - int v3_index = (indices[n + 2] * 3); - - glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]); - glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]); - glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]); - - newVertices.append(v1.x); - newVertices.append(v1.y); - newVertices.append(v1.z); - newVertices.append(v2.x); - newVertices.append(v2.y); - newVertices.append(v2.z); - newVertices.append(v3.x); - newVertices.append(v3.y); - newVertices.append(v3.z); - - glm::vec3 norm = glm::normalize(glm::cross(v2 - v1, v3 - v1)); - - newNormals.append(norm.x); - newNormals.append(norm.y); - newNormals.append(norm.z); - newNormals.append(norm.x); - newNormals.append(norm.y); - newNormals.append(norm.z); - newNormals.append(norm.x); - newNormals.append(norm.y); - newNormals.append(norm.z); - - if (texcoords.size() == partVerticesCount * texCoordStride) { - GLTF_APPEND_ARRAY_2(newTexcoords, texcoords) - } - - if (texcoords2.size() == partVerticesCount * texCoord2Stride) { - GLTF_APPEND_ARRAY_2(newTexcoords2, texcoords2) - } - - if (colors.size() == partVerticesCount * colorStride) { - if (colorStride == 4) { - GLTF_APPEND_ARRAY_4(newColors, colors) + if (joints.size() == partVerticesCount * jointStride) { + for (int n = 0; n < joints.size(); n += jointStride) { + mesh.clusterIndices.push_back(joints[n]); + if (jointStride > 1) { + mesh.clusterIndices.push_back(joints[n + 1]); + if (jointStride > 2) { + mesh.clusterIndices.push_back(joints[n + 2]); + if (jointStride > 3) { + mesh.clusterIndices.push_back(joints[n + 3]); } else { - GLTF_APPEND_ARRAY_3(newColors, colors) + mesh.clusterIndices.push_back(0); } + } else { + mesh.clusterIndices.push_back(0); + mesh.clusterIndices.push_back(0); } - - if (joints.size() == partVerticesCount * jointStride) { - if (jointStride == 4) { - GLTF_APPEND_ARRAY_4(newJoints, joints) - } else if (jointStride == 3) { - GLTF_APPEND_ARRAY_3(newJoints, joints) - } else if (jointStride == 2) { - GLTF_APPEND_ARRAY_2(newJoints, joints) - } else { - GLTF_APPEND_ARRAY_1(newJoints, joints) - } - } - - if (weights.size() == partVerticesCount * weightStride) { - if (weightStride == 4) { - GLTF_APPEND_ARRAY_4(newWeights, weights) - } else if (weightStride == 3) { - GLTF_APPEND_ARRAY_3(newWeights, weights) - } else if (weightStride == 2) { - GLTF_APPEND_ARRAY_2(newWeights, weights) - } else { - GLTF_APPEND_ARRAY_1(newWeights, weights) - } - } - newIndices.append(n); - newIndices.append(n + 1); - newIndices.append(n + 2); - } - - vertices = newVertices; - normals = newNormals; - tangents = QVector(); - texcoords = newTexcoords; - texcoords2 = newTexcoords2; - colors = newColors; - joints = newJoints; - weights = newWeights; - indices = newIndices; - - partVerticesCount = vertices.size() / 3; - } - - QVector validatedIndices; - for (int n = 0; n < indices.count(); ++n) { - if (indices[n] < partVerticesCount) { - validatedIndices.push_back(indices[n] + prevMeshVerticesCount); } else { - validatedIndices = QVector(); + mesh.clusterIndices.push_back(0); + mesh.clusterIndices.push_back(0); + mesh.clusterIndices.push_back(0); + } + } + } else if (primitiveAttributes.contains("JOINTS_0")) { + for (int i = 0; i < partVerticesCount; ++i) { + for (int j = 0; j < 4; ++j) { + mesh.clusterIndices.push_back(0); + } + } + } + + if (!mesh.clusterIndices.empty()) { + int skinIndex = gltfMeshToSkin[gltfMeshIndex]; + if (skinIndex != -1) { + const auto& deformer = hfmModel.skinDeformers[(size_t)skinIndex]; + std::vector oldToNew; + oldToNew.resize(_file.nodes.size()); + for (uint16_t clusterIndex = 0; clusterIndex < deformer.clusters.size() - 1; ++clusterIndex) { + const auto& cluster = deformer.clusters[clusterIndex]; + oldToNew[(size_t)cluster.jointIndex] = clusterIndex; + } + } + } + + // populate the texture coordinates if they don't exist + if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { + for (int i = 0; i < part.triangleIndices.size(); ++i) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } + } + + // Build morph targets (blend shapes) + if (!primitive.targets.isEmpty()) { + + // Build list of blendshapes from FST + typedef QPair WeightedIndex; + hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash(); + QMultiHash blendshapeIndices; + + for (int i = 0;; ++i) { + hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; + if (blendshapeName.isEmpty()) { break; } - } - - if (validatedIndices.size() == 0) { - qWarning(modelformat) << "Indices out of range for model " << _url; - continue; - } - - part.triangleIndices.append(validatedIndices); - - for (int n = 0; n < vertices.size(); n = n + verticesStride) { - mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); - } - - for (int n = 0; n < normals.size(); n = n + normalStride) { - mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); - } - - // TODO: add correct tangent generation - if (tangents.size() == partVerticesCount * tangentStride) { - for (int n = 0; n < tangents.size(); n += tangentStride) { - float tanW = tangentStride == 4 ? tangents[n + 3] : 1; - mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2])); + QList mappings = blendshapeMappings.values(blendshapeName); + foreach (const QVariant& mapping, mappings) { + QVariantList blendshapeMapping = mapping.toList(); + blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat())); } - } else { - if (meshAttributes.contains("TANGENT")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.tangents.push_back(glm::vec3(0.0f, 0.0f, 0.0f)); + } + + // glTF morph targets may or may not have names. if they are labeled, add them based on + // the corresponding names from the FST. otherwise, just add them in the order they are given + mesh.blendshapes.resize(blendshapeMappings.size()); + auto values = blendshapeIndices.values(); + auto keys = blendshapeIndices.keys(); + auto names = gltfMesh.extras.targetNames; + QVector weights = gltfMesh.weights; + + for (int weightedIndex = 0; weightedIndex < values.size(); ++weightedIndex) { + float weight = 0.1f; + int indexFromMapping = weightedIndex; + int targetIndex = weightedIndex; + hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex)); + + if (!names.isEmpty()) { + targetIndex = names.indexOf(keys[weightedIndex]); + indexFromMapping = values[weightedIndex].first; + weight = weight * values[weightedIndex].second; + hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex]; + } + HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping]; + blendshape.indices = part.triangleIndices; + auto target = primitive.targets[targetIndex]; + + QVector normals; + QVector vertices; + + if (weights.size() == primitive.targets.size()) { + int targetWeight = weights[targetIndex]; + if (targetWeight != 0) { + weight = weight * targetWeight; } } - } - if (texcoords.size() == partVerticesCount * texCoordStride) { - for (int n = 0; n < texcoords.size(); n = n + 2) { - mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + if (target.values.contains((QString) "NORMAL")) { + generateTargetData(target.values.value((QString) "NORMAL"), weight, normals); } - } else { - if (meshAttributes.contains("TEXCOORD_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.texCoords.push_back(glm::vec2(0.0f, 0.0f)); - } + if (target.values.contains((QString) "POSITION")) { + generateTargetData(target.values.value((QString) "POSITION"), weight, vertices); } - } - - if (texcoords2.size() == partVerticesCount * texCoord2Stride) { - for (int n = 0; n < texcoords2.size(); n = n + 2) { - mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1])); - } - } else { - if (meshAttributes.contains("TEXCOORD_1")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.texCoords1.push_back(glm::vec2(0.0f, 0.0f)); - } - } - } - - if (colors.size() == partVerticesCount * colorStride) { - for (int n = 0; n < colors.size(); n += colorStride) { - mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); - } - } else { - if (meshAttributes.contains("COLOR_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - mesh.colors.push_back(glm::vec3(1.0f, 1.0f, 1.0f)); - } - } - } - - if (joints.size() == partVerticesCount * jointStride) { - for (int n = 0; n < joints.size(); n += jointStride) { - clusterJoints.push_back(joints[n]); - if (jointStride > 1) { - clusterJoints.push_back(joints[n + 1]); - if (jointStride > 2) { - clusterJoints.push_back(joints[n + 2]); - if (jointStride > 3) { - clusterJoints.push_back(joints[n + 3]); - } else { - clusterJoints.push_back(0); - } - } else { - clusterJoints.push_back(0); - clusterJoints.push_back(0); - } + bool isNewBlendshape = blendshape.vertices.size() < vertices.size(); + int count = 0; + for (int i : blendshape.indices) { + if (isNewBlendshape) { + blendshape.vertices.push_back(vertices[i]); + blendshape.normals.push_back(normals[i]); } else { - clusterJoints.push_back(0); - clusterJoints.push_back(0); - clusterJoints.push_back(0); - } - } - } else { - if (meshAttributes.contains("JOINTS_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - for (int j = 0; j < 4; ++j) { - clusterJoints.push_back(0); - } + blendshape.vertices[count] = blendshape.vertices[count] + vertices[i]; + blendshape.normals[count] = blendshape.normals[count] + normals[i]; + ++count; } } } - - if (weights.size() == partVerticesCount * weightStride) { - for (int n = 0; n < weights.size(); n += weightStride) { - clusterWeights.push_back(weights[n]); - if (weightStride > 1) { - clusterWeights.push_back(weights[n + 1]); - if (weightStride > 2) { - clusterWeights.push_back(weights[n + 2]); - if (weightStride > 3) { - clusterWeights.push_back(weights[n + 3]); - } else { - clusterWeights.push_back(0.0f); - } - } else { - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - } - } else { - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - clusterWeights.push_back(0.0f); - } - } - } else { - if (meshAttributes.contains("WEIGHTS_0")) { - for (int i = 0; i < partVerticesCount; ++i) { - clusterWeights.push_back(1.0f); - for (int j = 1; j < 4; ++j) { - clusterWeights.push_back(0.0f); - } - } - } - } - - // Build weights (adapted from FBXSerializer.cpp) - if (hfmModel.hasSkeletonJoints) { - int prevMeshClusterIndexCount = mesh.clusterIndices.count(); - int prevMeshClusterWeightCount = mesh.clusterWeights.count(); - const int WEIGHTS_PER_VERTEX = 4; - const float ALMOST_HALF = 0.499f; - int numVertices = mesh.vertices.size() - prevMeshVerticesCount; - - // Append new cluster indices and weights for this mesh part - for (int i = 0; i < numVertices * WEIGHTS_PER_VERTEX; ++i) { - mesh.clusterIndices.push_back(mesh.clusters.size() - 1); - mesh.clusterWeights.push_back(0); - } - - for (int c = 0; c < clusterJoints.size(); ++c) { - mesh.clusterIndices[prevMeshClusterIndexCount + c] = - originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]]; - } - - // normalize and compress to 16-bits - for (int i = 0; i < numVertices; ++i) { - int j = i * WEIGHTS_PER_VERTEX; - - float totalWeight = 0.0f; - for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - totalWeight += clusterWeights[k]; - } - if (totalWeight > 0.0f) { - float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; - for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - mesh.clusterWeights[prevMeshClusterWeightCount + k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); - } - } else { - mesh.clusterWeights[prevMeshClusterWeightCount + j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); - } - for (int clusterIndex = 0; clusterIndex < mesh.clusters.size() - 1; ++clusterIndex) { - ShapeVertices& points = hfmModel.shapeVertices.at(clusterIndex); - glm::vec3 globalMeshScale = extractScale(globalTransforms[nodeIndex]); - const glm::mat4 meshToJoint = glm::scale(glm::mat4(), globalMeshScale) * jointInverseBindTransforms[clusterIndex]; - - const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; - if (mesh.clusterWeights[j] >= EXPANSION_WEIGHT_THRESHOLD) { - // TODO: fix transformed vertices being pushed back - auto& vertex = mesh.vertices[i]; - const glm::mat4 vertexTransform = meshToJoint * (glm::translate(glm::mat4(), vertex)); - glm::vec3 transformedVertex = hfmModel.joints[clusterIndex].translation * (extractTranslation(vertexTransform)); - points.push_back(transformedVertex); - } - } - } - } - - if (primitive.defined["material"]) { - part.materialID = materialIDs[primitive.material]; - } - mesh.parts.push_back(part); - - // populate the texture coordinates if they don't exist - if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { - for (int i = 0; i < part.triangleIndices.size(); ++i) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } - } - - // Build morph targets (blend shapes) - if (!primitive.targets.isEmpty()) { - - // Build list of blendshapes from FST - typedef QPair WeightedIndex; - hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash(); - QMultiHash blendshapeIndices; - - for (int i = 0;; ++i) { - hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; - if (blendshapeName.isEmpty()) { - break; - } - QList mappings = blendshapeMappings.values(blendshapeName); - foreach (const QVariant& mapping, mappings) { - QVariantList blendshapeMapping = mapping.toList(); - blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat())); - } - } - - // glTF morph targets may or may not have names. if they are labeled, add them based on - // the corresponding names from the FST. otherwise, just add them in the order they are given - mesh.blendshapes.resize(blendshapeMappings.size()); - auto values = blendshapeIndices.values(); - auto keys = blendshapeIndices.keys(); - auto names = _file.meshes[node.mesh].extras.targetNames; - QVector weights = _file.meshes[node.mesh].weights; - - for (int weightedIndex = 0; weightedIndex < values.size(); ++weightedIndex) { - float weight = 0.1f; - int indexFromMapping = weightedIndex; - int targetIndex = weightedIndex; - hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex)); - - if (!names.isEmpty()) { - targetIndex = names.indexOf(keys[weightedIndex]); - indexFromMapping = values[weightedIndex].first; - weight = weight * values[weightedIndex].second; - hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex]; - } - HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping]; - blendshape.indices = part.triangleIndices; - auto target = primitive.targets[targetIndex]; - - QVector normals; - QVector vertices; - - if (weights.size() == primitive.targets.size()) { - int targetWeight = weights[targetIndex]; - if (targetWeight != 0) { - weight = weight * targetWeight; - } - } - - if (target.values.contains((QString) "NORMAL")) { - generateTargetData(target.values.value((QString) "NORMAL"), weight, normals); - } - if (target.values.contains((QString) "POSITION")) { - generateTargetData(target.values.value((QString) "POSITION"), weight, vertices); - } - bool isNewBlendshape = blendshape.vertices.size() < vertices.size(); - int count = 0; - for (int i : blendshape.indices) { - if (isNewBlendshape) { - blendshape.vertices.push_back(vertices[i]); - blendshape.normals.push_back(normals[i]); - } else { - blendshape.vertices[count] = blendshape.vertices[count] + vertices[i]; - blendshape.normals[count] = blendshape.normals[count] + normals[i]; - ++count; - } - } - } - } - - foreach(const glm::vec3& vertex, mesh.vertices) { - glm::vec3 transformedVertex = glm::vec3(globalTransforms[nodeIndex] * glm::vec4(vertex, 1.0f)); - mesh.meshExtents.addPoint(transformedVertex); - hfmModel.meshExtents.addPoint(transformedVertex); - } - } - - // Add epsilon to mesh extents to compensate for planar meshes - mesh.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); - mesh.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); - hfmModel.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); - hfmModel.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); - - mesh.meshIndex = hfmModel.meshes.size(); + } } - ++nodecount; + } + + + // Create the instance shapes for each transform node + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + const auto& node = _file.nodes[nodeIndex]; + if (-1 == node.mesh) { + continue; + } + + const auto& gltfMesh = _file.meshes[node.mesh]; + const auto& templateShapePerPrim = templateShapePerPrimPerGLTFMesh[node.mesh]; + int primCount = (int)gltfMesh.primitives.size(); + for (int primIndex = 0; primIndex < primCount; ++primIndex) { + const auto& templateShape = templateShapePerPrim[primIndex]; + hfmModel.shapes.push_back(templateShape); + auto& hfmShape = hfmModel.shapes.back(); + // Everything else is already defined (mesh, meshPart, material), so just define the new transform and deformer if present + hfmShape.joint = nodeIndex; + hfmShape.skinDeformer = node.skin != -1 ? node.skin : hfm::UNDEFINED_KEY; + } + } + + // TODO: Fix skinning and remove this workaround which disables skinning + // TODO: Restore after testing + { + std::vector meshToRootJoint; + meshToRootJoint.resize(hfmModel.meshes.size(), -1); + std::vector meshToClusterSize; + meshToClusterSize.resize(hfmModel.meshes.size()); + for (auto& shape : hfmModel.shapes) { + shape.skinDeformer = hfm::UNDEFINED_KEY; + } + + for (auto& mesh : hfmModel.meshes) { + mesh.clusterWeights.clear(); + mesh.clusterIndices.clear(); + mesh.clusterWeightsPerVertex = 0; + } + } return true; @@ -1636,9 +1578,8 @@ std::unique_ptr GLTFSerializer::getFactory() const { } HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) { - _url = url; - + // Normalize url for local files hifi::URL normalizeUrl = DependencyManager::get()->normalizeURL(_url); if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) { @@ -1648,6 +1589,9 @@ HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi:: if (parseGLTF(data)) { //_file.dump(); + _file.sortNodes(); + _file.populateMaterialNames(); + _file.normalizeNodeTransforms(); auto hfmModelPtr = std::make_shared(); HFMModel& hfmModel = *hfmModelPtr; buildGeometry(hfmModel, mapping, _url); @@ -1671,7 +1615,7 @@ bool GLTFSerializer::readBinary(const QString& url, hifi::ByteArray& outdata) { hifi::URL binaryUrl = _url.resolved(url); std::tie(success, outdata) = requestData(binaryUrl); } - + return success; } @@ -1684,8 +1628,8 @@ bool GLTFSerializer::doesResourceExist(const QString& url) { } std::tuple GLTFSerializer::requestData(hifi::URL& url) { - auto request = DependencyManager::get()->createResourceRequest( - nullptr, url, true, -1, "GLTFSerializer::requestData"); + auto request = + DependencyManager::get()->createResourceRequest(nullptr, url, true, -1, "GLTFSerializer::requestData"); if (!request) { return std::make_tuple(false, hifi::ByteArray()); @@ -1704,19 +1648,16 @@ std::tuple GLTFSerializer::requestData(hifi::URL& url) { } hifi::ByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { - QString binaryUrl = url.split(",")[1]; + QString binaryUrl = url.split(",")[1]; return binaryUrl.isEmpty() ? hifi::ByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8()); } - QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) { if (!qApp) { return nullptr; } bool aboutToQuit{ false }; - auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { - aboutToQuit = true; - }); + auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { aboutToQuit = true; }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -1725,18 +1666,18 @@ QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) { netReply->deleteLater(); return nullptr; } - QEventLoop loop; // Create an event loop that will quit when we get the finished signal + QEventLoop loop; // Create an event loop that will quit when we get the finished signal QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); - loop.exec(); // Nothing is going to happen on this whole run thread until we get this + loop.exec(); // Nothing is going to happen on this whole run thread until we get this QObject::disconnect(connection); - return netReply; // trying to sync later on. + return netReply; // trying to sync later on. } HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; @@ -1744,10 +1685,10 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { hifi::URL textureUrl = _url.resolved(url); fbxtex.name = fname; fbxtex.filename = textureUrl.toEncoded(); - + if (_url.toString().endsWith("glb") && !_glbBinary.isEmpty()) { int bufferView = _file.images[texture.source].bufferView; - + GLTFBufferView& imagesBufferview = _file.bufferviews[bufferView]; int offset = imagesBufferview.byteOffset; int length = imagesBufferview.byteLength; @@ -1757,7 +1698,7 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { } if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { - fbxtex.content = requestEmbeddedData(url); + fbxtex.content = requestEmbeddedData(url); } } return fbxtex; @@ -1787,12 +1728,12 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const GLTFMaterial& mat hfmMat.emissiveTexture = getHFMTexture(_file.textures[material.emissiveTexture]); hfmMat.useEmissiveMap = true; } - + if (material.defined["normalTexture"]) { hfmMat.normalTexture = getHFMTexture(_file.textures[material.normalTexture]); hfmMat.useNormalMap = true; } - + if (material.defined["occlusionTexture"]) { hfmMat.occlusionTexture = getHFMTexture(_file.textures[material.occlusionTexture]); hfmMat.useOcclusionMap = true; @@ -1820,7 +1761,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const GLTFMaterial& mat if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { hfmMat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); } - if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && material.pbrMetallicRoughness.baseColorFactor.size() == 4) { glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], material.pbrMetallicRoughness.baseColorFactor[1], @@ -1830,13 +1771,10 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const GLTFMaterial& mat hfmMat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); } } - } -template -bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count, - QVector& outarray, int accessorType) { - +template +bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { QDataStream blobstream(bin); blobstream.setByteOrder(QDataStream::LittleEndian); blobstream.setVersion(QDataStream::Qt_5_9); @@ -1845,31 +1783,31 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c int bufferCount = 0; switch (accessorType) { - case GLTFAccessorType::SCALAR: - bufferCount = 1; - break; - case GLTFAccessorType::VEC2: - bufferCount = 2; - break; - case GLTFAccessorType::VEC3: - bufferCount = 3; - break; - case GLTFAccessorType::VEC4: - bufferCount = 4; - break; - case GLTFAccessorType::MAT2: - bufferCount = 4; - break; - case GLTFAccessorType::MAT3: - bufferCount = 9; - break; - case GLTFAccessorType::MAT4: - bufferCount = 16; - break; - default: - qWarning(modelformat) << "Unknown accessorType: " << accessorType; - blobstream.setDevice(nullptr); - return false; + case GLTFAccessorType::SCALAR: + bufferCount = 1; + break; + case GLTFAccessorType::VEC2: + bufferCount = 2; + break; + case GLTFAccessorType::VEC3: + bufferCount = 3; + break; + case GLTFAccessorType::VEC4: + bufferCount = 4; + break; + case GLTFAccessorType::MAT2: + bufferCount = 4; + break; + case GLTFAccessorType::MAT3: + bufferCount = 9; + break; + case GLTFAccessorType::MAT4: + bufferCount = 16; + break; + default: + qWarning(modelformat) << "Unknown accessorType: " << accessorType; + blobstream.setDevice(nullptr); + return false; } for (int i = 0; i < count; ++i) { for (int j = 0; j < bufferCount; ++j) { @@ -1887,31 +1825,142 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c blobstream.setDevice(nullptr); return true; } -template -bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, int byteOffset, int count, - QVector& outarray, int accessorType, int componentType) { - +template +bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, + int byteOffset, + int count, + QVector& outarray, + int accessorType, + int componentType) { switch (componentType) { - case GLTFAccessorComponentType::BYTE: {} - case GLTFAccessorComponentType::UNSIGNED_BYTE: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::SHORT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::UNSIGNED_INT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::UNSIGNED_SHORT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } - case GLTFAccessorComponentType::FLOAT: { - return readArray(bin, byteOffset, count, outarray, accessorType); - } + case GLTFAccessorComponentType::BYTE: {} + case GLTFAccessorComponentType::UNSIGNED_BYTE: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::SHORT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::UNSIGNED_INT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::UNSIGNED_SHORT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } + case GLTFAccessorComponentType::FLOAT: { + return readArray(bin, byteOffset, count, outarray, accessorType); + } } return false; } + +template +bool GLTFSerializer::addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector& outarray) { + switch (vertexAttribute) { + case GLTFVertexAttribute::POSITION: + if (accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::NORMAL: + if (accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::TANGENT: + if (accessor.type != GLTFAccessorType::VEC4 && accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::TEXCOORD_0: + if (accessor.type != GLTFAccessorType::VEC2) { + qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::TEXCOORD_1: + if (accessor.type != GLTFAccessorType::VEC2) { + qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url; + return false; + } + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::COLOR_0: + if (accessor.type != GLTFAccessorType::VEC4 && accessor.type != GLTFAccessorType::VEC3) { + qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::JOINTS_0: + if (accessor.type < GLTFAccessorType::SCALAR || accessor.type > GLTFAccessorType::VEC4) { + qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; + return false; + } + break; + + case GLTFVertexAttribute::WEIGHTS_0: + if (accessor.type < GLTFAccessorType::SCALAR || accessor.type > GLTFAccessorType::VEC4) { + qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url; + return false; + } + + if (!addArrayFromAccessor(accessor, outarray)) { + qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; + return false; + } + break; + + default: + qWarning(modelformat) << "Unexpected attribute type" << _url; + return false; + } + + + return true; +} + template bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& outarray) { bool success = true; @@ -1957,7 +2006,7 @@ bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& ou if (success) { for (int i = 0; i < accessor.sparse.count; ++i) { - if ((i * 3) + 2 < out_sparse_values_array.size()) { + if ((i * 3) + 2 < out_sparse_values_array.size()) { if ((out_sparse_indices_array[i] * 3) + 2 < outarray.length()) { for (int j = 0; j < 3; ++j) { outarray[(out_sparse_indices_array[i] * 3) + j] = out_sparse_values_array[(i * 3) + j]; @@ -1979,14 +2028,16 @@ bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector& ou return success; } -void GLTFSerializer::retriangulate(const QVector& inIndices, const QVector& in_vertices, - const QVector& in_normals, QVector& outIndices, - QVector& out_vertices, QVector& out_normals) { +void GLTFSerializer::retriangulate(const QVector& inIndices, + const QVector& in_vertices, + const QVector& in_normals, + QVector& outIndices, + QVector& out_vertices, + QVector& out_normals) { for (int i = 0; i < inIndices.size(); i = i + 3) { - int idx1 = inIndices[i]; - int idx2 = inIndices[i+1]; - int idx3 = inIndices[i+2]; + int idx2 = inIndices[i + 1]; + int idx3 = inIndices[i + 2]; out_vertices.push_back(in_vertices[idx1]); out_vertices.push_back(in_vertices[idx2]); @@ -1997,8 +2048,8 @@ void GLTFSerializer::retriangulate(const QVector& inIndices, const QVector< out_normals.push_back(in_normals[idx3]); outIndices.push_back(i); - outIndices.push_back(i+1); - outIndices.push_back(i+2); + outIndices.push_back(i + 1); + outIndices.push_back(i + 2); } } @@ -2007,7 +2058,7 @@ void GLTFSerializer::glTFDebugDump() { for (GLTFNode node : _file.nodes) { if (node.defined["mesh"]) { qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " node_transforms" << node.transforms; + qCDebug(modelformat) << " node_transform" << node.transform; qCDebug(modelformat) << "\n"; } } @@ -2035,144 +2086,7 @@ void GLTFSerializer::glTFDebugDump() { } void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { - qCDebug(modelformat) << "---------------- hfmModel ----------------"; - qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; - qCDebug(modelformat) << " offset =" << hfmModel.offset; - - qCDebug(modelformat) << " neckPivot = " << hfmModel.neckPivot; - - qCDebug(modelformat) << " bindExtents.size() = " << hfmModel.bindExtents.size(); - qCDebug(modelformat) << " meshExtents.size() = " << hfmModel.meshExtents.size(); - - qCDebug(modelformat) << " jointIndices.size() =" << hfmModel.jointIndices.size(); - qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); - qCDebug(modelformat) << "---------------- Meshes ----------------"; - qCDebug(modelformat) << " meshes.count() =" << hfmModel.meshes.count(); - qCDebug(modelformat) << " blendshapeChannelNames = " << hfmModel.blendshapeChannelNames; - foreach(HFMMesh mesh, hfmModel.meshes) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " meshpointer =" << mesh._mesh.get(); - qCDebug(modelformat) << " meshindex =" << mesh.meshIndex; - qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.size(); - qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); - qCDebug(modelformat) << " normals.count() =" << mesh.normals.size(); - qCDebug(modelformat) << " tangents.count() =" << mesh.tangents.size(); - qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); - qCDebug(modelformat) << " texCoords.count() =" << mesh.texCoords.count(); - qCDebug(modelformat) << " texCoords1.count() =" << mesh.texCoords1.count(); - qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); - qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); - qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; - qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); - qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; - foreach(HFMBlendshape bshape, mesh.blendshapes) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " bshape.indices.count() =" << bshape.indices.count(); - qCDebug(modelformat) << " bshape.vertices.count() =" << bshape.vertices.count(); - qCDebug(modelformat) << " bshape.normals.count() =" << bshape.normals.count(); - qCDebug(modelformat) << "\n"; - } - qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; - foreach(HFMMeshPart meshPart, mesh.parts) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); - qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); - qCDebug(modelformat) << " materialID =" << meshPart.materialID; - qCDebug(modelformat) << "\n"; - - } - qCDebug(modelformat) << "---------------- Meshes (clusters)--------"; - qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); - foreach(HFMCluster cluster, mesh.clusters) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; - qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; - qCDebug(modelformat) << "\n"; - } - qCDebug(modelformat) << "\n"; - } - qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; - foreach(HFMAnimationFrame anim, hfmModel.animationFrames) { - qCDebug(modelformat) << " anim.translations = " << anim.translations; - qCDebug(modelformat) << " anim.rotations = " << anim.rotations; - } - QList mitomona_keys = hfmModel.meshIndicesToModelNames.keys(); - foreach(int key, mitomona_keys) { - qCDebug(modelformat) << " meshIndicesToModelNames key =" << key << " val =" << hfmModel.meshIndicesToModelNames[key]; - } - - qCDebug(modelformat) << "---------------- Materials ----------------"; - - foreach(HFMMaterial mat, hfmModel.materials) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " mat.materialID =" << mat.materialID; - qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; - qCDebug(modelformat) << " diffuseFactor =" << mat.diffuseFactor; - qCDebug(modelformat) << " specularColor =" << mat.specularColor; - qCDebug(modelformat) << " specularFactor =" << mat.specularFactor; - qCDebug(modelformat) << " emissiveColor =" << mat.emissiveColor; - qCDebug(modelformat) << " emissiveFactor =" << mat.emissiveFactor; - qCDebug(modelformat) << " shininess =" << mat.shininess; - qCDebug(modelformat) << " opacity =" << mat.opacity; - qCDebug(modelformat) << " metallic =" << mat.metallic; - qCDebug(modelformat) << " roughness =" << mat.roughness; - qCDebug(modelformat) << " emissiveIntensity =" << mat.emissiveIntensity; - qCDebug(modelformat) << " ambientFactor =" << mat.ambientFactor; - - qCDebug(modelformat) << " materialID =" << mat.materialID; - qCDebug(modelformat) << " name =" << mat.name; - qCDebug(modelformat) << " shadingModel =" << mat.shadingModel; - qCDebug(modelformat) << " _material =" << mat._material.get(); - - qCDebug(modelformat) << " normalTexture =" << mat.normalTexture.filename; - qCDebug(modelformat) << " albedoTexture =" << mat.albedoTexture.filename; - qCDebug(modelformat) << " opacityTexture =" << mat.opacityTexture.filename; - - qCDebug(modelformat) << " lightmapParams =" << mat.lightmapParams; - - qCDebug(modelformat) << " isPBSMaterial =" << mat.isPBSMaterial; - qCDebug(modelformat) << " useNormalMap =" << mat.useNormalMap; - qCDebug(modelformat) << " useAlbedoMap =" << mat.useAlbedoMap; - qCDebug(modelformat) << " useOpacityMap =" << mat.useOpacityMap; - qCDebug(modelformat) << " useRoughnessMap =" << mat.useRoughnessMap; - qCDebug(modelformat) << " useSpecularMap =" << mat.useSpecularMap; - qCDebug(modelformat) << " useMetallicMap =" << mat.useMetallicMap; - qCDebug(modelformat) << " useEmissiveMap =" << mat.useEmissiveMap; - qCDebug(modelformat) << " useOcclusionMap =" << mat.useOcclusionMap; - qCDebug(modelformat) << "\n"; - } - - qCDebug(modelformat) << "---------------- Joints ----------------"; - - foreach (HFMJoint joint, hfmModel.joints) { - qCDebug(modelformat) << "\n"; - qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint; - qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines; - qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; - qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; - - qCDebug(modelformat) << " parentIndex" << joint.parentIndex; - qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; - qCDebug(modelformat) << " translation" << joint.translation; - qCDebug(modelformat) << " preTransform" << joint.preTransform; - qCDebug(modelformat) << " preRotation" << joint.preRotation; - qCDebug(modelformat) << " rotation" << joint.rotation; - qCDebug(modelformat) << " postRotation" << joint.postRotation; - qCDebug(modelformat) << " postTransform" << joint.postTransform; - qCDebug(modelformat) << " transform" << joint.transform; - qCDebug(modelformat) << " rotationMin" << joint.rotationMin; - qCDebug(modelformat) << " rotationMax" << joint.rotationMax; - qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; - qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; - qCDebug(modelformat) << " bindTransform" << joint.bindTransform; - qCDebug(modelformat) << " name" << joint.name; - qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; - qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.hasGeometricOffset; - qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricTranslation; - qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricRotation; - qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricScaling; - qCDebug(modelformat) << "\n"; - } + hfmModel.debugDump(); qCDebug(modelformat) << "---------------- GLTF Model ----------------"; glTFDebugDump(); diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index b1020f7154..d59df615e5 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -38,15 +38,15 @@ struct GLTFAsset { struct GLTFNode { QString name; - int camera; - int mesh; + int camera{ -1 }; + int mesh{ -1 }; QVector children; QVector translation; QVector rotation; QVector scale; QVector matrix; - QVector transforms; - int skin; + glm::mat4 transform; + int skin { -1 }; QVector skeletons; QString jointName; QMap defined; @@ -85,6 +85,8 @@ struct GLTFNode { qCDebug(modelformat) << "skeletons: " << skeletons; } } + + void normalizeTransform(); }; // Meshes @@ -458,15 +460,56 @@ struct GLTFMaterial { // Accesors namespace GLTFAccessorType { - enum Values { - SCALAR = 0, - VEC2, - VEC3, - VEC4, - MAT2, - MAT3, - MAT4 + enum Value { + SCALAR = 1, + VEC2 = 2, + VEC3 = 3, + VEC4 = 4, + MAT2 = 5, + MAT3 = 9, + MAT4 = 16 }; + + inline int count(Value value) { + if (value == MAT2) { + return 4; + } + return (int)value; + } +} + +namespace GLTFVertexAttribute { + enum Value { + UNKNOWN = -1, + POSITION = 0, + NORMAL, + TANGENT, + TEXCOORD_0, + TEXCOORD_1, + COLOR_0, + JOINTS_0, + WEIGHTS_0, + }; + inline Value fromString(const QString& key) { + if (key == "POSITION") { + return POSITION; + } else if (key == "NORMAL") { + return NORMAL; + } else if (key == "TANGENT") { + return TANGENT; + } else if (key == "TEXCOORD_0") { + return TEXCOORD_0; + } else if (key == "TEXCOORD_1") { + return TEXCOORD_1; + } else if (key == "COLOR_0") { + return COLOR_0; + } else if (key == "JOINTS_0") { + return JOINTS_0; + } else if (key == "WEIGHTS_0") { + return WEIGHTS_0; + } + return UNKNOWN; + } } namespace GLTFAccessorComponentType { enum Values { @@ -758,6 +801,13 @@ struct GLTFFile { foreach(auto tex, textures) tex.dump(); } } + + + void populateMaterialNames(); + void sortNodes(); + void normalizeNodeTransforms(); +private: + void reorderNodes(const std::unordered_map& reorderMap); }; class GLTFSerializer : public QObject, public HFMSerializer { @@ -772,7 +822,7 @@ private: hifi::URL _url; hifi::ByteArray _glbBinary; - glm::mat4 getModelTransform(const GLTFNode& node); + const glm::mat4& getModelTransform(const GLTFNode& node); void getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); void generateTargetData(int index, float weight, QVector& returnVector); @@ -841,6 +891,9 @@ private: template bool addArrayFromAccessor(GLTFAccessor& accessor, QVector& outarray); + template + bool addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector& outarray); + void retriangulate(const QVector& in_indices, const QVector& in_vertices, const QVector& in_normals, QVector& out_indices, QVector& out_vertices, QVector& out_normals); diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index 416f343a47..4da7351b42 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -174,11 +174,6 @@ glm::vec2 OBJTokenizer::getVec2() { return v; } - -void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID) { - meshPart.materialID = materialID; -} - // OBJFace // NOTE (trent, 7/20/17): The vertexColors vector being passed-in isn't necessary here, but I'm just // pairing it with the vertices vector for consistency. @@ -492,8 +487,7 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHa float& scaleGuess, bool combineParts) { FaceGroup faces; HFMMesh& mesh = hfmModel.meshes[0]; - mesh.parts.append(HFMMeshPart()); - HFMMeshPart& meshPart = mesh.parts.last(); + mesh.parts.push_back(HFMMeshPart()); bool sawG = false; bool result = true; int originalFaceCountForDebugging = 0; @@ -501,8 +495,6 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHa bool anyVertexColor { false }; int vertexCount { 0 }; - setMeshPartDefaults(meshPart, QString("dontknow") + QString::number(mesh.parts.count())); - while (true) { int tokenType = tokenizer.nextToken(); if (tokenType == OBJTokenizer::COMMENT_TOKEN) { @@ -675,17 +667,19 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V _url = url; bool combineParts = mapping.value("combineParts").toBool(); - hfmModel.meshExtents.reset(); - hfmModel.meshes.append(HFMMesh()); + hfmModel.meshes.push_back(HFMMesh()); + std::vector materialNamePerShape; try { // call parseOBJGroup as long as it's returning true. Each successful call will // add a new meshPart to the model's single mesh. while (parseOBJGroup(tokenizer, mapping, hfmModel, scaleGuess, combineParts)) {} - HFMMesh& mesh = hfmModel.meshes[0]; - mesh.meshIndex = 0; + uint32_t meshIndex = 0; + HFMMesh& mesh = hfmModel.meshes[meshIndex]; + mesh.meshIndex = meshIndex; + uint32_t jointIndex = 0; hfmModel.joints.resize(1); hfmModel.joints[0].parentIndex = -1; hfmModel.joints[0].distanceToParent = 0; @@ -697,19 +691,11 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V hfmModel.jointIndices["x"] = 1; - HFMCluster cluster; - cluster.jointIndex = 0; - cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - mesh.clusters.append(cluster); - QMap materialMeshIdMap; - QVector hfmMeshParts; - for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { - HFMMeshPart& meshPart = mesh.parts[i]; - FaceGroup faceGroup = faceGroups[meshPartCount]; + std::vector hfmMeshParts; + for (uint32_t meshPartIndex = 0; meshPartIndex < (uint32_t)mesh.parts.size(); ++meshPartIndex) { + HFMMeshPart& meshPart = mesh.parts[meshPartIndex]; + FaceGroup faceGroup = faceGroups[meshPartIndex]; bool specifiesUV = false; foreach(OBJFace face, faceGroup) { // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh). @@ -718,12 +704,13 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V // Create a new HFMMesh for this material mapping. materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count()); - hfmMeshParts.append(HFMMeshPart()); - HFMMeshPart& meshPartNew = hfmMeshParts.last(); + uint32_t partIndex = (int)hfmMeshParts.size(); + hfmMeshParts.push_back(HFMMeshPart()); + HFMMeshPart& meshPartNew = hfmMeshParts.back(); meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices. - + // Do some of the material logic (which previously lived below) now. // All the faces in the same group will have the same name and material. QString groupMaterialName = face.materialName; @@ -745,19 +732,26 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; } materials[groupMaterialName] = material; - meshPartNew.materialID = groupMaterialName; } + materialNamePerShape.push_back(groupMaterialName); + + + hfm::Shape shape; + shape.mesh = meshIndex; + shape.joint = jointIndex; + shape.meshPart = partIndex; + hfmModel.shapes.push_back(shape); } } } // clean up old mesh parts. - int unmodifiedMeshPartCount = mesh.parts.count(); + auto unmodifiedMeshPartCount = (uint32_t)mesh.parts.size(); mesh.parts.clear(); - mesh.parts = QVector(hfmMeshParts); + mesh.parts = hfmMeshParts; - for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) { - FaceGroup faceGroup = faceGroups[meshPartCount]; + for (uint32_t meshPartIndex = 0; meshPartIndex < unmodifiedMeshPartCount; meshPartIndex++) { + FaceGroup faceGroup = faceGroups[meshPartIndex]; // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not). foreach(OBJFace face, faceGroup) { @@ -823,18 +817,13 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V } } } - - mesh.meshExtents.reset(); - foreach(const glm::vec3& vertex, mesh.vertices) { - mesh.meshExtents.addPoint(vertex); - hfmModel.meshExtents.addPoint(vertex); - } - - // hfmDebugDump(hfmModel); } catch(const std::exception& e) { qCDebug(modelformat) << "OBJSerializer fail: " << e.what(); } + // At this point, the hfmModel joint, mesh, parts and shpaes have been defined + // only no material assigned + QString queryPart = _url.query(); bool suppressMaterialsHack = queryPart.contains("hifiusemat"); // If this appears in query string, don't fetch mtl even if used. OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; @@ -886,17 +875,23 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V } } + // As we are populating the material list in the hfmModel, let s also create the reverse map (from materialName to index) + QMap materialNameToIndex; foreach (QString materialID, materials.keys()) { OBJMaterial& objMaterial = materials[materialID]; if (!objMaterial.used) { continue; } - HFMMaterial& hfmMaterial = hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, - objMaterial.specularColor, - objMaterial.emissiveColor, - objMaterial.shininess, - objMaterial.opacity); + // capture the name to index map + materialNameToIndex[materialID] = (uint32_t) hfmModel.materials.size(); + + hfmModel.materials.emplace_back(objMaterial.diffuseColor, + objMaterial.specularColor, + objMaterial.emissiveColor, + objMaterial.shininess, + objMaterial.opacity); + HFMMaterial& hfmMaterial = hfmModel.materials.back(); hfmMaterial.name = materialID; hfmMaterial.materialID = materialID; @@ -996,77 +991,16 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V modelMaterial->setOpacity(hfmMaterial.opacity); } + // GO over the shapes once more to assign the material index correctly + for (uint32_t i = 0; i < (uint32_t)hfmModel.shapes.size(); ++i) { + const auto& materialName = materialNamePerShape[i]; + if (!materialName.isEmpty()) { + auto foundMaterialIndex = materialNameToIndex.find(materialName); + if (foundMaterialIndex != materialNameToIndex.end()) { + hfmModel.shapes[i].material = foundMaterialIndex.value(); + } + } + } + return hfmModelPtr; } - -void hfmDebugDump(const HFMModel& hfmModel) { - qCDebug(modelformat) << "---------------- hfmModel ----------------"; - qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; - qCDebug(modelformat) << " offset =" << hfmModel.offset; - qCDebug(modelformat) << " meshes.count() =" << hfmModel.meshes.count(); - foreach (HFMMesh mesh, hfmModel.meshes) { - qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.count(); - qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); - qCDebug(modelformat) << " normals.count() =" << mesh.normals.count(); - /*if (mesh.normals.count() == mesh.vertices.count()) { - for (int i = 0; i < mesh.normals.count(); i++) { - qCDebug(modelformat) << " " << mesh.vertices[ i ] << mesh.normals[ i ]; - } - }*/ - qCDebug(modelformat) << " tangents.count() =" << mesh.tangents.count(); - qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); - qCDebug(modelformat) << " texCoords.count() =" << mesh.texCoords.count(); - qCDebug(modelformat) << " texCoords1.count() =" << mesh.texCoords1.count(); - qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); - qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); - qCDebug(modelformat) << " meshExtents =" << mesh.meshExtents; - qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; - qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); - foreach (HFMMeshPart meshPart, mesh.parts) { - qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); - qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); - /* - qCDebug(modelformat) << " diffuseColor =" << meshPart.diffuseColor << "mat =" << meshPart._material->getDiffuse(); - qCDebug(modelformat) << " specularColor =" << meshPart.specularColor << "mat =" << meshPart._material->getMetallic(); - qCDebug(modelformat) << " emissiveColor =" << meshPart.emissiveColor << "mat =" << meshPart._material->getEmissive(); - qCDebug(modelformat) << " emissiveParams =" << meshPart.emissiveParams; - qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getRoughness(); - qCDebug(modelformat) << " opacity =" << meshPart.opacity << "mat =" << meshPart._material->getOpacity(); - */ - qCDebug(modelformat) << " materialID =" << meshPart.materialID; - /* qCDebug(modelformat) << " diffuse texture =" << meshPart.diffuseTexture.filename; - qCDebug(modelformat) << " specular texture =" << meshPart.specularTexture.filename; - */ - } - qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); - foreach (HFMCluster cluster, mesh.clusters) { - qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; - qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; - } - } - - qCDebug(modelformat) << " jointIndices =" << hfmModel.jointIndices; - qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); - - foreach (HFMJoint joint, hfmModel.joints) { - - qCDebug(modelformat) << " parentIndex" << joint.parentIndex; - qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; - qCDebug(modelformat) << " translation" << joint.translation; - qCDebug(modelformat) << " preTransform" << joint.preTransform; - qCDebug(modelformat) << " preRotation" << joint.preRotation; - qCDebug(modelformat) << " rotation" << joint.rotation; - qCDebug(modelformat) << " postRotation" << joint.postRotation; - qCDebug(modelformat) << " postTransform" << joint.postTransform; - qCDebug(modelformat) << " transform" << joint.transform; - qCDebug(modelformat) << " rotationMin" << joint.rotationMin; - qCDebug(modelformat) << " rotationMax" << joint.rotationMax; - qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; - qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; - qCDebug(modelformat) << " bindTransform" << joint.bindTransform; - qCDebug(modelformat) << " name" << joint.name; - qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; - } - - qCDebug(modelformat) << "\n"; -} diff --git a/libraries/fbx/src/OBJSerializer.h b/libraries/fbx/src/OBJSerializer.h index 6fdd95e2c3..462d32a119 100644 --- a/libraries/fbx/src/OBJSerializer.h +++ b/libraries/fbx/src/OBJSerializer.h @@ -120,6 +120,5 @@ private: // What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility. void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID); -void hfmDebugDump(const HFMModel& hfmModel); #endif // hifi_OBJSerializer_h diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp index d636c6aaca..96f6b99f7a 100644 --- a/libraries/gpu/src/gpu/FrameReader.cpp +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -734,7 +734,7 @@ BatchPointer Deserializer::readBatch(const json& node) { std::string batchName; if (node.count(keys::name)) { - batchName = node[keys::name]; + batchName = node.value(keys::name, ""); } BatchPointer result = std::make_shared(batchName); auto& batch = *result; diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index acef5a5bd4..29f8079422 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -36,39 +36,6 @@ namespace scriptable { using ModelProviderPointer = std::shared_ptr; using WeakModelProviderPointer = std::weak_ptr; - /**jsdoc - * @typedef {object} Graphics.Material - * @property {string} name - * @property {string} model - * @property {number|string} opacity - * @property {number|string} roughness - * @property {number|string} metallic - * @property {number|string} scattering - * @property {boolean|string} unlit - * @propety {Vec3|string} emissive - * @propety {Vec3|string} albedo - * @property {string} emissiveMap - * @property {string} albedoMap - * @property {string} opacityMap - * @property {string} opacityMapMode - * @property {number|string} opacityCutoff - * @property {string} metallicMap - * @property {string} specularMap - * @property {string} roughnessMap - * @property {string} glossMap - * @property {string} normalMap - * @property {string} bumpMap - * @property {string} occlusionMap - * @property {string} lightMap - * @property {string} scatteringMap - * @property {Mat4|string} texCoordTransform0 - * @property {Mat4|string} texCoordTransform1 - * @property {string} lightmapParams - * @property {string} materialParams - * @property {string} cullFaceMode - * @property {boolean} defaultFallthrough - * @property {string} procedural - */ class ScriptableMaterial { public: ScriptableMaterial() {} @@ -110,9 +77,11 @@ namespace scriptable { }; /**jsdoc + * A material layer. * @typedef {object} Graphics.MaterialLayer - * @property {Graphics.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. + * @property {Graphics.Material} material - The layer's material. + * @property {number} priority - The priority of the layer. If multiple materials are applied to a mesh part, only the + * layer with the highest priority is applied, with materials of the same priority randomly assigned. */ class ScriptableMaterialLayer { public: @@ -138,8 +107,29 @@ namespace scriptable { ScriptableMeshBase(const ScriptableMeshBase& other, QObject* parent = nullptr) : QObject(parent) { *this = other; } ScriptableMeshBase& operator=(const ScriptableMeshBase& view); virtual ~ScriptableMeshBase(); + + /**jsdoc + * @function GraphicsMesh.getMeshPointer + * @deprecated This method is deprecated and will be removed. + * @returns {undefined} + */ + // scriptable::MeshPointer is not registered as a JavaScript type. Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return weakMesh.lock(); } + + /**jsdoc + * @function GraphicsMesh.getModelProviderPointer + * @deprecated This method is deprecated and will be removed. + * @returns {undefined} + */ + // scriptable::ModelProviderPointer is not registered as a JavaScript type. Q_INVOKABLE const scriptable::ModelProviderPointer getModelProviderPointer() const { return provider.lock(); } + + /**jsdoc + * @function GraphicsMesh.getModelBasePointer + * @deprecated This method is deprecated and will be removed. + * @returns {undefined} + */ + // scriptable::ScriptableModelBasePointer is not registered as a JavaScript type. Q_INVOKABLE const scriptable::ScriptableModelBasePointer getModelBasePointer() const { return model; } }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index c0a2d4bf25..a27eff9cbe 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -168,14 +168,15 @@ scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVar // in the future we want to support a formal C++ structure data type here instead /**jsdoc + * IFS (Indexed-Face Set) data defining a mesh. * @typedef {object} Graphics.IFSData - * @property {string} [name=""] - mesh name (useful for debugging / debug prints). - * @property {string} [topology=""] - * @property {number[]} indices - vertex indices to use for the mesh faces. - * @property {Vec3[]} vertices - vertex positions (model space) - * @property {Vec3[]} [normals=[]] - vertex normals (normalized) - * @property {Vec3[]} [colors=[]] - vertex colors (normalized) - * @property {Vec2[]} [texCoords0=[]] - vertex texture coordinates (normalized) + * @property {string} [name=""] - Mesh name. (Useful for debugging.) + * @property {Graphics.MeshTopology} topology - Element interpretation. Currently only triangles is supported. + * @property {number[]} indices - Vertex indices to use for the mesh faces, in tuples per the topology. + * @property {Vec3[]} positions - Vertex positions, in model coordinates. + * @property {Vec3[]} [normals=[]] - Vertex normals (normalized). + * @property {Vec3[]} [colors=[]] - Vertex colors (normalized). + * @property {Vec2[]} [texCoords0=[]] - Vertex texture coordinates (normalized). */ QString meshName = ifsMeshData.value("name").toString(); QString topologyName = ifsMeshData.value("topology").toString(); @@ -258,8 +259,8 @@ scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVar return scriptable::make_scriptowned(mesh, nullptr); } -QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::ScriptableModel& _in) { - const auto& in = _in.getConstMeshes(); +QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::ScriptableModelPointer& model) { + const auto& in = model->getConstMeshes(); if (in.size()) { QList meshes; foreach (auto meshProxy, in) { @@ -354,6 +355,120 @@ namespace scriptable { qScriptValueToSequence(array, result); } + /**jsdoc + * A material in a {@link GraphicsModel}. + * @typedef {object} Graphics.Material + * @property {string} name - The name of the material. + * @property {string} model - Different material models support different properties and rendering modes. Supported models + * are: "hifi_pbr" and "hifi_shader_simple". + * @property {Vec3|string} [albedo] - The albedo color. Component values are in the range 0.0 – + * 1.0. + * If "fallthrough" then it falls through to the material below. + * @property {number|string} [opacity] - The opacity, range 0.01.0. + * If "fallthrough" then it falls through to the material below. + * + * @property {number|string} [opacityCutoff] - The opacity cutoff threshold used to determine the opaque texels of the + * opacityMap when opacityMapMode is "OPACITY_MAP_MASK". Range 0.0 + * – 1.0. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {number|string} [roughness] - The roughness, range 0.01.0. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {number|string} [metallic] - The metallicness, range 0.01.0. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {number|string} [scattering] - The scattering, range 0.01.0. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {boolean|string} [unlit] - true if the material is unaffected by lighting, false if it + * it is lit by the key light and local lights. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {Vec3|string} [emissive] - The emissive color, i.e., the color that the material emits. Component values are + * in the range 0.01.0. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [albedoMap] - The URL of the albedo texture image. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [opacityMap] - The URL of the opacity texture image. + * "hifi_pbr" model only. + * @property {string} [opacityMapMode] - The mode defining the interpretation of the opacity map. Values can be: + *

    + *
  • "OPACITY_MAP_OPAQUE" for ignoring the opacity map information.
  • + *
  • "OPACITY_MAP_MASK" for using the opacityMap as a mask, where only the texel greater + * than opacityCutoff are visible and rendered opaque.
  • + *
  • "OPACITY_MAP_BLEND" for using the opacityMap for alpha blending the material surface + * with the background.
  • + *
+ * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [occlusionMap] - The URL of the occlusion texture image. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [lightMap] - The URL of the light map texture image. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [lightmapParams] - Parameters for controlling how lightMap is used. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + *

Currently not used.

+ * @property {string} [scatteringMap] - The URL of the scattering texture image. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [emissiveMap] - The URL of the emissive texture image. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {string} [metallicMap] - The URL of the metallic texture image. + * If "fallthrough" then it and specularMap fall through to the material below. + * Only use one of metallicMap and specularMap. + * "hifi_pbr" model only. + * @property {string} [specularMap] - The URL of the specular texture image. + * Only use one of metallicMap and specularMap. + * "hifi_pbr" model only. + * @property {string} [roughnessMap] - The URL of the roughness texture image. + * If "fallthrough" then it and glossMap fall through to the material below. + * Only use one of roughnessMap and glossMap. + * "hifi_pbr" model only. + * @property {string} [glossMap] - The URL of the gloss texture image. + * Only use one of roughnessMap and glossMap. + * "hifi_pbr" model only. + * @property {string} [normalMap] - The URL of the normal texture image. + * If "fallthrough" then it and bumpMap fall through to the material below. + * Only use one of normalMap and bumpMap. + * "hifi_pbr" model only. + * @property {string} [bumpMap] - The URL of the bump texture image. + * Only use one of normalMap and bumpMap. + * "hifi_pbr" model only. + * @property {string} [materialParams] - Parameters for controlling the material projection and repetition. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + *

Currently not used.

+ * @property {string} [cullFaceMode="CULL_BACK"] - Specifies Which faces of the geometry to render. Values can be: + *
    + *
  • "CULL_NONE" to render both sides of the geometry.
  • + *
  • "CULL_FRONT" to cull the front faces of the geometry.
  • + *
  • "CULL_BACK" (the default) to cull the back faces of the geometry.
  • + *
+ * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {Mat4|string} [texCoordTransform0] - The transform to use for all of the maps apart from + * occlusionMap and lightMap. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * @property {Mat4|string} [texCoordTransform1] - The transform to use for occlusionMap and + * lightMap. + * If "fallthrough" then it falls through to the material below. + * "hifi_pbr" model only. + * + * @property {string} procedural - The definition of a procedural shader material. + * "hifi_shader_simple" model only. + *

Currently not used.

+ * + * @property {boolean} defaultFallthrough - true if all properties fall through to the material below unless + * they are set, false if properties respect their individual fall-through settings. + */ QScriptValue scriptableMaterialToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterial &material) { QScriptValue obj = engine->newObject(); obj.setProperty("name", material.name); diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 808d3f221f..0f24ae8461 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -23,7 +23,10 @@ /**jsdoc - * The experimental Graphics API (experimental) lets you query and manage certain graphics-related structures (like underlying meshes and textures) from scripting. + * The Graphics API enables you to access and manipulate avatar, entity, and overlay models in the rendered scene. + * This includes getting mesh and material information for applying {@link Entities.EntityProperties-Material|Material} + * entities. + * * @namespace Graphics * * @hifi-interface @@ -40,44 +43,83 @@ public: public slots: /**jsdoc - * Returns a model reference object associated with the specified UUID ({@link EntityID} or {@link AvatarID}). - * + * Gets a handle to the model data used for displaying an avatar, 3D entity, or 3D overlay. + *

Note: The model data may be used for more than one instance of the item displayed in the scene.

* @function Graphics.getModel - * @param {UUID} entityID - The objectID of the model whose meshes are to be retrieved. - * @returns {Graphics.Model} the resulting Model object + * @param {UUID} id - The ID of the avatar, 3D entity, or 3D overlay. + * @returns {GraphicsModel} The model data for the avatar, entity, or overlay, as displayed. This includes the results of + * applying any {@link Entities.EntityProperties-Material|Material} entities to the item. + * @example Report some details of your avatar's model. + * var model = Graphics.getModel(MyAvatar.sessionUUID); + * var meshes = model.meshes; + * var numMeshparts = 0; + * for (var i = 0; i < meshes.length; i++) { + * numMeshparts += meshes[i].numParts; + * } + * + * print("Avatar:", MyAvatar.skeletonModelURL); + * print("Number of meshes:", model.numMeshes); + * print("Number of mesh parts:", numMeshparts); + * print("Material names: ", model.materialNames); + * print("Material layers:", Object.keys(model.materialLayers)); */ scriptable::ScriptableModelPointer getModel(const QUuid& uuid); /**jsdoc + * Updates the model for an avatar, 3D entity, or 3D overlay in the rendered scene. * @function Graphics.updateModel - * @param {Uuid} id - * @param {Graphics.Model} model - * @returns {boolean} + * @param {Uuid} id - The ID of the avatar, 3D entity, or 3D overlay to update. + * @param {GraphicsModel} model - The model to update the avatar, 3D entity, or 3D overlay with. + * @returns {boolean} true if the update was completed successfully, false if it wasn't. */ bool updateModel(const QUuid& uuid, const scriptable::ScriptableModelPointer& model); /**jsdoc + * Checks whether the model for an avatar, entity, or overlay can be updated in the rendered scene. Only avatars, + * "Model" entities and "model" overlays can have their meshes updated. * @function Graphics.canUpdateModel - * @param {Uuid} id - * @param {number} [meshIndex=-1] - * @param {number} [partNumber=-1] - * @returns {boolean} + * @param {Uuid} id - The ID of the avatar, entity, or overlay. + * @param {number} [meshIndex=-1] - Not used. + * @param {number} [partNumber=-1] - Not used. + * @returns {boolean} true if the model can be updated, false if it can't. + * @example Test whether different types of items can be updated. + * var modelEntityID = Entities.addEntity({ + * type: "Model", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(Camera.orientation, { x: -0.5, y: 0, z: -3 })), + * rotation: MyAvatar.orientation, + * modelURL: "https://apidocs.vircadia.dev/models/cowboy-hat.fbx", + * dimensions: { x: 0.8569, y: 0.3960, z: 1.0744 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * var shapeEntityID = Entities.addEntity({ + * type: "Shape", + * shape: "Cylinder", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(Camera.orientation, { x: 0.5, y: 0, z: -3 })), + * dimensions: { x: 0.4, y: 0.6, z: 0.4 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * print("Can update avatar:", Graphics.canUpdateModel(MyAvatar.sessionUUID)); // true + * print("Can update model entity:", Graphics.canUpdateModel(modelEntityID)); // true + * print("Can update shape entity:", Graphics.canUpdateModel(shapeEntityID)); // false + * }, 1000); // Wait for the entities to rez. */ bool canUpdateModel(const QUuid& uuid, int meshIndex = -1, int partNumber = -1); /**jsdoc + * Creates a new graphics model from meshes. * @function Graphics.newModel - * @param {Graphics.Mesh[]} meshes - * @returns {Graphics.Model} + * @param {GraphicsMesh[]} meshes - The meshes to include in the model. + * @returns {GraphicsModel} The new graphics model. */ scriptable::ScriptableModelPointer newModel(const scriptable::ScriptableMeshes& meshes); /**jsdoc - * Create a new Mesh / Mesh Part with the specified data buffers. - * + * Creates a new graphics mesh. * @function Graphics.newMesh - * @param {Graphics.IFSData} ifsMeshData Index-Faced Set (IFS) arrays used to create the new mesh. - * @returns {Graphics.Mesh} the resulting Mesh / Mesh Part object + * @param {Graphics.IFSData} ifsMeshData - Index-Faced Set (IFS) data defining the mesh. + * @returns {GraphicsMesh} The new graphics mesh. */ scriptable::ScriptableMeshPointer newMesh(const QVariantMap& ifsMeshData); @@ -89,11 +131,13 @@ public slots: #endif /**jsdoc + * Exports a model to OBJ format. * @function Graphics.exportModelToOBJ - * @param {Graphics.Model} model - * @returns {string} + * @param {GraphicsModel} model - The model to export. + * @returns {string} The OBJ format representation of the model. */ - QString exportModelToOBJ(const scriptable::ScriptableModel& in); + // FIXME: If you put the OBJ on the Asset Server and rez it, Interface keeps crashing until the model is removed. + QString exportModelToOBJ(const scriptable::ScriptableModelPointer& model); private: scriptable::ModelProviderPointer getModelProvider(const QUuid& uuid); diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp index da582b2d21..92dddc953e 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -37,6 +37,14 @@ QVariant toVariant(const Extents& box) { }; } +/**jsdoc + * The extents of a mesh. + * @typedef {object} Graphics.MeshExtents + * @property {Vec3} brn - The bottom right near (minimum axes values) corner of the enclosing box. + * @property {Vec3} tfl - The top far left (maximum axes values) corner of the enclosing box. + * @property {Vec3} center - The center of the enclosing box. + * @property {Vec3} dimensions - The dimensions of the enclosing box. + */ QVariant toVariant(const AABox& box) { return QVariantMap{ { "brn", glmVecToVariant(box.getCorner()) }, @@ -48,6 +56,16 @@ QVariant toVariant(const AABox& box) { }; } +/**jsdoc + * Details of a buffer element's format. + * @typedef {object} Graphics.BufferElementFormat + * @property {string} type - Type. + * @property {string} semantic - Semantic. + * @property {string} dimension - Dimension. + * @property {number} scalarCount - Scalar count. + * @property {number} byteSize - Byte size. + * @property {number} BYTES_PER_ELEMENT - Bytes per element. + */ QVariant toVariant(const gpu::Element& element) { return QVariantMap{ { "type", gpu::toString(element.getType()) }, diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 72d2adb48f..3de5119fa7 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -127,6 +127,16 @@ int scriptable::ScriptableMesh::getSlotNumber(const QString& attributeName) cons return -1; } +/**jsdoc + * Details of buffer's format. + * @typedef {object} Graphics.BufferFormat + * @property {number} slot - Slot. + * @property {number} length - Length. + * @property {number} byteLength - Byte length. + * @property {number} offset - Offset. + * @property {number} stride - Stride. + * @property {Graphics.BufferElementFormat} element - Element format. + */ QVariantMap scriptable::ScriptableMesh::getBufferFormats() const { QVariantMap result; for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { @@ -247,6 +257,13 @@ bool scriptable::ScriptableMesh::setVertexProperty(glm::uint32 vertexIndex, cons return buffer_helpers::setValue(bufferView, vertexIndex, value); } +/**jsdoc + * Called for each vertex when {@link GraphicsMesh.updateVertexAttributes} is called. + * @callback GraphicsMesh~forEachVertextCallback + * @param {Object} attributes - The attributes of the vertex. + * @param {number} index - The vertex index. + * @param {object} properties - The properties of the mesh, per {@link GraphicsMesh}. + */ glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { auto mesh = getMeshPointer(); if (!mesh) { @@ -275,7 +292,16 @@ glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { return numProcessed; } - +/**jsdoc + * Called for each vertex when {@link GraphicsMesh.updateVertexAttributes} is called. The value returned by the script function + * should be the modified attributes to update the vertex with, or false to not update the particular vertex. + * @callback GraphicsMesh~updateVertexAttributesCallback + * @param {Object} attributes - The attributes of the vertex. + * @param {number} index - The vertex index. + * @param {object} properties - The properties of the mesh, per {@link GraphicsMesh}. + * @returns {Object|boolean} The attribute values to update the vertex with, or + * false to not update the vertex. + */ glm::uint32 scriptable::ScriptableMesh::updateVertexAttributes(QScriptValue _callback) { auto mesh = getMeshPointer(); if (!mesh) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index dcb1c53759..0e7eecc03b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -29,17 +29,41 @@ namespace scriptable { /**jsdoc - * @typedef {object} Graphics.Mesh - * @property {Graphics.MeshPart[]} parts - Array of submesh part references. - * @property {string[]} attributeNames - Vertex attribute names (color, normal, etc.) - * @property {number} numParts - The number of parts contained in the mesh. - * @property {number} numIndices - Total number of vertex indices in the mesh. - * @property {number} numVertices - Total number of vertices in the Mesh. - * @property {number} numAttributes - Number of currently defined vertex attributes. - * @property {boolean} valid - * @property {boolean} strong - * @property {object} extents - * @property {object} bufferFormats + * A handle to in-memory mesh data in a {@link GraphicsModel}. + * + *

Create using the {@link Graphics} API, {@link GraphicsModel.cloneModel}, or {@link GraphicsMesh.cloneMesh}.

+ * + * @class GraphicsMesh + * @hideconstructor + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {number} numParts - The number of mesh parts. + * Read-only. + * @property {GraphicsMeshPart[]} parts - The mesh parts. + * Read-only. + * @property {number} numIndices - The total number of vertex indices in the mesh. + * Read-only. + * @property {number} numVertices - The total number of vertices in the mesh. + * Read-only. + * @property {number} numAttributes - The number of vertex attributes. + * Read-only. + * @property {Graphics.BufferTypeName[]} attributeNames - The names of the vertex attributes. + * Read-only. + * @property {boolean} valid - true if the mesh is valid, false if it isn't. + * Read-only. + * @property {boolean} strong - true if the mesh is valid and able to be used, false if it isn't. + * Read-only. + * @property {Graphics.MeshExtents} extents - The mesh extents, in model coordinates. + * Read-only. + * @property {Object} bufferFormats - Details of the buffers used for the + * mesh. + * Read-only. + * + * @borrows GraphicsMesh.getVertextAttributes as getVertextAttributes + * @borrows GraphicsMesh.setVertextAttributes as setVertextAttributes */ class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT @@ -82,26 +106,153 @@ namespace scriptable { int getSlotNumber(const QString& attributeName) const; public slots: + + /**jsdoc + * Gets the model the mesh is part of. + *

Currently doesn't work.

+ * @function GraphicsMesh.getParentModel + * @returns {GraphicsModel} The model the mesh is part of, null if it isn't part of a model. + */ const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } + + /**jsdoc + * Gets the vertex indices. + * @function GraphicsMesh.getIndices + * @returns {number[]} The vertex indices. + */ QVector getIndices() const; + + /**jsdoc + * Gets the indices of nearby vertices. + * @function GraphicsMesh.findNearbyVertexIndices + * @param {Vec3} origin - The search position, in model coordinates. + * @param {number} [epsilon=1e-6] - The search distance. If a vertex is within this distance from the + * origin it is considered to be "nearby". + * @returns {number[]} The indices of nearby vertices. + */ QVector findNearbyVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + /**jsdoc + * Adds an attribute for all vertices. + * @function GraphicsMesh.addAttribute + * @param {Graphics.BufferTypeName} name - The name of the attribute. + * @param {Graphics.BufferType} [defaultValue] - The value to give the attributes added to the vertices. + * @returns {number} The number of vertices the attribute was added to, 0 if the name was + * invalid or all vertices already had the attribute. + */ glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant()); + + /**jsdoc + * Sets the value of an attribute for all vertices. + * @function GraphicsMesh.fillAttribute + * @param {Graphics.BufferTypeName} name - The name of the attribute. The attribute is added to the vertices if not + * already present. + * @param {Graphics.BufferType} value - The value to give the attributes. + * @returns {number} 1 if the attribute name was valid and the attribute values were set, 0 + * otherwise. + */ glm::uint32 fillAttribute(const QString& attributeName, const QVariant& value); + + /**jsdoc + * Removes an attribute from all vertices. + *

Note: The "position" attribute cannot be removed.

+ * @function GraphicsMesh.removeAttribute + * @param {Graphics.BufferTypeName} name - The name of the attribute to remove. + * @returns {boolean} true if the attribute existed and was removed, false otherwise. + */ bool removeAttribute(const QString& attributeName); + /**jsdoc + * Gets the value of an attribute for all vertices. + * @function GraphicsMesh.queryVertexAttributes + * @param {Graphics.BufferTypeName} name - The name of the attribute to get the vertex values of. + * @throws Throws an error if the name is invalid or isn't used in the mesh. + * @returns {Graphics.BufferType[]} The attribute values for all vertices. + */ QVariantList queryVertexAttributes(QVariant selector) const; + + /**jsdoc + * Gets the attributes and attribute values of a vertex. + * @function GraphicsMesh.getVertexAttributes + * @param {number} index - The vertex to get the attributes for. + * @returns {Object} The attribute names and values for the vertex. + * @throws Throws an error if the index is invalid. + */ QVariantMap getVertexAttributes(glm::uint32 vertexIndex) const; + + /**jsdoc + * Updates attribute values of a vertex. + * @function GraphicsMesh.setVertexAttributes + * @param {number} index - The vertex to set the attributes for. + * @param {Object} values - The attribute names and values. Need not + * specify unchanged values. + * @returns {boolean} true if the index and the attribute names and values were valid and the vertex was + * updated, false otherwise. + * @throws Throws an error if the index is invalid or one of the attribute names is invalid or isn't used + * in the mesh. + */ + // @borrows jsdoc from GraphicsMesh bool setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributeValues); + /**jsdoc + * Gets the value of a vertex's attribute. + * @function GraphicsMesh.getVertexProperty + * @param {number} index - The vertex index. + * @param {Graphics.BufferTypeName} name - The name of the vertex attribute to get. + * @returns {Graphics.BufferType} The value of the vertex attribute. + * @throws Throws an error if the index is invalid or name is invalid or isn't used in the + * mesh. + */ QVariant getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const; + + /**jsdoc + * Sets the value of a vertex's attribute. + * @function GraphicsMesh.setVertexProperty + * @param {number} index - The vertex index. + * @param {Graphics.BufferTypeName} name - The name of the vertex attribute to set. + * @param {Graphics.BufferType} value - The vertex attribute value to set. + * @returns {boolean} true if the vertex attribute value was set, false if it wasn't. + * @throws Throws an error if the index is invalid or name is invalid or isn't used in the + * mesh. + */ bool setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value); + /**jsdoc + * Makes a copy of the mesh. + * @function GraphicsMesh.cloneMesh + * @returns {GraphicsMesh} A copy of the mesh. + */ scriptable::ScriptableMeshPointer cloneMesh(); // QScriptEngine-specific wrappers + + /**jsdoc + * Updates vertex attributes by calling a function for each vertex. The function can return modified attributes to + * update the vertex with. + * @function GraphicsMesh.updateVertexAttributes + * @param {GraphicsMesh~updateVertexAttributesCallback} callback - The function to call for each vertex. + * @returns {number} The number of vertices the callback was called for. + */ glm::uint32 updateVertexAttributes(QScriptValue callback); + + /**jsdoc + * Calls a function for each vertex. + * @function GraphicsMesh.forEachVertex + * @param {GraphicsMesh~forEachVertexCallback} callback - The function to call for each vertex. + * @returns {number} The number of vertices the callback was called for. + */ glm::uint32 forEachVertex(QScriptValue callback); + + /**jsdoc + * Checks if an index is valid and, optionally, that vertex has a particular attribute. + * @function GraphicsMesh.isValidIndex + * @param {number} index - The index to check. + * @param {Graphics.BufferTypeName} [attribute] - The attribute to check. + * @returns {boolean} true if the index is valid and that vertex has the attribute if specified. + * @throws Throws an error if the index if invalid or name is invalid or isn't used in the + * mesh. + */ + // FIXME: Should return false rather than throw an error. bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const; }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index 7352fcd0f6..878b239f3d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -11,23 +11,50 @@ namespace scriptable { /**jsdoc - * @typedef {object} Graphics.MeshPart - * @property {boolean} valid - * @property {number} partIndex - The part index (within the containing Mesh). - * @property {number} firstVertexIndex - * @property {number} baseVertexIndex - * @property {number} lastVertexIndex - * @property {Graphics.Topology} topology - element interpretation (currently only 'triangles' is supported). - * @property {string[]} attributeNames - Vertex attribute names (color, normal, etc.) - * @property {number} numIndices - Number of vertex indices that this mesh part refers to. - * @property {number} numVerticesPerFace - Number of vertices per face (eg: 3 when topology is 'triangles'). - * @property {number} numFaces - Number of faces represented by the mesh part (numIndices / numVerticesPerFace). - * @property {number} numVertices - Total number of vertices in the containing Mesh. - * @property {number} numAttributes - Number of currently defined vertex attributes. - * @property {object} extents - * @property {object} bufferFormats - */ + * A handle to in-memory mesh part data in a {@link GraphicsModel}. + * + *

Create using the {@link Graphics} API, {@link GraphicsModel.cloneModel}, {@link GraphicsMesh.cloneMesh}, or + * {@link GraphicsMeshPart.cloneMeshPart}.

+ * + * @class GraphicsMeshPart + * @hideconstructor + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {boolean} valid - true if the mesh part is valid, false if it isn't. + * Read-only. + * @property {number} partIndex - The index of the part within the whole mesh (i.e., parent and mesh parts). + * Read-only. + * @property {number} firstVertexIndex - The index of the first vertex. + * @property {number} baseVertexIndex - The index of the base vertex. + * @property {number} lastVertexIndex - The index of the last vertex. + * @property {Graphics.MeshTopology} topology - The element interpretation. Currently only triangles is supported. + * @property {number} numIndices - The number of vertex indices in the mesh part. + * @property {number} numVertices - The number of vertices in the whole mesh (i.e., parent and mesh parts). + * Read-only. + * @property {number} numVerticesPerFace - The number of vertices per face, per the topology (e.g., 3 for + * triangles). + * Read-only. + * @property {number} numFaces - The number of faces represented by the mesh part. + * Read-only. + * @property {number} numAttributes - The number of vertex attributes in the whole mesh (i.e., parent and mesh + * parts). + * Read-only. + * @property {Graphics.BufferTypeName[]} attributeNames - The names of the vertex attributes in the whole mesh + * (i.e., parent and mesh parts). + * Read-only. + * @property {Graphics.MeshExtents} extents - The mesh part extents, in model coordinates. + * Read-only. + * @property {Object} bufferFormats - Details of the buffers used for the + * whole mesh (i.e., parent and mesh parts). + * Read-only. + * @borrows GraphicsMesh.addAttribute as addAttribute + * @borrows GraphicsMesh.getVertexAttributes as getVertextAttributes + * @borrows GraphicsMesh.setVertexAttributes as setVertextAttributes + */ class ScriptableMeshPart : public QObject, QScriptable { Q_OBJECT Q_PROPERTY(bool valid READ isValid) @@ -55,42 +82,228 @@ namespace scriptable { bool isValid() const { auto mesh = getMeshPointer(); return mesh && partIndex < mesh->getNumParts(); } public slots: + + /**jsdoc + * Gets the vertex indices. + * @function GraphicsMeshPart.getIndices + * @returns {number[]} The vertex indices. + */ QVector getIndices() const; + + /**jsdoc + * Sets the vertex indices. + * @function GraphicsMeshPart.setIndices + * @param {number[]} indices - The vertex indices. + * @returns {boolean} true if successful, false if not. + * @throws Throws an error if the number of indices isn't the same, or an index is invalid. + */ bool setIndices(const QVector& indices); + + /**jsdoc + * Gets the indices of nearby vertices in the mesh part. + * @function GraphicsMeshPart.findNearbyPartVertexIndices + * @param {Vec3} origin - The search position, in model coordinates. + * @param {number} [epsilon=1e-6] - The search distance. If a vertex is within this distance from the + * origin it is considered to be "nearby". + * @returns {number[]} The indices of nearby vertices. + */ QVector findNearbyPartVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + + /**jsdoc + * Gets the value of an attribute for all vertices in the whole mesh (i.e., parent and mesh parts). + * @function GraphicsMeshPArt.queryVertexAttributes + * @param {Graphics.BufferTypeName} name - The name of the attribute to get the vertex values of. + * @throws Throws an error if the name is invalid or isn't used in the mesh. + * @returns {Graphics.BufferType[]} The attribute values for all vertices. + */ QVariantList queryVertexAttributes(QVariant selector) const; + + // @borrows jsdoc from GraphicsMesh. QVariantMap getVertexAttributes(glm::uint32 vertexIndex) const; + + // @borrows jsdoc from GraphicsMesh. bool setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributeValues); + /**jsdoc + * Gets the value of a vertex's attribute. + * @function GraphicsMeshPart.getVertexProperty + * @param {number} index - The vertex index. + * @param {Graphics.BufferTypeName} name - The name of the vertex attribute to get. + * @returns {Graphics.BufferType} The value of the vertex attribute. + * @throws Throws an error if the index is invalid or name is invalid or isn't used in the + * mesh. + */ QVariant getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const; + + /**jsdoc + * Sets the value of a vertex's attribute. + * @function GraphicsMeshPart.setVertexProperty + * @param {number} index - The vertex index. + * @param {Graphics.BufferTypeName} name - The name of the vertex attribute to set. + * @param {Graphics.BufferType} value - The vertex attribute value to set. + * @returns {boolean} true if the vertex attribute value was set, false if it wasn't. + * @throws Throws an error if the index is invalid or name is invalid or isn't used in the + * mesh. + */ bool setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& attributeValues); + /**jsdoc + * Gets the vertex indices that make up a face. + * @function GraphicsMeshPart.getFace + * @param {number} index - The index of the face. + * @returns {number[]} The vertex indices that make up the face, of number per the mesh topology. + */ QVector getFace(glm::uint32 faceIndex) const; + /**jsdoc + * Scales the mesh to so that it's maximum model coordinate dimension is a specified length. + * @function GraphicsMeshPart.scaleToFit + * @param {number} scale - The target dimension. + * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. + */ QVariantMap scaleToFit(float unitScale); + + /**jsdoc + * Translates the mesh part. + * @function GraphicsMeshPart.translate + * @param {Vec3} translation - The translation to apply, in model coordinates. + * @returns {Graphics.MeshExtents} The rseulting mesh extents, in model coordinates. + */ QVariantMap translate(const glm::vec3& translation); + + /**jsdoc + * Scales the mesh part. + * @function GraphicsMeshPart.scale + * @param {Vec3} scale - The scale to apply in each model coordinate direction. + * @param {Vec3} [origin] - The origin to scale about. If not specified, the center of the mesh part is used. + * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. + */ QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); + + /**jsdoc + * Rotates the mesh part, using Euler angles. + * @function GraphicsMeshPart.rotateDegrees + * @param {Vec3} eulerAngles - The rotation to perform, in mesh coordinates, as Euler angles in degrees. + * @param {Vec3} [origin] - The point about which to rotate, in model coordinates. + *

Warning: Currently doesn't work as expected.

+ * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. + */ QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); + + /**jsdoc + * Rotates the mesh part, using a quaternion. + * @function GraphicsMeshPart.rotate + * @param {Quat} rotation - The rotation to perform, in model coordinates. + * @param {Vec3} [origin] - The point about which to rotate, in model coordinates. + *

Warning: Currently doesn't work as expected.

+ * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. + */ QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); + + /**jsdoc + * Scales, rotates, and translates the mesh. + * @function GraphicsMeshPart.transform + * @param {Mat4} transform - The scale, rotate, and translate transform to apply. + * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. + */ QVariantMap transform(const glm::mat4& transform); + // @borrows jsdoc from GraphicsMesh. glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant()); + + /**jsdoc + * Sets the value of an attribute for all vertices in the whole mesh (i.e., parent and mesh parts). + * @function GraphicsMeshPart.fillAttribute + * @param {Graphics.BufferTypeName} name - The name of the attribute. The attribute is added to the vertices if not + * already present. + * @param {Graphics.BufferType} value - The value to give the attributes. + * @returns {number} 1 if the attribute name was valid and the attribute values were set, 0 + * otherwise. + */ glm::uint32 fillAttribute(const QString& attributeName, const QVariant& value); + + /**jsdoc + * Removes an attribute from all vertices in the whole mesh (i.e., parent and mesh parts). + *

Note: The "position" attribute cannot be removed.

+ * @function GraphicsMeshPArt.removeAttribute + * @param {Graphics.BufferTypeName} name - The name of the attribute to remove. + * @returns {boolean} true if the attribute existed and was removed, false otherwise. + */ bool removeAttribute(const QString& attributeName); + + /**jsdoc + * Deduplicates vertices. + * @function GraphicsMeshPart.dedupeVertices + * @param {number} [epsilon=1e-6] - The deduplicadtion distance. If a pair of vertices is within this distance of each + * other they are combined into a single vertex. + * @returns {boolean} true if the deduplication succeeded, false if it didn't. + */ bool dedupeVertices(float epsilon = 1e-6); + /**jsdoc + * Gets the parent mesh. + * @function GraphicsMeshPart.getParentMesh + * @returns {GraphicsMesh} The parent mesh. + */ scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } + /**jsdoc + * Replaces a mesh part with a copy of another mesh part. + * @function GraphicsMeshPart.replaceMeshPartData + * @param {GrphicsMeshPart} source - The mesh part to copy. + * @param {Graphics.BufferTypeName[]} [attributes] - The attributes to copy. If not specified, all attributes are + * copied from the source. + * @throws Throws an error if the mesh part of source mesh part aren't valid. + * @returns {boolean} true if the mesh part was successfully replaced, false if it wasn't. + */ bool replaceMeshPartData(scriptable::ScriptableMeshPartPointer source, const QVector& attributeNames = QVector()); + + /**jsdoc + * Makes a copy of the mesh part. + * @function GraphicsMeshPart.cloneMeshPart + * @returns {GraphicsMeshPart} A copy of the mesh part. + */ scriptable::ScriptableMeshPartPointer cloneMeshPart(); + /**jsdoc + * Exports the mesh part to OBJ format. + * @function GraphicsMeshPart.toOBJ + * @returns {string} The OBJ format representation of the mesh part. + */ QString toOBJ(); + // QScriptEngine-specific wrappers + + /**jsdoc + * Updates vertex attributes by calling a function for each vertex in the whole mesh (i.e., the parent and + * mesh parts). The function can return modified attributes to update the vertex with. + * @function GraphicsMeshPart.updateVertexAttributes + * @param {GraphicsMesh~updateVertexAttributesCallback} callback - The function to call for each vertex. + * @returns {number} The number of vertices the callback was called for. + */ glm::uint32 updateVertexAttributes(QScriptValue callback); + + /**jsdoc + * Calls a function for each vertex in the whole mesh (i.e., parent and mesh parts). + * @function GraphicsMeshPArt.forEachVertex + * @param {GraphicsMesh~forEachVertexCallback} callback - The function to call for each vertex. + * @returns {number} The number of vertices the callback was called for. + */ glm::uint32 forEachVertex(QScriptValue callback); + /**jsdoc + * Checks if an index is valid and, optionally, that vertex has a particular attribute. + * @function GraphicsMeshPart.isValidIndex + * @param {number} index - The index to check. + * @param {Graphics.BufferTypeName} [attribute] - The attribute to check. + * @returns {boolean} true if the index is valid and that vertex has the attribute if specified. + * @throws Throws an error if the index if invalid or name is invalid or isn't used in the + * mesh. + */ + // FIXME: Should return false rather than throw an error. bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const; + public: scriptable::ScriptableMeshPointer parentMesh; glm::uint32 partIndex; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 7d1ca5f560..a6f135c321 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -17,14 +17,36 @@ namespace scriptable { using ScriptableMeshes = QVector; /**jsdoc - * @typedef {object} Graphics.Model - * @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). + * A handle to in-memory model data such as may be used in displaying avatars, 3D entities, or 3D overlays in the rendered + * scene. Changes made to the model are visible only to yourself; they are not persisted. + *

Note: The model may be used for more than one instance of an item displayed in the scene. Modifying the model updates + * all instances displayed.

+ * + *

Create using the {@link Graphics} API or {@link GraphicsModel.cloneModel}.

+ * + * @class GraphicsModel + * @hideconstructor + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {Uuid} objectID - The ID of the entity or avatar that the model is associated with, if any; null + * if the model is not associated with an entity or avatar. + * Read-only. + * @property {number} numMeshes - The number of meshes in the model. + * Read-only. + * @property {GraphicsMesh[]} meshes - The meshes in the model. Each mesh may have more than one mesh part. + * Read-only. + * @property {string[]} materialNames - The names of the materials used by each mesh part in the model. The names are in + * the order of the meshes and their mesh parts. + * Read-only. + * @property {Object.} materialLayers - The mapping from mesh parts and material + * names to material layers. The mesh parts are numbered from "0" per the array indexes of + * materialNames. The material names are those used in materialNames. (You can look up a + * material layer by mesh part number or by material name.) + * Read-only. */ - class ScriptableModel : public ScriptableModelBase { Q_OBJECT Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) @@ -49,7 +71,23 @@ namespace scriptable { QVector getMaterialNames() { return materialNames; } public slots: + + /**jsdoc + * Makes a copy of the model. + * @function GraphicsModel.cloneModel + * @param {object} [options] - Not used. + * @returns {GraphicsModel} A copy of the model. + */ scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); + + /**jsdoc + * Gets a string description of the model. + * @function GraphicsModel.toString + * @returns {string} A string description of the model. + * @example Report the string description of your avatar's model. + * var model = Graphics.getModel(MyAvatar.sessionUUID); + * print("Avatar model info:", model.toString()); + */ QString toString() const; protected: diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index 301f5d8d73..87d19ec6a2 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -34,6 +34,50 @@ namespace buffer_helpers { const std::array XYZW = { { "x", "y", "z", "w" } }; const std::array ZERO123 = { { "0", "1", "2", "3" } }; +/**jsdoc + *

The type name of a graphics buffer.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"position"Position buffer.
"normal"normal buffer.
"tangent"Tangent buffer.
"color"Color buffer.
"skin_cluster_index"Skin cluster index buffer.
"skin_cluster_weight"Skin cluster weight buffer.
"texcoord0"First UV coordinates buffer.
"texcoord1"Second UV coordinates buffer.
"texcoord2"Third UV coordinates buffer.
"texcoord3"Fourth UV coordinates buffer.
"texcoord4"Fifth UV coordinates buffer.
+ * @typedef {string} Graphics.BufferTypeName + */ +/**jsdoc + *

The type of a graphics buffer value as accessed by JavaScript.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
TypeNameDescription
{@link Vec3}"position"Position buffer.
{@link Vec3}"normal"normal buffer.
{@link Vec3}"tangent"Tangent buffer.
{@link Vec4}"color"Color buffer.
{@link Vec4}"skin_cluster_index"Skin cluster index buffer.
{@link Vec4}"skin_cluster_weight"Skin cluster weight buffer.
{@link Vec2}"texcoord0"First UV coordinates buffer.
{@link Vec2}"texcoord1"Second UV coordinates buffer.
{@link Vec2}"texcoord2"Third UV coordinates buffer.
{@link Vec2}"texcoord3"Fourth UV coordinates buffer.
{@link Vec2}"texcoord4"Fifth UV coordinates buffer.
+ * @typedef {Vec4|Vec3|Vec2} Graphics.BufferType + */ QMap ATTRIBUTES{ {"position", gpu::Stream::POSITION }, {"normal", gpu::Stream::NORMAL }, diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index 20018ba71b..fe1981c0e9 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -79,23 +79,6 @@ public: // Access vertex position value const Vec3& getPos(Index index) const { return _vertexBuffer.get(index); } - /**jsdoc - * - * - * - * - * - * - * - * - * - * - * - * - * - *
ValueDescription
0Points.
1Lines.
2Line strip.
3Triangles.
4Triangle strip.
5Quads.
6Quad strip.
- * @typedef {number} Graphics.Topology - */ enum Topology { POINTS = 0, LINES, diff --git a/libraries/graphics/src/graphics/GpuHelpers.cpp b/libraries/graphics/src/graphics/GpuHelpers.cpp index b864b0f040..dd911e33c2 100644 --- a/libraries/graphics/src/graphics/GpuHelpers.cpp +++ b/libraries/graphics/src/graphics/GpuHelpers.cpp @@ -8,6 +8,24 @@ #include "GpuHelpers.h" +/**jsdoc + *

The interpretation of mesh elements.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"points"Points.
"lines"Lines.
"line_strip"Line strip.
"triangles"Triangles.
"triangle_strip"Triangle strip.
"quads"Quads.
"quad_strip"Quad strip.
+ * @typedef {string} Graphics.MeshTopology + */ namespace graphics { DebugEnums TOPOLOGIES{ { Mesh::Topology::POINTS, "points" }, diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index 236445bfda..500aaaa842 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -76,7 +76,7 @@ QStringList HFMModel::getJointNames() const { } bool HFMModel::hasBlendedMeshes() const { - if (!meshes.isEmpty()) { + if (!meshes.empty()) { foreach (const HFMMesh& mesh, meshes) { if (!mesh.blendshapes.isEmpty()) { return true; @@ -166,16 +166,16 @@ void HFMModel::computeKdops() { glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) }; - if (joints.size() != (int)shapeVertices.size()) { + if (joints.size() != shapeVertices.size()) { return; } // now that all joints have been scanned compute a k-Dop bounding volume of mesh - for (int i = 0; i < joints.size(); ++i) { + for (size_t i = 0; i < joints.size(); ++i) { HFMJoint& joint = joints[i]; // NOTE: points are in joint-frame ShapeVertices& points = shapeVertices.at(i); - glm::quat rotOffset = jointRotationOffsets.contains(i) ? glm::inverse(jointRotationOffsets[i]) : quat(); + glm::quat rotOffset = jointRotationOffsets.contains((int)i) ? glm::inverse(jointRotationOffsets[(int)i]) : quat(); if (points.size() > 0) { // compute average point glm::vec3 avgPoint = glm::vec3(0.0f); @@ -208,3 +208,164 @@ void HFMModel::computeKdops() { } } } + +void hfm::Model::debugDump() const { + qCDebug(modelformat) << "---------------- hfmModel ----------------"; + qCDebug(modelformat) << " hasSkeletonJoints =" << hasSkeletonJoints; + qCDebug(modelformat) << " offset =" << offset; + + qCDebug(modelformat) << " neckPivot = " << neckPivot; + + qCDebug(modelformat) << " bindExtents.size() = " << bindExtents.size(); + qCDebug(modelformat) << " meshExtents.size() = " << meshExtents.size(); + + qCDebug(modelformat) << "---------------- Shapes ----------------"; + qCDebug(modelformat) << " shapes.size() =" << shapes.size(); + for (const hfm::Shape& shape : shapes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " mesh =" << shape.mesh; + qCDebug(modelformat) << " meshPart =" << shape.meshPart; + qCDebug(modelformat) << " material =" << shape.material; + qCDebug(modelformat) << " joint =" << shape.joint; + qCDebug(modelformat) << " transformedExtents =" << shape.transformedExtents; + qCDebug(modelformat) << " skinDeformer =" << shape.skinDeformer; + } + + qCDebug(modelformat) << " jointIndices.size() =" << jointIndices.size(); + qCDebug(modelformat) << " joints.size() =" << joints.size(); + qCDebug(modelformat) << "---------------- Meshes ----------------"; + qCDebug(modelformat) << " meshes.size() =" << meshes.size(); + qCDebug(modelformat) << " blendshapeChannelNames = " << blendshapeChannelNames; + for (const HFMMesh& mesh : meshes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " meshpointer =" << mesh._mesh.get(); + qCDebug(modelformat) << " meshindex =" << mesh.meshIndex; + qCDebug(modelformat) << " vertices.size() =" << mesh.vertices.size(); + qCDebug(modelformat) << " colors.size() =" << mesh.colors.size(); + qCDebug(modelformat) << " normals.size() =" << mesh.normals.size(); + qCDebug(modelformat) << " tangents.size() =" << mesh.tangents.size(); + qCDebug(modelformat) << " colors.size() =" << mesh.colors.size(); + qCDebug(modelformat) << " texCoords.size() =" << mesh.texCoords.size(); + qCDebug(modelformat) << " texCoords1.size() =" << mesh.texCoords1.size(); + qCDebug(modelformat) << " clusterIndices.size() =" << mesh.clusterIndices.size(); + qCDebug(modelformat) << " clusterWeights.size() =" << mesh.clusterWeights.size(); + qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; + qCDebug(modelformat) << " parts.size() =" << mesh.parts.size(); + qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; + for (HFMBlendshape bshape : mesh.blendshapes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " bshape.indices.size() =" << bshape.indices.size(); + qCDebug(modelformat) << " bshape.vertices.size() =" << bshape.vertices.size(); + qCDebug(modelformat) << " bshape.normals.size() =" << bshape.normals.size(); + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; + for (HFMMeshPart meshPart : mesh.parts) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " quadIndices.size() =" << meshPart.quadIndices.size(); + qCDebug(modelformat) << " triangleIndices.size() =" << meshPart.triangleIndices.size(); + qCDebug(modelformat) << "\n"; + } + } + qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; + for (HFMAnimationFrame anim : animationFrames) { + qCDebug(modelformat) << " anim.translations = " << anim.translations; + qCDebug(modelformat) << " anim.rotations = " << anim.rotations; + } + QList mitomona_keys = meshIndicesToModelNames.keys(); + for (int key : mitomona_keys) { + qCDebug(modelformat) << " meshIndicesToModelNames key =" << key + << " val =" << meshIndicesToModelNames[key]; + } + + qCDebug(modelformat) << "---------------- Materials ----------------"; + + for (HFMMaterial mat : materials) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " mat.materialID =" << mat.materialID; + qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; + qCDebug(modelformat) << " diffuseFactor =" << mat.diffuseFactor; + qCDebug(modelformat) << " specularColor =" << mat.specularColor; + qCDebug(modelformat) << " specularFactor =" << mat.specularFactor; + qCDebug(modelformat) << " emissiveColor =" << mat.emissiveColor; + qCDebug(modelformat) << " emissiveFactor =" << mat.emissiveFactor; + qCDebug(modelformat) << " shininess =" << mat.shininess; + qCDebug(modelformat) << " opacity =" << mat.opacity; + qCDebug(modelformat) << " metallic =" << mat.metallic; + qCDebug(modelformat) << " roughness =" << mat.roughness; + qCDebug(modelformat) << " emissiveIntensity =" << mat.emissiveIntensity; + qCDebug(modelformat) << " ambientFactor =" << mat.ambientFactor; + + qCDebug(modelformat) << " materialID =" << mat.materialID; + qCDebug(modelformat) << " name =" << mat.name; + qCDebug(modelformat) << " shadingModel =" << mat.shadingModel; + qCDebug(modelformat) << " _material =" << mat._material.get(); + + qCDebug(modelformat) << " normalTexture =" << mat.normalTexture.filename; + qCDebug(modelformat) << " albedoTexture =" << mat.albedoTexture.filename; + qCDebug(modelformat) << " opacityTexture =" << mat.opacityTexture.filename; + + qCDebug(modelformat) << " lightmapParams =" << mat.lightmapParams; + + qCDebug(modelformat) << " isPBSMaterial =" << mat.isPBSMaterial; + qCDebug(modelformat) << " useNormalMap =" << mat.useNormalMap; + qCDebug(modelformat) << " useAlbedoMap =" << mat.useAlbedoMap; + qCDebug(modelformat) << " useOpacityMap =" << mat.useOpacityMap; + qCDebug(modelformat) << " useRoughnessMap =" << mat.useRoughnessMap; + qCDebug(modelformat) << " useSpecularMap =" << mat.useSpecularMap; + qCDebug(modelformat) << " useMetallicMap =" << mat.useMetallicMap; + qCDebug(modelformat) << " useEmissiveMap =" << mat.useEmissiveMap; + qCDebug(modelformat) << " useOcclusionMap =" << mat.useOcclusionMap; + qCDebug(modelformat) << "\n"; + } + + qCDebug(modelformat) << "---------------- Joints ----------------"; + + for (const HFMJoint& joint : joints) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint; + qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines; + qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; + qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; + + qCDebug(modelformat) << " ---"; + + qCDebug(modelformat) << " parentIndex" << joint.parentIndex; + qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; + qCDebug(modelformat) << " localTransform" << joint.localTransform; + qCDebug(modelformat) << " transform" << joint.transform; + qCDebug(modelformat) << " globalTransform" << joint.globalTransform; + + qCDebug(modelformat) << " ---"; + + qCDebug(modelformat) << " translation" << joint.translation; + qCDebug(modelformat) << " preTransform" << joint.preTransform; + qCDebug(modelformat) << " preRotation" << joint.preRotation; + qCDebug(modelformat) << " rotation" << joint.rotation; + qCDebug(modelformat) << " postRotation" << joint.postRotation; + qCDebug(modelformat) << " postTransform" << joint.postTransform; + qCDebug(modelformat) << " rotationMin" << joint.rotationMin; + qCDebug(modelformat) << " rotationMax" << joint.rotationMax; + qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; + qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.bindTransformFoundInCluster; + qCDebug(modelformat) << " bindTransform" << joint.bindTransform; + qCDebug(modelformat) << " name" << joint.name; + qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; + qCDebug(modelformat) << " geometricOffset" << joint.geometricOffset; + qCDebug(modelformat) << "\n"; + } + + qCDebug(modelformat) << "------------- SkinDeformers ------------"; + qCDebug(modelformat) << " skinDeformers.size() =" << skinDeformers.size(); + for(const hfm::SkinDeformer& skinDeformer : skinDeformers) { + qCDebug(modelformat) << "------- SkinDeformers (Clusters) -------"; + for (const hfm::Cluster& cluster : skinDeformer.clusters) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; + qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; + qCDebug(modelformat) << "\n"; + } + } + qCDebug(modelformat) << "\n"; +} diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 7111ad2e65..ca73676f86 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -66,6 +66,8 @@ static const int DRACO_ATTRIBUTE_ORIGINAL_INDEX = DRACO_BEGIN_CUSTOM_HIFI_ATTRIB // High Fidelity Model namespace namespace hfm { +static const uint32_t UNDEFINED_KEY = (uint32_t)-1; + /// A single blendshape. class Blendshape { public: @@ -111,19 +113,22 @@ public: bool isSkeletonJoint; bool bindTransformFoundInCluster; + // geometric offset is applied in local space but does NOT affect children. - bool hasGeometricOffset; - glm::vec3 geometricTranslation; - glm::quat geometricRotation; - glm::vec3 geometricScaling; + // TODO: Apply hfm::Joint.geometricOffset to transforms in the model preparation step + glm::mat4 geometricOffset; + + // globalTransform is the transform of the joint with all parent transforms applied, plus the geometric offset + glm::mat4 localTransform; + glm::mat4 globalTransform; }; /// A single binding to a joint. class Cluster { public: - - int jointIndex; + static const uint32_t INVALID_JOINT_INDEX { (uint32_t)-1 }; + uint32_t jointIndex { INVALID_JOINT_INDEX }; glm::mat4 inverseBindMatrix; Transform inverseBindTransform; }; @@ -155,8 +160,6 @@ public: QVector quadIndices; // original indices from the FBX mesh QVector quadTrianglesIndices; // original indices from the FBX mesh of the quad converted as triangles QVector triangleIndices; // original indices from the FBX mesh - - QString materialID; }; class Material { @@ -227,11 +230,20 @@ public: bool needTangentSpace() const; }; + +/// Simple Triangle List Mesh +struct TriangleListMesh { + std::vector vertices; + std::vector indices; + std::vector parts; // Offset in the indices, Number of indices + std::vector partExtents; // Extents of each part with no transform applied. Same length as parts. +}; + /// A single mesh (with optional blendshapes). class Mesh { public: - QVector parts; + std::vector parts; QVector vertices; QVector normals; @@ -239,21 +251,27 @@ public: QVector colors; QVector texCoords; QVector texCoords1; - QVector clusterIndices; - QVector clusterWeights; - QVector originalIndices; - QVector clusters; + Extents meshExtents; // DEPRECATED (see hfm::Shape::transformedExtents) + glm::mat4 modelTransform; // DEPRECATED (see hfm::Joint::globalTransform, hfm::Shape::transform, hfm::Model::joints) - Extents meshExtents; - glm::mat4 modelTransform; + // Skinning cluster attributes + std::vector clusterIndices; + std::vector clusterWeights; + uint16_t clusterWeightsPerVertex { 0 }; + // Blendshape attributes QVector blendshapes; + // Simple Triangle List Mesh generated during baking + hfm::TriangleListMesh triangleListMesh; + + QVector originalIndices; // Original indices of the vertices unsigned int meshIndex; // the order the meshes appeared in the object file graphics::MeshPointer _mesh; bool wasCompressed { false }; + }; /// A single animation frame. @@ -290,6 +308,30 @@ public: bool shouldInitCollisions() const { return _collisionsConfig.size() > 0; } }; +// A different skinning representation, used by FBXSerializer. We convert this to our graphics-optimized runtime representation contained within the mesh. +class SkinCluster { +public: + std::vector indices; + std::vector weights; +}; + +class SkinDeformer { +public: + std::vector clusters; +}; + +// The lightweight model part description. +class Shape { +public: + uint32_t mesh { UNDEFINED_KEY }; + uint32_t meshPart { UNDEFINED_KEY }; + uint32_t material { UNDEFINED_KEY }; + uint32_t joint { UNDEFINED_KEY }; // The hfm::Joint associated with this shape, containing transform information + // TODO: Have all serializers calculate hfm::Shape::transformedExtents in world space where they previously calculated hfm::Mesh::meshExtents. Change all code that uses hfm::Mesh::meshExtents to use this instead. + Extents transformedExtents; // The precise extents of the meshPart vertices in world space, after transform information is applied, while not taking into account rigging/skinning + uint32_t skinDeformer { UNDEFINED_KEY }; +}; + /// The runtime model format. class Model { public: @@ -300,15 +342,18 @@ public: QString author; QString applicationName; ///< the name of the application that generated the model - QVector joints; + std::vector shapes; + + std::vector meshes; + std::vector materials; + + std::vector skinDeformers; + + std::vector joints; QHash jointIndices; ///< 1-based, so as to more easily detect missing indices bool hasSkeletonJoints; - - QVector meshes; QVector scripts; - QHash materials; - glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file glm::vec3 neckPivot; @@ -340,19 +385,12 @@ public: QMap jointRotationOffsets; std::vector shapeVertices; FlowData flowData; + + void debugDump() const; }; }; -class ExtractedMesh { -public: - hfm::Mesh mesh; - QMultiHash newIndices; - QVector > blendshapeIndexMaps; - QVector > partMaterialTextures; - QHash texcoordSetMap; -}; - typedef hfm::Blendshape HFMBlendshape; typedef hfm::JointShapeInfo HFMJointShapeInfo; typedef hfm::Joint HFMJoint; @@ -361,8 +399,10 @@ typedef hfm::Texture HFMTexture; typedef hfm::MeshPart HFMMeshPart; typedef hfm::Material HFMMaterial; typedef hfm::Mesh HFMMesh; +typedef hfm::SkinDeformer HFMSkinDeformer; typedef hfm::AnimationFrame HFMAnimationFrame; typedef hfm::Light HFMLight; +typedef hfm::Shape HFMShape; typedef hfm::Model HFMModel; typedef hfm::FlowData FlowData; diff --git a/libraries/hfm/src/hfm/HFMModelMath.cpp b/libraries/hfm/src/hfm/HFMModelMath.cpp new file mode 100644 index 0000000000..436e520643 --- /dev/null +++ b/libraries/hfm/src/hfm/HFMModelMath.cpp @@ -0,0 +1,212 @@ +// +// HFMModelMath.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/10/04. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "HFMModelMath.h" + +#include + +#include + +#include +#include + +namespace hfm { + +void forEachIndex(const hfm::MeshPart& meshPart, std::function func) { + for (int i = 0; i <= meshPart.quadIndices.size() - 4; i += 4) { + func((uint32_t)meshPart.quadIndices[i]); + func((uint32_t)meshPart.quadIndices[i+1]); + func((uint32_t)meshPart.quadIndices[i+2]); + func((uint32_t)meshPart.quadIndices[i+3]); + } + for (int i = 0; i <= meshPart.triangleIndices.size() - 3; i += 3) { + func((uint32_t)meshPart.triangleIndices[i]); + func((uint32_t)meshPart.triangleIndices[i+1]); + func((uint32_t)meshPart.triangleIndices[i+2]); + } +} + +void thickenFlatExtents(Extents& extents) { + // Add epsilon to extents to compensate for flat plane + extents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON); + extents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON); +} + +void calculateExtentsForTriangleListMesh(TriangleListMesh& triangleListMesh) { + triangleListMesh.partExtents.resize(triangleListMesh.parts.size()); + for (size_t partIndex = 0; partIndex < triangleListMesh.parts.size(); ++partIndex) { + const auto& part = triangleListMesh.parts[partIndex]; + auto& extents = triangleListMesh.partExtents[partIndex]; + int partEnd = part.x + part.y; + for (int i = part.x; i < partEnd; ++i) { + auto index = triangleListMesh.indices[i]; + const auto& position = triangleListMesh.vertices[index]; + extents.addPoint(position); + } + } +} + +void calculateExtentsForShape(hfm::Shape& shape, const std::vector& triangleListMeshes, const std::vector& joints) { + auto& shapeExtents = shape.transformedExtents; + shapeExtents.reset(); + + const auto& triangleListMesh = triangleListMeshes[shape.mesh]; + const auto& partExtent = triangleListMesh.partExtents[shape.meshPart]; + + const glm::mat4& transform = joints[shape.joint].transform; + shapeExtents = partExtent; + shapeExtents.transform(transform); + + thickenFlatExtents(shapeExtents); +} + +void calculateExtentsForModel(Extents& modelExtents, const std::vector& shapes) { + modelExtents.reset(); + + for (size_t i = 0; i < shapes.size(); ++i) { + const auto& shape = shapes[i]; + const auto& shapeExtents = shape.transformedExtents; + modelExtents.addExtents(shapeExtents); + } +} + +ReweightedDeformers getReweightedDeformers(const size_t numMeshVertices, const std::vector skinClusters, const uint16_t weightsPerVertex) { + ReweightedDeformers reweightedDeformers; + if (skinClusters.size() == 0) { + return reweightedDeformers; + } + + size_t numClusterIndices = numMeshVertices * weightsPerVertex; + reweightedDeformers.indices.resize(numClusterIndices, (uint16_t)(skinClusters.size() - 1)); + reweightedDeformers.weights.resize(numClusterIndices, 0); + reweightedDeformers.weightsPerVertex = weightsPerVertex; + + std::vector weightAccumulators; + weightAccumulators.resize(numClusterIndices, 0.0f); + for (uint16_t i = 0; i < (uint16_t)skinClusters.size(); ++i) { + const hfm::SkinCluster& skinCluster = skinClusters[i]; + + if (skinCluster.indices.size() != skinCluster.weights.size()) { + reweightedDeformers.trimmedToMatch = true; + } + size_t numIndicesOrWeights = std::min(skinCluster.indices.size(), skinCluster.weights.size()); + for (size_t j = 0; j < numIndicesOrWeights; ++j) { + uint32_t index = skinCluster.indices[j]; + float weight = skinCluster.weights[j]; + + // look for an unused slot in the weights vector + uint32_t weightIndex = index * weightsPerVertex; + uint32_t lowestIndex = -1; + float lowestWeight = FLT_MAX; + uint16_t k = 0; + for (; k < weightsPerVertex; k++) { + if (weightAccumulators[weightIndex + k] == 0.0f) { + reweightedDeformers.indices[weightIndex + k] = i; + weightAccumulators[weightIndex + k] = weight; + break; + } + if (weightAccumulators[weightIndex + k] < lowestWeight) { + lowestIndex = k; + lowestWeight = weightAccumulators[weightIndex + k]; + } + } + if (k == weightsPerVertex && weight > lowestWeight) { + // no space for an additional weight; we must replace the lowest + weightAccumulators[weightIndex + lowestIndex] = weight; + reweightedDeformers.indices[weightIndex + lowestIndex] = i; + } + } + } + + // now that we've accumulated the most relevant weights for each vertex + // normalize and compress to 16-bits + for (size_t i = 0; i < numMeshVertices; ++i) { + size_t j = i * weightsPerVertex; + + // normalize weights into uint16_t + float totalWeight = 0.0f; + for (size_t k = j; k < j + weightsPerVertex; ++k) { + totalWeight += weightAccumulators[k]; + } + + const float ALMOST_HALF = 0.499f; + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (size_t k = j; k < j + weightsPerVertex; ++k) { + reweightedDeformers.weights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); + } + } else { + reweightedDeformers.weights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + } + } + + return reweightedDeformers; +} + +const TriangleListMesh generateTriangleListMesh(const std::vector& srcVertices, const std::vector& srcParts) { + + TriangleListMesh dest; + + // copy vertices for now + dest.vertices = srcVertices; + + std::vector oldToNewIndex(srcVertices.size()); + { + std::unordered_map uniqueVertexToNewIndex; + int oldIndex = 0; + int newIndex = 0; + for (const auto& srcVertex : srcVertices) { + auto foundIndex = uniqueVertexToNewIndex.find(srcVertex); + if (foundIndex != uniqueVertexToNewIndex.end()) { + oldToNewIndex[oldIndex] = foundIndex->second; + } else { + uniqueVertexToNewIndex[srcVertex] = newIndex; + oldToNewIndex[oldIndex] = newIndex; + dest.vertices[newIndex] = srcVertex; + ++newIndex; + } + ++oldIndex; + } + if (uniqueVertexToNewIndex.size() < srcVertices.size()) { + dest.vertices.resize(uniqueVertexToNewIndex.size()); + dest.vertices.shrink_to_fit(); + } + } + + auto newIndicesCount = 0; + for (const auto& part : srcParts) { + newIndicesCount += part.triangleIndices.size() + part.quadTrianglesIndices.size(); + } + + { + dest.indices.resize(newIndicesCount); + int i = 0; + for (const auto& part : srcParts) { + glm::ivec2 spart(i, 0); + for (const auto& qti : part.quadTrianglesIndices) { + dest.indices[i] = oldToNewIndex[qti]; + ++i; + } + for (const auto& ti : part.triangleIndices) { + dest.indices[i] = oldToNewIndex[ti]; + ++i; + } + spart.y = i - spart.x; + dest.parts.push_back(spart); + } + } + + calculateExtentsForTriangleListMesh(dest); + + return dest; +} + +}; diff --git a/libraries/hfm/src/hfm/HFMModelMath.h b/libraries/hfm/src/hfm/HFMModelMath.h new file mode 100644 index 0000000000..ef86e7379a --- /dev/null +++ b/libraries/hfm/src/hfm/HFMModelMath.h @@ -0,0 +1,45 @@ +// +// HFMModelMath.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/10/04. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_hfm_ModelMath_h +#define hifi_hfm_ModelMath_h + +#include "HFM.h" + +namespace hfm { + +void forEachIndex(const hfm::MeshPart& meshPart, std::function func); + +void initializeExtents(Extents& extents); + +void calculateExtentsForTriangleListMesh(TriangleListMesh& triangleListMesh); + +// This can't be moved to model-baker until +void calculateExtentsForShape(hfm::Shape& shape, const std::vector& triangleListMeshes, const std::vector& joints); + +void calculateExtentsForModel(Extents& modelExtents, const std::vector& shapes); + +struct ReweightedDeformers { + std::vector indices; + std::vector weights; + uint16_t weightsPerVertex { 0 }; + bool trimmedToMatch { false }; +}; + +const uint16_t DEFAULT_SKINNING_WEIGHTS_PER_VERTEX = 4; + +ReweightedDeformers getReweightedDeformers(const size_t numMeshVertices, const std::vector skinClusters, const uint16_t weightsPerVertex = DEFAULT_SKINNING_WEIGHTS_PER_VERTEX); + +const TriangleListMesh generateTriangleListMesh(const std::vector& srcVertices, const std::vector& srcParts); + +}; + +#endif // #define hifi_hfm_ModelMath_h diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h index d0be588d60..f28ef9f9c3 100644 --- a/libraries/hfm/src/hfm/HFMSerializer.h +++ b/libraries/hfm/src/hfm/HFMSerializer.h @@ -1,5 +1,5 @@ // -// FBXSerializer.h +// HFMSerializer.h // libraries/hfm/src/hfm // // Created by Sabrina Shanman on 2018/11/07. diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index b1746951bb..c436a5b510 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -126,12 +127,66 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { } } -void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { - auto currentMove = event->angleDelta() / 120.0f; - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()].value = currentMove.x() > 0 ? currentMove.x() : 0; - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()].value = currentMove.x() < 0 ? -currentMove.x() : 0; - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()].value = currentMove.y() > 0 ? currentMove.y() : 0; - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()].value = currentMove.y() < 0 ? -currentMove.y() : 0; +bool KeyboardMouseDevice::isWheelByTouchPad(QWheelEvent* event) { + // This function is only used to track two finger swipe using the touchPad on Windows. + // That gesture gets sent as a wheel event. This wheel delta values are used to orbit the camera. + // On MacOS the two finger swipe fires touch events and wheel events. + // In that case we always return false to avoid interference between both. +#ifdef Q_OS_MAC + return false; +#endif + QPoint delta = event->angleDelta(); + int deltaValueX = abs(delta.x()); + int deltaValueY = abs(delta.y()); + const int COMMON_WHEEL_DELTA_VALUE = 120; + // If deltaValueX or deltaValueY are multiple of 120 they are triggered by a mouse wheel + bool isMouseWheel = (deltaValueX + deltaValueY) % COMMON_WHEEL_DELTA_VALUE == 0; + if (!isMouseWheel) { + // We track repetition in wheel values to detect non-standard mouse wheels + const int MAX_WHEEL_DELTA_REPEAT = 10; + if (deltaValueX != 0) { + if (abs(_lastWheelDelta.x()) == deltaValueX) { + _wheelDeltaRepeatCount.setX(_wheelDeltaRepeatCount.x() + 1); + } else { + _wheelDeltaRepeatCount.setX(0); + } + return _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT; + } + if (deltaValueY != 0) { + if (abs(_lastWheelDelta.y()) == deltaValueY) { + _wheelDeltaRepeatCount.setY(_wheelDeltaRepeatCount.y() + 1); + } else { + _wheelDeltaRepeatCount.setY(0); + } + return _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT; + } + } + return false; +} + +void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { + if (isWheelByTouchPad(event)) { + // Check for horizontal and vertical scroll not triggered by the mouse. + // These are most likelly triggered by two fingers gesture on touchpad for windows. + QPoint delta = event->angleDelta(); + float deltaX = (float)delta.x(); + float deltaY = (float)delta.y(); + const float WHEEL_X_ATTENUATION = 0.3f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (deltaX > 0 ? WHEEL_X_ATTENUATION * deltaX : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (deltaX < 0 ? -WHEEL_X_ATTENUATION * deltaX : 0.0f); + // Y mouse is inverted positive is pointing up the screen + const float WHEEL_Y_ATTENUATION = 0.02f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = (deltaY < 0 ? -WHEEL_Y_ATTENUATION * deltaY : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()].value = (deltaY > 0 ? WHEEL_Y_ATTENUATION * deltaY : 0.0f); + } else { + auto currentMove = event->angleDelta() / 120.0f; + float currentMoveX = (float)currentMove.x(); + float currentMoveY = (float)currentMove.y(); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()].value = currentMoveX > 0 ? currentMoveX : 0.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()].value = currentMoveX < 0 ? -currentMoveX : 0.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()].value = currentMoveY > 0 ? currentMoveY : 0.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()].value = currentMoveY < 0 ? -currentMoveY : 0.0f; + } } glm::vec2 evalAverageTouchPoints(const QList& points) { @@ -145,6 +200,37 @@ glm::vec2 evalAverageTouchPoints(const QList& points) { return averagePoint; } +void KeyboardMouseDevice::touchGestureEvent(const QGestureEvent* event) { + QPinchGesture* pinchGesture = (QPinchGesture*) event->gesture(Qt::PinchGesture); + + if (pinchGesture) { + switch (pinchGesture->state()) { + case Qt::GestureStarted: + _lastTotalScaleFactor = pinchGesture->totalScaleFactor(); + break; + + case Qt::GestureUpdated: { + const float PINCH_DELTA_STEP = 0.04f; + qreal totalScaleFactor = pinchGesture->totalScaleFactor(); + qreal scaleFactorDelta = _lastTotalScaleFactor - totalScaleFactor; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()].value = scaleFactorDelta > 0.0 ? PINCH_DELTA_STEP : 0.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()].value = scaleFactorDelta < 0.0 ? PINCH_DELTA_STEP : 0.0f; + _lastTotalScaleFactor = totalScaleFactor; + break; + } + + case Qt::GestureFinished: { + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()].value = 0.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()].value = 0.0f; + break; + } + + default: + break; + } + } +} + void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { if (_enableTouch) { _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); @@ -167,7 +253,7 @@ void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { _lastTouchTime = _clock.now(); if (!_isTouching) { - _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + _isTouching = true; } else { auto currentMove = currentPos - _lastTouch; _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (currentMove.x > 0 ? currentMove.x : 0.0f); @@ -270,17 +356,17 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic * new x-coordinate value. * MouseYnumbernumberThe mouse y-coordinate changed. The data value is its * new y-coordinate value. - * MouseWheelRightnumbernumberThe mouse wheel rotated right. The data value - * is the number of units rotated (typically 1.0). - * MouseWheelLeftnumbernumberThe mouse wheel rotated left. The data value - * is the number of units rotated (typically 1.0). - * MouseWheelUpnumbernumberThe mouse wheel rotated up. The data value - * is the number of units rotated (typically 1.0). + * MouseWheelRightnumbernumberThe mouse wheel rotated right or two-finger + * swipe moved right. The data value is the number of units moved (typically 1.0). + * MouseWheelLeftnumbernumberThe mouse wheel rotated left or two-finger + * swipe moved left. The data value is the number of units moved (typically 1.0). + * MouseWheelUpnumbernumberThe mouse wheel rotated up or two-finger swipe + * moved up. The data value is the number of units move3d (typically 1.0). *

Warning: The mouse wheel in an ordinary mouse generates left/right wheel events instead of * up/down.

* - * MouseWheelDownnumbernumberThe mouse wheel rotated down. The data value - * is the number of units rotated (typically 1.0). + * MouseWheelDownnumbernumberThe mouse wheel rotated down or two-finger + * swipe moved down. The data value is the number of units moved (typically 1.0). *

Warning: The mouse wheel in an ordinary mouse generates left/right wheel events instead of * up/down.

* @@ -292,7 +378,11 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic * moved up. The data value is how far the average position of all touch points moved. * TouchpadDownnumbernumberThe average touch on a touch-enabled device * moved down. The data value is how far the average position of all touch points moved. - * + * GesturePinchOutnumbernumberThe average of two touches on a touch-enabled + * device moved out. The data value is how far the average positions of the touch points moved out. + * GesturePinchOutnumbernumberThe average of two touches on a touch-enabled + * device moved in. The data value is how far the average positions of the touch points moved in. + * * * @typedef {object} Controller.Hardware-Keyboard */ @@ -344,6 +434,8 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_NEG), "TouchpadLeft")); availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_POS), "TouchpadUp")); availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_NEG), "TouchpadDown")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_GESTURE_PINCH_POS), "GesturePinchOut")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_GESTURE_PINCH_NEG), "GesturePinchIn")); }); return availableInputs; } diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index f6921c8e23..4286ced477 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -23,6 +23,7 @@ class QTouchEvent; class QKeyEvent; class QMouseEvent; class QWheelEvent; +class QGestureEvent; class KeyboardMouseDevice : public InputPlugin { Q_OBJECT @@ -60,6 +61,8 @@ public: TOUCH_AXIS_X_NEG, TOUCH_AXIS_Y_POS, TOUCH_AXIS_Y_NEG, + TOUCH_GESTURE_PINCH_POS, + TOUCH_GESTURE_PINCH_NEG, }; enum TouchButtonChannel { @@ -81,11 +84,13 @@ public: void mouseReleaseEvent(QMouseEvent* event); void eraseMouseClicked(); + void touchGestureEvent(const QGestureEvent* event); void touchBeginEvent(const QTouchEvent* event); void touchEndEvent(const QTouchEvent* event); void touchUpdateEvent(const QTouchEvent* event); void wheelEvent(QWheelEvent* event); + bool isWheelByTouchPad(QWheelEvent* event); static void enableTouch(bool enableTouch) { _enableTouch = enableTouch; } @@ -121,6 +126,7 @@ protected: QPoint _previousCursor; QPoint _mousePressPos; quint64 _mousePressTime; + qreal _lastTotalScaleFactor; bool _clickDeadspotActive; glm::vec2 _lastTouch; std::shared_ptr _inputDevice { std::make_shared() }; @@ -130,6 +136,8 @@ protected: std::chrono::high_resolution_clock::time_point _lastTouchTime; static bool _enableTouch; + QPoint _lastWheelDelta; + QPoint _wheelDeltaRepeatCount; private: void updateDeltaAxisValue(int channel, float value); diff --git a/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h b/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h index 58d2784855..5ff15c03d9 100644 --- a/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h +++ b/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h @@ -37,6 +37,10 @@ class TextureCacheScriptingInterface : public ScriptableResourceCache, public De * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 47a8db82b8..d200df211d 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -13,34 +13,61 @@ #include "BakerTypes.h" #include "ModelMath.h" +#include "CollectShapeVerticesTask.h" #include "BuildGraphicsMeshTask.h" #include "CalculateMeshNormalsTask.h" #include "CalculateMeshTangentsTask.h" #include "CalculateBlendshapeNormalsTask.h" #include "CalculateBlendshapeTangentsTask.h" #include "PrepareJointsTask.h" +#include "CalculateTransformedExtentsTask.h" #include "BuildDracoMeshTask.h" #include "ParseFlowDataTask.h" +#include namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector>; + using Output = VaryingSet9, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector, std::vector, std::vector, Extents, std::vector>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { const auto& hfmModelIn = input; - output.edit0() = hfmModelIn->meshes.toStdVector(); + output.edit0() = hfmModelIn->meshes; output.edit1() = hfmModelIn->originalURL; output.edit2() = hfmModelIn->meshIndicesToModelNames; auto& blendshapesPerMesh = output.edit3(); blendshapesPerMesh.reserve(hfmModelIn->meshes.size()); - for (int i = 0; i < hfmModelIn->meshes.size(); i++) { + for (size_t i = 0; i < hfmModelIn->meshes.size(); i++) { blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); } - output.edit4() = hfmModelIn->joints.toStdVector(); + output.edit4() = hfmModelIn->joints; + output.edit5() = hfmModelIn->shapes; + output.edit6() = hfmModelIn->skinDeformers; + output.edit7() = hfmModelIn->meshExtents; + output.edit8() = hfmModelIn->materials; + } + }; + + class BuildMeshTriangleListTask { + public: + using Input = std::vector; + using Output = std::vector; + using JobModel = Job::ModelIO; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + const auto& meshesIn = input; + auto& indexedTrianglesMeshOut = output; + indexedTrianglesMeshOut.clear(); + indexedTrianglesMeshOut.resize(meshesIn.size()); + + for (size_t i = 0; i < meshesIn.size(); i++) { + auto& mesh = meshesIn[i]; + const auto verticesStd = mesh.vertices.toStdVector(); + indexedTrianglesMeshOut[i] = hfm::generateTriangleListMesh(verticesStd, mesh.parts); + } } }; @@ -75,21 +102,23 @@ namespace baker { class BuildMeshesTask { public: - using Input = VaryingSet5, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; + using Input = VaryingSet6, std::vector, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; using Output = std::vector; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { auto& meshesIn = input.get0(); int numMeshes = (int)meshesIn.size(); - auto& graphicsMeshesIn = input.get1(); - auto& normalsPerMeshIn = input.get2(); - auto& tangentsPerMeshIn = input.get3(); - auto& blendshapesPerMeshIn = input.get4(); + auto& triangleListMeshesIn = input.get1(); + auto& graphicsMeshesIn = input.get2(); + auto& normalsPerMeshIn = input.get3(); + auto& tangentsPerMeshIn = input.get4(); + auto& blendshapesPerMeshIn = input.get5(); auto meshesOut = meshesIn; for (int i = 0; i < numMeshes; i++) { auto& meshOut = meshesOut[i]; + meshOut.triangleListMesh = triangleListMeshesIn[i]; meshOut._mesh = safeGet(graphicsMeshesIn, i); meshOut.normals = QVector::fromStdVector(safeGet(normalsPerMeshIn, i)); meshOut.tangents = QVector::fromStdVector(safeGet(tangentsPerMeshIn, i)); @@ -101,17 +130,22 @@ namespace baker { class BuildModelTask { public: - using Input = VaryingSet6, std::vector, QMap, QHash, FlowData>; + using Input = VaryingSet9, std::vector, QMap, QHash, FlowData, std::vector, std::vector, Extents>; using Output = hfm::Model::Pointer; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { auto hfmModelOut = input.get0(); - hfmModelOut->meshes = QVector::fromStdVector(input.get1()); - hfmModelOut->joints = QVector::fromStdVector(input.get2()); + hfmModelOut->meshes = input.get1(); + hfmModelOut->joints = input.get2(); hfmModelOut->jointRotationOffsets = input.get3(); hfmModelOut->jointIndices = input.get4(); hfmModelOut->flowData = input.get5(); + hfmModelOut->shapeVertices = input.get6(); + hfmModelOut->shapes = input.get7(); + hfmModelOut->meshExtents = input.get8(); + // These depend on the ShapeVertices + // TODO: Create a task for this rather than calculating it here hfmModelOut->computeKdops(); output = hfmModelOut; } @@ -134,6 +168,10 @@ namespace baker { const auto meshIndicesToModelNames = modelPartsIn.getN(2); const auto blendshapesPerMeshIn = modelPartsIn.getN(3); const auto jointsIn = modelPartsIn.getN(4); + const auto shapesIn = modelPartsIn.getN(5); + const auto skinDeformersIn = modelPartsIn.getN(6); + const auto modelExtentsIn = modelPartsIn.getN(7); + const auto materialsIn = modelPartsIn.getN(8); // Calculate normals and tangents for meshes and blendshapes if they do not exist // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. @@ -145,8 +183,15 @@ namespace baker { const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn).asVarying(); const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); + // Calculate shape vertices. These rely on the weight-normalized clusterIndices/clusterWeights in the mesh, and are used later for computing the joint kdops + const auto collectShapeVerticesInputs = CollectShapeVerticesTask::Input(meshesIn, shapesIn, jointsIn, skinDeformersIn).asVarying(); + const auto shapeVerticesPerJoint = model.addJob("CollectShapeVertices", collectShapeVerticesInputs); + + // Build the slim triangle list mesh for each hfm::mesh + const auto triangleListMeshes = model.addJob("BuildMeshTriangleListTask", meshesIn); + // Build the graphics::MeshPointer for each hfm::Mesh - const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh, shapesIn, skinDeformersIn).asVarying(); const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); // Prepare joint information @@ -156,6 +201,12 @@ namespace baker { const auto jointRotationOffsets = jointInfoOut.getN(1); const auto jointIndices = jointInfoOut.getN(2); + // Use transform information to compute extents + const auto calculateExtentsInputs = CalculateTransformedExtentsTask::Input(modelExtentsIn, triangleListMeshes, shapesIn, jointsOut).asVarying(); + const auto calculateExtentsOutputs = model.addJob("CalculateExtents", calculateExtentsInputs); + const auto modelExtentsOut = calculateExtentsOutputs.getN(0); + const auto shapesOut = calculateExtentsOutputs.getN(1); + // Parse material mapping const auto parseMaterialMappingInputs = ParseMaterialMappingTask::Input(mapping, materialMappingBaseURL).asVarying(); const auto materialMapping = model.addJob("ParseMaterialMapping", parseMaterialMappingInputs); @@ -165,7 +216,7 @@ namespace baker { // TODO: Tangent support (Needs changes to FBXSerializer_Mesh as well) // NOTE: Due to an unresolved linker error, BuildDracoMeshTask is not functional on Android // TODO: Figure out why BuildDracoMeshTask.cpp won't link with draco on Android - const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(meshesIn, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(shapesOut, meshesIn, materialsIn, normalsPerMesh, tangentsPerMesh).asVarying(); const auto buildDracoMeshOutputs = model.addJob("BuildDracoMesh", buildDracoMeshInputs); const auto dracoMeshes = buildDracoMeshOutputs.getN(0); const auto dracoErrors = buildDracoMeshOutputs.getN(1); @@ -177,9 +228,9 @@ namespace baker { // Combine the outputs into a new hfm::Model const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); - const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); + const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, triangleListMeshes, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); - const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData).asVarying(); + const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData, shapeVerticesPerJoint, shapesOut, modelExtentsOut).asVarying(); const auto hfmModelOut = model.addJob("BuildModel", buildModelInputs); output = Output(hfmModelOut, materialMapping, dracoMeshes, dracoErrors, materialList); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 3d16afab2e..8760fa6db4 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -36,6 +36,14 @@ namespace baker { using TangentsPerBlendshape = std::vector>; using MeshIndicesToModelNames = QHash; + + class ReweightedDeformers { + public: + std::vector indices; + std::vector weights; + uint16_t weightsPerVertex { 0 }; + bool trimmedToMatch { false }; + }; }; #endif // hifi_BakerTypes_h diff --git a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp index 12347c30b1..5c9d1dac25 100644 --- a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp @@ -39,19 +39,47 @@ #include "ModelMath.h" #ifndef Q_OS_ANDROID -std::vector createMaterialList(const hfm::Mesh& mesh) { - std::vector materialList; - for (const auto& meshPart : mesh.parts) { - auto materialID = QVariant(meshPart.materialID).toByteArray(); - const auto materialIt = std::find(materialList.cbegin(), materialList.cend(), materialID); - if (materialIt == materialList.cend()) { - materialList.push_back(materialID); + +void reindexMaterials(const std::vector& originalMaterialIndices, std::vector& materials, std::vector& materialIndices) { + materialIndices.resize(originalMaterialIndices.size()); + for (size_t i = 0; i < originalMaterialIndices.size(); ++i) { + uint32_t material = originalMaterialIndices[i]; + auto foundMaterial = std::find(materials.cbegin(), materials.cend(), material); + if (foundMaterial == materials.cend()) { + materials.push_back(material); + materialIndices[i] = (uint16_t)(materials.size() - 1); + } else { + materialIndices[i] = (uint16_t)(foundMaterial - materials.cbegin()); } } - return materialList; } -std::tuple, bool> createDracoMesh(const hfm::Mesh& mesh, const std::vector& normals, const std::vector& tangents, const std::vector& materialList) { +void createMaterialLists(const std::vector& shapes, const std::vector& meshes, const std::vector& hfmMaterials, std::vector>& materialIndexLists, std::vector>& partMaterialIndicesPerMesh) { + std::vector> materialsPerMesh; + for (const auto& mesh : meshes) { + materialsPerMesh.emplace_back(mesh.parts.size(), hfm::UNDEFINED_KEY); + } + for (const auto& shape : shapes) { + materialsPerMesh[shape.mesh][shape.meshPart] = shape.material; + } + + materialIndexLists.resize(materialsPerMesh.size()); + partMaterialIndicesPerMesh.resize(materialsPerMesh.size()); + for (size_t i = 0; i < materialsPerMesh.size(); ++i) { + const std::vector& materials = materialsPerMesh[i]; + std::vector uniqueMaterials; + + reindexMaterials(materials, uniqueMaterials, partMaterialIndicesPerMesh[i]); + + materialIndexLists[i].reserve(uniqueMaterials.size()); + for (const uint32_t material : uniqueMaterials) { + const auto& hfmMaterial = hfmMaterials[material]; + materialIndexLists[i].push_back(QVariant(hfmMaterial.materialID).toByteArray()); + } + } +} + +std::tuple, bool> createDracoMesh(const hfm::Mesh& mesh, const std::vector& normals, const std::vector& tangents, const std::vector& partMaterialIndices) { Q_ASSERT(normals.size() == 0 || (int)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()); @@ -122,11 +150,9 @@ std::tuple, bool> createDracoMesh(const hfm::Mesh& auto partIndex = 0; draco::FaceIndex face; - uint16_t materialID; for (auto& part : mesh.parts) { - auto materialIt = std::find(materialList.cbegin(), materialList.cend(), QVariant(part.materialID).toByteArray()); - materialID = (uint16_t)(materialIt - materialList.cbegin()); + uint16_t materialID = partMaterialIndices[partIndex]; auto addFace = [&](const QVector& indices, int index, draco::FaceIndex face) { int32_t idx0 = indices[index]; @@ -214,30 +240,33 @@ void BuildDracoMeshTask::run(const baker::BakeContextPointer& context, const Inp #ifdef Q_OS_ANDROID qCWarning(model_baker) << "BuildDracoMesh is disabled on Android. Output meshes will be empty."; #else - const auto& meshes = input.get0(); - const auto& normalsPerMesh = input.get1(); - const auto& tangentsPerMesh = input.get2(); + const auto& shapes = input.get0(); + const auto& meshes = input.get1(); + const auto& materials = input.get2(); + const auto& normalsPerMesh = input.get3(); + const auto& tangentsPerMesh = input.get4(); auto& dracoBytesPerMesh = output.edit0(); auto& dracoErrorsPerMesh = output.edit1(); + auto& materialLists = output.edit2(); + std::vector> partMaterialIndicesPerMesh; + createMaterialLists(shapes, meshes, materials, materialLists, partMaterialIndicesPerMesh); dracoBytesPerMesh.reserve(meshes.size()); // vector is an exception to the std::vector conventions as it is a bit field // So a bool reference to an element doesn't work dracoErrorsPerMesh.resize(meshes.size()); - materialLists.reserve(meshes.size()); for (size_t i = 0; i < meshes.size(); i++) { const auto& mesh = meshes[i]; const auto& normals = baker::safeGet(normalsPerMesh, i); const auto& tangents = baker::safeGet(tangentsPerMesh, i); dracoBytesPerMesh.emplace_back(); auto& dracoBytes = dracoBytesPerMesh.back(); - materialLists.push_back(createMaterialList(mesh)); - const auto& materialList = materialLists.back(); + const auto& partMaterialIndices = partMaterialIndicesPerMesh[i]; bool dracoError; std::unique_ptr dracoMesh; - std::tie(dracoMesh, dracoError) = createDracoMesh(mesh, normals, tangents, materialList); + std::tie(dracoMesh, dracoError) = createDracoMesh(mesh, normals, tangents, partMaterialIndices); dracoErrorsPerMesh[i] = dracoError; if (dracoMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h index ac9ad648ab..a83f2ae163 100644 --- a/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildDracoMeshTask.h @@ -33,7 +33,7 @@ public: class BuildDracoMeshTask { public: using Config = BuildDracoMeshConfig; - using Input = baker::VaryingSet3, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Input = baker::VaryingSet5, std::vector, std::vector, baker::NormalsPerMesh, baker::TangentsPerMesh>; using Output = baker::VaryingSet3, std::vector, std::vector>>; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 2467da7656..66429ed2c4 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -2,8 +2,8 @@ // BuildGraphicsMeshTask.h // model-baker/src/model-baker // -// Created by Sabrina Shanman on 2018/12/06. -// Copyright 2018 High Fidelity, Inc. +// Created by Sabrina Shanman on 2019/09/16. +// Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -15,6 +15,7 @@ #include #include "ModelBakerLogging.h" +#include #include "ModelMath.h" using vec2h = glm::tvec2; @@ -27,7 +28,7 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn) { +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn, uint16_t numDeformerControllers) { auto graphicsMesh = std::make_shared(); // Fill tangents with a dummy value to force tangents to be present if there are normals @@ -86,25 +87,24 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Support for 4 skinning clusters: // 4 Indices are uint8 ideally, uint16 if more than 256. - const auto clusterIndiceElement = (hfmMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + const auto clusterIndiceElement = ((numDeformerControllers < (uint16_t)UINT8_MAX) ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); // 4 Weights are normalized 16bits const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); - // Cluster indices and weights must be the same sizes - const int NUM_CLUSTERS_PER_VERT = 4; - const int numVertClusters = (hfmMesh.clusterIndices.size() == hfmMesh.clusterWeights.size() ? hfmMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); - const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); - const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); + // Record cluster sizes + const size_t numVertClusters = hfmMesh.clusterWeightsPerVertex == 0 ? 0 : hfmMesh.clusterIndices.size() / hfmMesh.clusterWeightsPerVertex; + const size_t clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); + const size_t clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); // Decide on where to put what seequencially in a big buffer: - const int positionsOffset = 0; - const int normalsAndTangentsOffset = positionsOffset + positionsSize; - const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; - const int texCoordsOffset = colorsOffset + colorsSize; - const int texCoords1Offset = texCoordsOffset + texCoordsSize; - const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; - const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; + const size_t positionsOffset = 0; + const size_t normalsAndTangentsOffset = positionsOffset + positionsSize; + const size_t colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; + const size_t texCoordsOffset = colorsOffset + colorsSize; + const size_t texCoords1Offset = texCoordsOffset + texCoordsSize; + const size_t clusterIndicesOffset = texCoords1Offset + texCoords1Size; + const size_t clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; + const size_t totalVertsSize = clusterWeightsOffset + clusterWeightsSize; // Copy all vertex data in a single buffer auto vertBuffer = std::make_shared(); @@ -181,22 +181,22 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Clusters data if (clusterIndicesSize > 0) { - if (hfmMesh.clusters.size() < UINT8_MAX) { + if (numDeformerControllers < (uint16_t)UINT8_MAX) { // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = hfmMesh.clusterIndices.size(); - QVector clusterIndices; - clusterIndices.resize(numIndices); + int32_t numIndices = (int32_t)hfmMesh.clusterIndices.size(); + std::vector packedDeformerIndices; + packedDeformerIndices.resize(numIndices); for (int32_t i = 0; i < numIndices; ++i) { assert(hfmMesh.clusterIndices[i] <= UINT8_MAX); - clusterIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); + packedDeformerIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); } - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) packedDeformerIndices.data()); } else { - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.constData()); + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.data()); } } if (clusterWeightsSize > 0) { - vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.constData()); + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.data()); } @@ -206,7 +206,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics auto vertexBufferStream = std::make_shared(); gpu::BufferPointer attribBuffer; - int totalAttribBufferSize = totalVertsSize; + size_t totalAttribBufferSize = totalVertsSize; gpu::uint8 posChannel = 0; gpu::uint8 tangentChannel = posChannel; gpu::uint8 attribChannel = posChannel; @@ -377,6 +377,17 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const const auto& meshIndicesToModelNames = input.get2(); const auto& normalsPerMesh = input.get3(); const auto& tangentsPerMesh = input.get4(); + const auto& shapes = input.get5(); + const auto& skinDeformers = input.get6(); + + // Currently, there is only (at most) one skinDeformer per mesh + // An undefined shape.skinDeformer has the value hfm::UNDEFINED_KEY + std::vector skinDeformerPerMesh; + skinDeformerPerMesh.resize(meshes.size(), hfm::UNDEFINED_KEY); + for (const auto& shape : shapes) { + uint32_t skinDeformerIndex = shape.skinDeformer; + skinDeformerPerMesh[shape.mesh] = skinDeformerIndex; + } auto& graphicsMeshes = output; @@ -384,9 +395,16 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const for (int i = 0; i < n; i++) { graphicsMeshes.emplace_back(); auto& graphicsMesh = graphicsMeshes[i]; - + + uint16_t numDeformerControllers = 0; + uint32_t skinDeformerIndex = skinDeformerPerMesh[i]; + if (skinDeformerIndex != hfm::UNDEFINED_KEY) { + const hfm::SkinDeformer& skinDeformer = skinDeformers[skinDeformerIndex]; + numDeformerControllers = (uint16_t)skinDeformer.clusters.size(); + } + // Try to create the graphics::Mesh - buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i)); + buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i), numDeformerControllers); // Choose a name for the mesh if (graphicsMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h index bb4136c086..34128eabe8 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -2,8 +2,8 @@ // BuildGraphicsMeshTask.h // model-baker/src/model-baker // -// Created by Sabrina Shanman on 2018/12/06. -// Copyright 2018 High Fidelity, Inc. +// Created by Sabrina Shanman on 2019/09/16. +// Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,7 +20,7 @@ class BuildGraphicsMeshTask { public: - using Input = baker::VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Input = baker::VaryingSet7, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh, std::vector, std::vector>; using Output = std::vector; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index 297d8cbde7..6147ce72e7 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -30,7 +30,7 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co // Otherwise confirm if we have the normals and texcoords needed if (!tangentsIn.empty()) { tangentsOut = tangentsIn.toStdVector(); - } else if (!normals.empty() && mesh.vertices.size() == mesh.texCoords.size()) { + } else if (!normals.empty() && mesh.vertices.size() <= mesh.texCoords.size()) { tangentsOut.resize(normals.size()); baker::calculateTangents(mesh, [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { diff --git a/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp new file mode 100644 index 0000000000..028dba4939 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.cpp @@ -0,0 +1,41 @@ +// +// CalculateTransformedExtentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/10/04. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CalculateTransformedExtentsTask.h" + +#include "hfm/HFMModelMath.h" + +void CalculateTransformedExtentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& modelExtentsIn = input.get0(); + const auto& triangleListMeshes = input.get1(); + const auto& shapesIn = input.get2(); + const auto& joints = input.get3(); + auto& modelExtentsOut = output.edit0(); + auto& shapesOut = output.edit1(); + + shapesOut.reserve(shapesIn.size()); + for (size_t i = 0; i < shapesIn.size(); ++i) { + shapesOut.push_back(shapesIn[i]); + auto& shapeOut = shapesOut.back(); + + auto& shapeExtents = shapeOut.transformedExtents; + if (shapeExtents.isValid()) { + continue; + } + + hfm::calculateExtentsForShape(shapeOut, triangleListMeshes, joints); + } + + modelExtentsOut = modelExtentsIn; + if (!modelExtentsOut.isValid()) { + hfm::calculateExtentsForModel(modelExtentsOut, shapesOut); + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h b/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h new file mode 100644 index 0000000000..aed089a13d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateTransformedExtentsTask.h @@ -0,0 +1,29 @@ +// +// CalculateTransformedExtentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/10/04. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CalculateExtentsTask_h +#define hifi_CalculateExtentsTask_h + +#include "Engine.h" +#include "hfm/HFM.h" + +// Calculates any undefined extents in the shapes and the model. Precalculated extents will be left alone. +// Bind extents will currently not be calculated +class CalculateTransformedExtentsTask { +public: + using Input = baker::VaryingSet4, std::vector, std::vector>; + using Output = baker::VaryingSet2>; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateExtentsTask_h diff --git a/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp b/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp new file mode 100644 index 0000000000..13bc75ced9 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.cpp @@ -0,0 +1,91 @@ +// +// CollectShapeVerticesTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/09/27. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CollectShapeVerticesTask.h" + +#include + +#include + +// Used to track and avoid duplicate shape vertices, as multiple shapes can have the same mesh and skinDeformer +class VertexSource { +public: + uint32_t mesh; + uint32_t skinDeformer; + + bool operator==(const VertexSource& other) const { + return mesh == other.mesh && + skinDeformer == other.skinDeformer; + } +}; + +void CollectShapeVerticesTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& meshes = input.get0(); + const auto& shapes = input.get1(); + const auto& joints = input.get2(); + const auto& skinDeformers = input.get3(); + auto& shapeVerticesPerJoint = output; + + shapeVerticesPerJoint.resize(joints.size()); + std::vector> vertexSourcesPerJoint; + vertexSourcesPerJoint.resize(joints.size()); + for (size_t i = 0; i < shapes.size(); ++i) { + const auto& shape = shapes[i]; + const uint32_t skinDeformerKey = shape.skinDeformer; + if (skinDeformerKey == hfm::UNDEFINED_KEY) { + continue; + } + + VertexSource vertexSource; + vertexSource.mesh = shape.mesh; + vertexSource.skinDeformer = skinDeformerKey; + + const auto& skinDeformer = skinDeformers[skinDeformerKey]; + for (size_t j = 0; j < skinDeformer.clusters.size(); ++j) { + const auto& cluster = skinDeformer.clusters[j]; + const uint32_t jointIndex = cluster.jointIndex; + + auto& vertexSources = vertexSourcesPerJoint[jointIndex]; + if (std::find(vertexSources.cbegin(), vertexSources.cend(), vertexSource) == vertexSources.cend()) { + vertexSources.push_back(vertexSource); + auto& shapeVertices = shapeVerticesPerJoint[jointIndex]; + + const auto& mesh = meshes[shape.mesh]; + const auto& vertices = mesh.vertices; + const glm::mat4 meshToJoint = cluster.inverseBindMatrix; + + const uint16_t weightsPerVertex = mesh.clusterWeightsPerVertex; + if (weightsPerVertex == 0) { + for (int vertexIndex = 0; vertexIndex < (int)vertices.size(); ++vertexIndex) { + const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertices[vertexIndex]); + shapeVertices.push_back(extractTranslation(vertexTransform)); + } + } else { + for (int vertexIndex = 0; vertexIndex < (int)vertices.size(); ++vertexIndex) { + for (uint16_t weightIndex = 0; weightIndex < weightsPerVertex; ++weightIndex) { + const size_t index = vertexIndex*weightsPerVertex + weightIndex; + const uint16_t clusterIndex = mesh.clusterIndices[index]; + const uint16_t clusterWeight = mesh.clusterWeights[index]; + // Remember vertices associated with this joint with at least 1/4 weight + const uint16_t EXPANSION_WEIGHT_THRESHOLD = std::numeric_limits::max() / 4; + if (clusterIndex != j || clusterWeight < EXPANSION_WEIGHT_THRESHOLD) { + continue; + } + + const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertices[vertexIndex]); + shapeVertices.push_back(extractTranslation(vertexTransform)); + } + } + } + } + } + } +} diff --git a/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h b/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h new file mode 100644 index 0000000000..a665004d6b --- /dev/null +++ b/libraries/model-baker/src/model-baker/CollectShapeVerticesTask.h @@ -0,0 +1,30 @@ +// +// CollectShapeVerticesTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/09/27. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CollectShapeVerticesTask_h +#define hifi_CollectShapeVerticesTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +class CollectShapeVerticesTask { +public: + using Input = baker::VaryingSet4, std::vector, std::vector, std::vector>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CollectShapeVerticesTask_h + diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 1fcfcfcc70..bb911c6914 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -203,23 +203,23 @@ QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) { return textureBaseUrl.isValid() ? textureBaseUrl : url; } -GeometryResource::GeometryResource(const GeometryResource& other) : +ModelResource::ModelResource(const ModelResource& other) : Resource(other), - Geometry(other), + NetworkModel(other), _modelLoader(other._modelLoader), _mappingPair(other._mappingPair), _textureBaseURL(other._textureBaseURL), _combineParts(other._combineParts), _isCacheable(other._isCacheable) { - if (other._geometryResource) { + if (other._modelResource) { _startedLoading = false; } } -void GeometryResource::downloadFinished(const QByteArray& data) { +void ModelResource::downloadFinished(const QByteArray& data) { if (_effectiveBaseURL.fileName().toLower().endsWith(".fst")) { - PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString(), { { "url", _url.toString() } }); + PROFILE_ASYNC_BEGIN(resource_parse_geometry, "ModelResource::downloadFinished", _url.toString(), { { "url", _url.toString() } }); // store parsed contents of FST file _mapping = FSTReader::readMapping(data); @@ -267,19 +267,19 @@ void GeometryResource::downloadFinished(const QByteArray& data) { auto modelCache = DependencyManager::get(); GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseURL, false }; - // Get the raw GeometryResource - _geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); + // Get the raw ModelResource + _modelResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); // Avoid caching nested resources - their references will be held by the parent - _geometryResource->_isCacheable = false; + _modelResource->_isCacheable = false; - if (_geometryResource->isLoaded()) { - onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty()); + if (_modelResource->isLoaded()) { + onGeometryMappingLoaded(!_modelResource->getURL().isEmpty()); } else { if (_connection) { disconnect(_connection); } - _connection = connect(_geometryResource.data(), &Resource::finished, this, &GeometryResource::onGeometryMappingLoaded); + _connection = connect(_modelResource.data(), &Resource::finished, this, &ModelResource::onGeometryMappingLoaded); } } } else { @@ -291,32 +291,31 @@ void GeometryResource::downloadFinished(const QByteArray& data) { } } -void GeometryResource::onGeometryMappingLoaded(bool success) { - if (success && _geometryResource) { - _hfmModel = _geometryResource->_hfmModel; - _materialMapping = _geometryResource->_materialMapping; - _meshParts = _geometryResource->_meshParts; - _meshes = _geometryResource->_meshes; - _materials = _geometryResource->_materials; +void ModelResource::onGeometryMappingLoaded(bool success) { + if (success && _modelResource) { + _hfmModel = _modelResource->_hfmModel; + _materialMapping = _modelResource->_materialMapping; + _meshes = _modelResource->_meshes; + _materials = _modelResource->_materials; // Avoid holding onto extra references - _geometryResource.reset(); + _modelResource.reset(); // Make sure connection will not trigger again disconnect(_connection); // FIXME Should not have to do this } - PROFILE_ASYNC_END(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString()); + PROFILE_ASYNC_END(resource_parse_geometry, "ModelResource::downloadFinished", _url.toString()); finishedLoading(success); } -void GeometryResource::setExtra(void* extra) { +void ModelResource::setExtra(void* extra) { const GeometryExtra* geometryExtra = static_cast(extra); _mappingPair = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash()); _textureBaseURL = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl(); _combineParts = geometryExtra ? geometryExtra->combineParts : true; } -void GeometryResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) { +void ModelResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) { // Assume ownership of the processed HFMModel _hfmModel = hfmModel; _materialMapping = materialMapping; @@ -329,31 +328,23 @@ void GeometryResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const M } std::shared_ptr meshes = std::make_shared(); - std::shared_ptr parts = std::make_shared(); int meshID = 0; for (const HFMMesh& mesh : _hfmModel->meshes) { // Copy mesh pointers meshes->emplace_back(mesh._mesh); - int partID = 0; - for (const HFMMeshPart& part : mesh.parts) { - // Construct local parts - parts->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); - partID++; - } meshID++; } _meshes = meshes; - _meshParts = parts; finishedLoading(true); } -void GeometryResource::deleter() { +void ModelResource::deleter() { resetTextures(); Resource::deleter(); } -void GeometryResource::setTextures() { +void ModelResource::setTextures() { if (_hfmModel) { for (const HFMMaterial& material : _hfmModel->materials) { _materials.push_back(std::make_shared(material, _textureBaseURL)); @@ -361,7 +352,7 @@ void GeometryResource::setTextures() { } } -void GeometryResource::resetTextures() { +void ModelResource::resetTextures() { _materials.clear(); } @@ -377,17 +368,17 @@ ModelCache::ModelCache() { } QSharedPointer ModelCache::createResource(const QUrl& url) { - return QSharedPointer(new GeometryResource(url, _modelLoader), &GeometryResource::deleter); + return QSharedPointer(new ModelResource(url, _modelLoader), &ModelResource::deleter); } QSharedPointer ModelCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new GeometryResource(*resource.staticCast()), &GeometryResource::deleter); + return QSharedPointer(new ModelResource(*resource.staticCast()), &ModelResource::deleter); } -GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { +ModelResource::Pointer ModelCache::getModelResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { bool combineParts = true; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); + ModelResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -396,12 +387,12 @@ GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const return resource; } -GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url, +ModelResource::Pointer ModelCache::getCollisionModelResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { bool combineParts = false; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); + ModelResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -410,7 +401,7 @@ GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& u return resource; } -const QVariantMap Geometry::getTextures() const { +const QVariantMap NetworkModel::getTextures() const { QVariantMap textures; for (const auto& material : _materials) { for (const auto& texture : material->_textures) { @@ -424,22 +415,21 @@ const QVariantMap Geometry::getTextures() const { } // FIXME: The materials should only be copied when modified, but the Model currently caches the original -Geometry::Geometry(const Geometry& geometry) { - _hfmModel = geometry._hfmModel; - _materialMapping = geometry._materialMapping; - _meshes = geometry._meshes; - _meshParts = geometry._meshParts; +NetworkModel::NetworkModel(const NetworkModel& networkModel) { + _hfmModel = networkModel._hfmModel; + _materialMapping = networkModel._materialMapping; + _meshes = networkModel._meshes; - _materials.reserve(geometry._materials.size()); - for (const auto& material : geometry._materials) { + _materials.reserve(networkModel._materials.size()); + for (const auto& material : networkModel._materials) { _materials.push_back(std::make_shared(*material)); } - _animGraphOverrideUrl = geometry._animGraphOverrideUrl; - _mapping = geometry._mapping; + _animGraphOverrideUrl = networkModel._animGraphOverrideUrl; + _mapping = networkModel._mapping; } -void Geometry::setTextures(const QVariantMap& textureMap) { +void NetworkModel::setTextures(const QVariantMap& textureMap) { if (_meshes->size() > 0) { for (auto& material : _materials) { // Check if any material textures actually changed @@ -447,7 +437,7 @@ void Geometry::setTextures(const QVariantMap& textureMap) { [&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.second.texture && textureMap.contains(it.second.name); })) { // FIXME: The Model currently caches the materials (waste of space!) - // so they must be copied in the Geometry copy-ctor + // so they must be copied in the NetworkModel copy-ctor // if (material->isOriginal()) { // // Copy the material to avoid mutating the cached version // material = std::make_shared(*material); @@ -461,11 +451,11 @@ void Geometry::setTextures(const QVariantMap& textureMap) { // If we only use cached textures, they should all be loaded areTexturesLoaded(); } else { - qCWarning(modelnetworking) << "Ignoring setTextures(); geometry not ready"; + qCWarning(modelnetworking) << "Ignoring setTextures(); NetworkModel not ready"; } } -bool Geometry::areTexturesLoaded() const { +bool NetworkModel::areTexturesLoaded() const { if (!_areTexturesLoaded) { for (auto& material : _materials) { if (material->isMissingTexture()) { @@ -500,30 +490,28 @@ bool Geometry::areTexturesLoaded() const { return true; } -const std::shared_ptr Geometry::getShapeMaterial(int partID) const { - if ((partID >= 0) && (partID < (int)_meshParts->size())) { - int materialID = _meshParts->at(partID)->materialID; - if ((materialID >= 0) && (materialID < (int)_materials.size())) { - return _materials[materialID]; - } +const std::shared_ptr NetworkModel::getShapeMaterial(int shapeID) const { + uint32_t materialID = getHFMModel().shapes[shapeID].material; + if (materialID < (uint32_t)_materials.size()) { + return _materials[materialID]; } return nullptr; } -void GeometryResourceWatcher::startWatching() { - connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); - connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); +void ModelResourceWatcher::startWatching() { + connect(_resource.data(), &Resource::finished, this, &ModelResourceWatcher::resourceFinished); + connect(_resource.data(), &Resource::onRefresh, this, &ModelResourceWatcher::resourceRefreshed); if (_resource->isLoaded()) { resourceFinished(!_resource->getURL().isEmpty()); } } -void GeometryResourceWatcher::stopWatching() { - disconnect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); - disconnect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); +void ModelResourceWatcher::stopWatching() { + disconnect(_resource.data(), &Resource::finished, this, &ModelResourceWatcher::resourceFinished); + disconnect(_resource.data(), &Resource::onRefresh, this, &ModelResourceWatcher::resourceRefreshed); } -void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { +void ModelResourceWatcher::setResource(ModelResource::Pointer resource) { if (_resource) { stopWatching(); } @@ -537,14 +525,14 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { } } -void GeometryResourceWatcher::resourceFinished(bool success) { +void ModelResourceWatcher::resourceFinished(bool success) { if (success) { - _geometryRef = std::make_shared(*_resource); + _networkModelRef = std::make_shared(*_resource); } emit finished(success); } -void GeometryResourceWatcher::resourceRefreshed() { +void ModelResourceWatcher::resourceRefreshed() { // FIXME: Model is not set up to handle a refresh // _instance.reset(); } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 615951345f..d61bd61696 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -22,23 +22,20 @@ #include #include "ModelLoader.h" -class MeshPart; - using GeometryMappingPair = std::pair; Q_DECLARE_METATYPE(GeometryMappingPair) -class Geometry { +class NetworkModel { public: - using Pointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; + using Pointer = std::shared_ptr; + using WeakPointer = std::weak_ptr; - Geometry() = default; - Geometry(const Geometry& geometry); - virtual ~Geometry() = default; + NetworkModel() = default; + NetworkModel(const NetworkModel& geometry); + virtual ~NetworkModel() = default; // Immutable over lifetime using GeometryMeshes = std::vector>; - using GeometryMeshParts = std::vector>; // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; @@ -63,7 +60,6 @@ protected: HFMModel::ConstPointer _hfmModel; MaterialMapping _materialMapping; std::shared_ptr _meshes; - std::shared_ptr _meshParts; // Copied to each geometry, mutable throughout lifetime via setTextures NetworkMaterials _materials; @@ -76,22 +72,22 @@ private: }; /// A geometry loaded from the network. -class GeometryResource : public Resource, public Geometry { +class ModelResource : public Resource, public NetworkModel { Q_OBJECT public: - using Pointer = QSharedPointer; + using Pointer = QSharedPointer; - GeometryResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {} - GeometryResource(const GeometryResource& other); + ModelResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {} + ModelResource(const ModelResource& other); - QString getType() const override { return "Geometry"; } + QString getType() const override { return "Model"; } virtual void deleter() override; virtual void downloadFinished(const QByteArray& data) override; void setExtra(void* extra) override; - virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); } + virtual bool areTexturesLoaded() const override { return isLoaded() && NetworkModel::areTexturesLoaded(); } private slots: void onGeometryMappingLoaded(bool success); @@ -115,21 +111,21 @@ private: QUrl _textureBaseURL; bool _combineParts; - GeometryResource::Pointer _geometryResource; + ModelResource::Pointer _modelResource; QMetaObject::Connection _connection; bool _isCacheable{ true }; }; -class GeometryResourceWatcher : public QObject { +class ModelResourceWatcher : public QObject { Q_OBJECT public: - using Pointer = std::shared_ptr; + using Pointer = std::shared_ptr; - GeometryResourceWatcher() = delete; - GeometryResourceWatcher(Geometry::Pointer& geometryPtr) : _geometryRef(geometryPtr) {} + ModelResourceWatcher() = delete; + ModelResourceWatcher(NetworkModel::Pointer& geometryPtr) : _networkModelRef(geometryPtr) {} - void setResource(GeometryResource::Pointer resource); + void setResource(ModelResource::Pointer resource); QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } int getResourceDownloadAttempts() { return _resource ? _resource->getDownloadAttempts() : 0; } @@ -147,8 +143,8 @@ private slots: void resourceRefreshed(); private: - GeometryResource::Pointer _resource; - Geometry::Pointer& _geometryRef; + ModelResource::Pointer _resource; + NetworkModel::Pointer& _networkModelRef; }; /// Stores cached model geometries. @@ -158,18 +154,18 @@ class ModelCache : public ResourceCache, public Dependency { public: - GeometryResource::Pointer getGeometryResource(const QUrl& url, + ModelResource::Pointer getModelResource(const QUrl& url, const GeometryMappingPair& mapping = GeometryMappingPair(QUrl(), QVariantHash()), const QUrl& textureBaseUrl = QUrl()); - GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url, + ModelResource::Pointer getCollisionModelResource(const QUrl& url, const GeometryMappingPair& mapping = GeometryMappingPair(QUrl(), QVariantHash()), const QUrl& textureBaseUrl = QUrl()); protected: - friend class GeometryResource; + friend class ModelResource; virtual QSharedPointer createResource(const QUrl& url) override; QSharedPointer createResourceCopy(const QSharedPointer& resource) override; @@ -180,12 +176,4 @@ private: ModelLoader _modelLoader; }; -class MeshPart { -public: - MeshPart(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {} - int meshID { -1 }; - int partID { -1 }; - int materialID { -1 }; -}; - #endif // hifi_ModelCache_h diff --git a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h index cea2a6cd40..d83b853b01 100644 --- a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h +++ b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h @@ -37,6 +37,10 @@ class ModelCacheScriptingInterface : public ScriptableResourceCache, public Depe * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 5473f1a010..83c0fd28dd 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -52,11 +52,13 @@ const int PULL_SETTINGS_RETRY_INTERVAL = 2 * MSECS_PER_SECOND; const int MAX_PULL_RETRIES = 10; JSONCallbackParameters::JSONCallbackParameters(QObject* callbackReceiver, - const QString& jsonCallbackMethod, - const QString& errorCallbackMethod) : + const QString& jsonCallbackMethod, + const QString& errorCallbackMethod, + const QJsonObject& callbackData) : callbackReceiver(callbackReceiver), jsonCallbackMethod(jsonCallbackMethod), - errorCallbackMethod(errorCallbackMethod) + errorCallbackMethod(errorCallbackMethod), + callbackData(callbackData) { } @@ -348,9 +350,17 @@ void AccountManager::sendRequest(const QString& path, [callbackParams, networkReply] { if (networkReply->error() == QNetworkReply::NoError) { if (!callbackParams.jsonCallbackMethod.isEmpty()) { - bool invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, - qPrintable(callbackParams.jsonCallbackMethod), - Q_ARG(QNetworkReply*, networkReply)); + bool invoked = false; + if (callbackParams.callbackData.isEmpty()) { + invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.jsonCallbackMethod), + Q_ARG(QNetworkReply*, networkReply)); + } else { + invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.jsonCallbackMethod), + Q_ARG(QNetworkReply*, networkReply), + Q_ARG(QJsonObject, callbackParams.callbackData)); + } if (!invoked) { QString error = "Could not invoke " + callbackParams.jsonCallbackMethod + " with QNetworkReply* " @@ -366,9 +376,18 @@ void AccountManager::sendRequest(const QString& path, } } else { if (!callbackParams.errorCallbackMethod.isEmpty()) { - bool invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, - qPrintable(callbackParams.errorCallbackMethod), - Q_ARG(QNetworkReply*, networkReply)); + bool invoked = false; + if (callbackParams.callbackData.isEmpty()) { + invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.errorCallbackMethod), + Q_ARG(QNetworkReply*, networkReply)); + } + else { + invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.errorCallbackMethod), + Q_ARG(QNetworkReply*, networkReply), + Q_ARG(QJsonObject, callbackParams.callbackData)); + } if (!invoked) { QString error = "Could not invoke " + callbackParams.errorCallbackMethod + " with QNetworkReply* " diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 37cb548b7e..26fbe502a0 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -31,14 +31,16 @@ class JSONCallbackParameters { public: JSONCallbackParameters(QObject* callbackReceiver = nullptr, - const QString& jsonCallbackMethod = QString(), - const QString& errorCallbackMethod = QString()); + const QString& jsonCallbackMethod = QString(), + const QString& errorCallbackMethod = QString(), + const QJsonObject& callbackData = QJsonObject()); bool isEmpty() const { return !callbackReceiver; } QObject* callbackReceiver; QString jsonCallbackMethod; QString errorCallbackMethod; + QJsonObject callbackData; }; namespace AccountManagerAuth { diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 1939e6401a..5f08f19d2b 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -243,7 +243,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrlIn, LookupTrigger trigger) { QUrl lookupUrl = lookupUrlIn; - qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); + if (!lookupUrl.host().isEmpty() && !lookupUrl.path().isEmpty()) { + // Assignment clients ping for empty url until assigned. Don't spam. + qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); + } if (lookupUrl.scheme().isEmpty() && !lookupUrl.path().startsWith("/")) { // 'urls' without schemes are taken as domain names, as opposed to diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 31bda960ec..8bdb777f96 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -49,10 +49,10 @@ const QString GET_PLACE = "/api/v1/places/%1"; * @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid(0)|Uuid.NULL} if you're not * connected to the domain or are in a serverless domain. * Read-only. - * @property {string} hostname - The name of the domain for your current metaverse address (e.g., "AvatarIsland", + * @property {string} hostname - The name of the domain for your current metaverse address (e.g., "DomainName", * localhost, or an IP address). Is blank if you're in a serverless domain. * Read-only. - * @property {string} href - Your current metaverse address (e.g., "hifi://avatarisland/15,-10,26/0,0,0,1") + * @property {string} href - Your current metaverse address (e.g., "hifi://domainname/15,-10,26/0,0,0,1") * regardless of whether or not you're connected to the domain. Starts with "file:///" if you're in a * serverless domain. * Read-only. @@ -62,12 +62,72 @@ const QString GET_PLACE = "/api/v1/places/%1"; * (e.g., "/15,-10,26/0,0,0,1"). * Read-only. * @property {string} placename - The place name in your current href metaverse address - * (e.g., "AvatarIsland"). Is blank if your hostname is an IP address. + * (e.g., "DomainName"). Is blank if your hostname is an IP address. * Read-only. * @property {string} protocol - The protocol of your current href metaverse address (e.g., "hifi"). * Read-only. */ +/**jsdoc + * The AddressManager API provides facilities related to your current location in the metaverse. + * + * @namespace AddressManager + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @deprecated This API is deprecated and will be removed. Use the {@link location} or {@link Window|Window.location} APIs + * instead. + * + * @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid(0)|Uuid.NULL} if you're not + * connected to the domain or are in a serverless domain. + * Read-only. + * @property {string} hostname - The name of the domain for your current metaverse address (e.g., "DomainName", + * localhost, or an IP address). Is blank if you're in a serverless domain. + * Read-only. + * @property {string} href - Your current metaverse address (e.g., "hifi://domainname/15,-10,26/0,0,0,1") + * regardless of whether or not you're connected to the domain. Starts with "file:///" if you're in a + * serverless domain. + * Read-only. + * @property {boolean} isConnected - true if you're connected to the domain in your current href + * metaverse address, otherwise false. + * @property {string} pathname - The location and orientation in your current href metaverse address + * (e.g., "/15,-10,26/0,0,0,1"). + * Read-only. + * @property {string} placename - The place name in your current href metaverse address + * (e.g., "DomainName"). Is blank if your hostname is an IP address. + * Read-only. + * @property {string} protocol - The protocol of your current href metaverse address (e.g., "hifi"). + * Read-only. + * + * @borrows location.handleLookupString as handleLookupString + * @borrows location.goToViewpointForPath as goToViewpointForPath + * @borrows location.goBack as goBack + * @borrows location.goForward as goForward + * @borrows location.goToLocalSandbox as goToLocalSandbox + * @borrows location.goToEntry as goToEntry + * @borrows location.goToUser as goToUser + * @borrows location.goToLastAddress as goToLastAddress + * @borrows location.canGoBack as canGoBack + * @borrows location.refreshPreviousLookup as refreshPreviousLookup + * @borrows location.storeCurrentAddress as storeCurrentAddress + * @borrows location.copyAddress as copyAddress + * @borrows location.copyPath as copyPath + * @borrows location.lookupShareableNameForDomainID as lookupShareableNameForDomainID + * + * @borrows location.lookupResultsFinished as lookupResultsFinished + * @borrows location.lookupResultIsOffline as lookupResultIsOffline + * @borrows location.lookupResultIsNotFound as lookupResultIsNotFound + * @borrows location.possibleDomainChangeRequired as possibleDomainChangeRequired + * @borrows location.locationChangeRequired as locationChangeRequired + * @borrows location.possibleDomainChangeRequiredViaICEForID as possibleDomainChangeRequiredViaICEForID + * @borrows location.pathChangeRequired as pathChangeRequired + * @borrows location.hostChanged as hostChanged + * @borrows location.goBackPossible as goBackPossible + * @borrows location.goForwardPossible as goForwardPossible + */ + class AddressManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 4213d92fc0..35261298b9 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -91,10 +91,11 @@ private: class ScriptableResource : public QObject { /**jsdoc - * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link ModelCache.prefetch}, - * {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}. + * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link MaterialCache.prefetch}, + * {@link ModelCache.prefetch}, {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}. * * @class ResourceObject + * @hideconstructor * * @hifi-interface * @hifi-client-entity @@ -318,9 +319,11 @@ class ScriptableResourceCache : public QObject { Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) /**jsdoc - * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource managers). Read-only. - * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). Read-only. - */ + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. + */ Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty) Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty) @@ -332,7 +335,7 @@ public: * @function ResourceCache.getResourceList * @returns {string[]} The URLs of all resources in the cache. * @example Report cached resources. - * // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate. + * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate. * * var cachedResources = AnimationCache.getResourceList(); * print("Cached resources: " + JSON.stringify(cachedResources)); @@ -352,7 +355,7 @@ public: * @param {string} url - The URL of the resource to prefetch. * @returns {ResourceObject} A resource object. * @example Prefetch a resource and wait until it has loaded. - * // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate. + * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate. * // TextureCache has its own version of this function. * * var resourceURL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx"; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index fbf575065e..805e5d3a09 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -135,6 +135,7 @@ public: AudioSoloRequest, BulkAvatarTraitsAck, StopInjector, + AvatarZonePresence, NUM_PACKET_TYPE }; @@ -185,7 +186,8 @@ public: << PacketTypeEnum::Value::OctreeFileReplacement << PacketTypeEnum::Value::ReplicatedMicrophoneAudioNoEcho << PacketTypeEnum::Value::ReplicatedMicrophoneAudioWithEcho << PacketTypeEnum::Value::ReplicatedInjectAudio << PacketTypeEnum::Value::ReplicatedSilentAudioFrame << PacketTypeEnum::Value::ReplicatedAvatarIdentity - << PacketTypeEnum::Value::ReplicatedKillAvatar << PacketTypeEnum::Value::ReplicatedBulkAvatarData; + << PacketTypeEnum::Value::ReplicatedKillAvatar << PacketTypeEnum::Value::ReplicatedBulkAvatarData + << PacketTypeEnum::Value::AvatarZonePresence; return NON_SOURCED_PACKETS; } @@ -275,6 +277,7 @@ enum class EntityVersion : PacketVersion { ShadowBiasAndDistance, TextEntityFonts, ScriptServerKinematicMotion, + ScreenshareZone, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index e48f0603bd..f3d129871f 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -62,7 +62,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - if (_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + if (_entity->isAvatarEntity() && !_entity->isMyAvatarEntity()) { // avatar entities are always thus, so we cache this fact in _ownershipState _ownershipState = EntityMotionState::OwnershipState::Unownable; } @@ -407,8 +407,8 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // 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. - // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor - assert(!(_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); + // this case is prevented by setting _ownershipState to OwnershipState::Unownable in EntityMotionState::ctor + assert(!(_entity->isAvatarEntity() && !_entity->isMyAvatarEntity())); if (_entity->getTransitingWithAvatar()) { return false; @@ -768,7 +768,7 @@ uint8_t EntityMotionState::computeFinalBidPriority() const { } bool EntityMotionState::isLocallyOwned() const { - return _entity->getSimulatorID() == Physics::getSessionUUID(); + return _entity->getSimulatorID() == Physics::getSessionUUID() || _entity->isMyAvatarEntity(); } bool EntityMotionState::isLocallyOwnedOrShouldBe() const { @@ -786,13 +786,21 @@ void EntityMotionState::setRegion(uint8_t region) { } void EntityMotionState::initForBid() { - assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); - _ownershipState = EntityMotionState::OwnershipState::PendingBid; + if (_ownershipState != EntityMotionState::OwnershipState::Unownable) { + _ownershipState = EntityMotionState::OwnershipState::PendingBid; + } } void EntityMotionState::initForOwned() { - assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); - _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; + if (_ownershipState != EntityMotionState::OwnershipState::Unownable) { + _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; + } +} + +void EntityMotionState::clearOwnershipState() { + if (_ownershipState != OwnershipState::Unownable) { + _ownershipState = OwnershipState::NotLocallyOwned; + } } void EntityMotionState::clearObjectVelocities() const { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 7456837777..be6f5c0658 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -107,7 +107,7 @@ protected: uint64_t getNextBidExpiry() const { return _nextBidExpiry; } void initForBid(); void initForOwned(); - void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; } + void clearOwnershipState(); void updateServerPhysicsVariables(); bool remoteSimulationOutOfSync(uint32_t simulationStep); diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index df8c3fa32e..4a2ee9184f 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -40,14 +40,9 @@ void PhysicalEntitySimulation::init( } // begin EntitySimulation overrides -void PhysicalEntitySimulation::updateEntitiesInternal(uint64_t now) { - // Do nothing here because the "internal" update the PhysicsEngine::stepSimulation() which is done elsewhere. -} - -void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - assert(entity); - assert(!entity->isDead()); +void PhysicalEntitySimulation::addEntityToInternalLists(EntityItemPointer entity) { + EntitySimulation::addEntityToInternalLists(entity); + entity->deserializeActions(); // TODO: do this elsewhere uint8_t region = _space->getRegion(entity->getSpaceIndex()); bool maybeShouldBePhysical = (region < workload::Region::R3 || region == workload::Region::UNKNOWN) && entity->shouldBePhysical(); bool canBeKinematic = region <= workload::Region::R3; @@ -66,23 +61,20 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { } } -void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { - if (entity->isSimulated()) { - EntitySimulation::removeEntityInternal(entity); - _entitiesToAddToPhysics.remove(entity); - - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - removeOwnershipData(motionState); - _entitiesToRemoveFromPhysics.insert(entity); - } - if (entity->isDead() && entity->getElement()) { - _deadEntities.insert(entity); - } +void PhysicalEntitySimulation::removeEntityFromInternalLists(EntityItemPointer entity) { + _entitiesToAddToPhysics.remove(entity); + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + removeOwnershipData(motionState); + _entitiesToRemoveFromPhysics.insert(entity); + } + if (entity->isDead() && entity->getElement()) { + _deadEntitiesToRemoveFromTree.insert(entity); } if (entity->isAvatarEntity()) { _deadAvatarEntities.insert(entity); } + EntitySimulation::removeEntityFromInternalLists(entity); } void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { @@ -115,18 +107,6 @@ void PhysicalEntitySimulation::clearOwnershipData() { _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); - } - } - _deadEntities.swap(deadEntities); - _deadEntities.clear(); -} - void PhysicalEntitySimulation::takeDeadAvatarEntities(SetOfEntities& deadEntities) { _deadAvatarEntities.swap(deadEntities); _deadAvatarEntities.clear(); @@ -190,11 +170,43 @@ void PhysicalEntitySimulation::processChangedEntity(const EntityItemPointer& ent } } -void PhysicalEntitySimulation::clearEntitiesInternal() { +void PhysicalEntitySimulation::processDeadEntities() { + // Note: this override is a complete rewite of the base class's method because we cannot assume all entities + // are domain entities, and the consequence of trying to delete a domain-entity in this case is very different. + if (_deadEntitiesToRemoveFromTree.empty()) { + return; + } + PROFILE_RANGE(simulation_physics, "Deletes"); + std::vector entitiesToDeleteImmediately; + entitiesToDeleteImmediately.reserve(_deadEntitiesToRemoveFromTree.size()); + QUuid sessionID = Physics::getSessionUUID(); + QMutexLocker lock(&_mutex); + for (auto entity : _deadEntitiesToRemoveFromTree) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + _entitiesToRemoveFromPhysics.insert(entity); + } + if (entity->isDomainEntity()) { + // interface-client can't delete domainEntities outright, they must roundtrip through the entity-server + _entityPacketSender->queueEraseEntityMessage(entity->getID()); + } else if (entity->isLocalEntity() || entity->isMyAvatarEntity()) { + entitiesToDeleteImmediately.push_back(entity); + entity->collectChildrenForDelete(entitiesToDeleteImmediately, sessionID); + } + } + _deadEntitiesToRemoveFromTree.clear(); + + if (!entitiesToDeleteImmediately.empty()) { + getEntityTree()->deleteEntitiesByPointer(entitiesToDeleteImmediately); + } +} + +void PhysicalEntitySimulation::clearEntities() { // TODO: we should probably wait to lock the _physicsEngine so we don't mess up data structures // 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... + QMutexLocker lock(&_mutex); // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); @@ -216,11 +228,20 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { _entitiesToAddToPhysics.clear(); _incomingChanges.clear(); _entitiesToDeleteLater.clear(); + + EntitySimulation::clearEntities(); +} + +void PhysicalEntitySimulation::queueEraseDomainEntity(const QUuid& id) const { + if (_entityPacketSender) { + _entityPacketSender->queueEraseEntityMessage(id); + } } // virtual void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { - // this can be called on any thread + // DANGER! this can be called on any thread + // do no dirty deeds here --> assemble list for later assert(entity); assert(entity->isDead()); QMutexLocker lock(&_mutex); @@ -228,11 +249,11 @@ void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) } void PhysicalEntitySimulation::removeDeadEntities() { - // only ever call this on the main thread + // DANGER! only ever call this on the main thread QMutexLocker lock(&_mutex); for (auto& entity : _entitiesToDeleteLater) { entity->clearActions(getThisPointer()); - removeEntityInternal(entity); + EntitySimulation::prepareEntityForDelete(entity); } _entitiesToDeleteLater.clear(); } @@ -647,10 +668,16 @@ void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) { "dynamic that was already in _physicsEngine"; } } - EntitySimulation::addDynamic(dynamic); + QMutexLocker lock(&_dynamicsMutex); + _dynamicsToAdd += dynamic; } } +void PhysicalEntitySimulation::removeDynamic(const QUuid dynamicID) { + QMutexLocker lock(&_dynamicsMutex); + _dynamicsToRemove += dynamicID; +} + void PhysicalEntitySimulation::applyDynamicChanges() { QList dynamicsFailedToAdd; if (_physicsEngine) { @@ -665,8 +692,8 @@ void PhysicalEntitySimulation::applyDynamicChanges() { } } } - // applyDynamicChanges will clear _dynamicsToRemove and _dynamicsToAdd - EntitySimulation::applyDynamicChanges(); + _dynamicsToAdd.clear(); + _dynamicsToRemove.clear(); } // put back the ones that couldn't yet be added diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index f5213f7fef..0f0a8e9295 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -58,22 +59,24 @@ public: void init(EntityTreePointer tree, PhysicsEnginePointer engine, EntityEditPacketSender* packetSender); void setWorkloadSpace(const workload::SpacePointer space) { _space = space; } - virtual void addDynamic(EntityDynamicPointer dynamic) override; - virtual void applyDynamicChanges() override; + void addDynamic(EntityDynamicPointer dynamic) override; + void removeDynamic(const QUuid dynamicID) override; + void applyDynamicChanges() override; - virtual void takeDeadEntities(SetOfEntities& deadEntities) override; void takeDeadAvatarEntities(SetOfEntities& deadEntities); + virtual void clearEntities() override; + void queueEraseDomainEntity(const QUuid& id) const override; + signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); protected: // only called by EntitySimulation // overrides for EntitySimulation - virtual void updateEntitiesInternal(uint64_t now) override; - virtual void addEntityInternal(EntityItemPointer entity) override; - virtual void removeEntityInternal(EntityItemPointer entity) override; + void addEntityToInternalLists(EntityItemPointer entity) override; + void removeEntityFromInternalLists(EntityItemPointer entity) override; void processChangedEntity(const EntityItemPointer& entity) override; - virtual void clearEntitiesInternal() override; + void processDeadEntities() override; void removeOwnershipData(EntityMotionState* motionState); void clearOwnershipData(); @@ -121,8 +124,13 @@ private: VectorOfEntityMotionStates _owned; VectorOfEntityMotionStates _bids; - SetOfEntities _deadAvatarEntities; + SetOfEntities _deadAvatarEntities; // to remove from Avatar's lists std::vector _entitiesToDeleteLater; + + QList _dynamicsToAdd; + QSet _dynamicsToRemove; + QMutex _dynamicsMutex { QMutex::Recursive }; + workload::SpacePointer _space; uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index ef5213df8f..43c6fc27dc 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -109,7 +109,7 @@ btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { glm::vec3 center = points[0]; glm::vec3 maxCorner = center; glm::vec3 minCorner = center; - for (int i = 1; i < points.size(); i++) { + for (size_t i = 1; i < points.size(); i++) { center += points[i]; maxCorner = glm::max(maxCorner, points[i]); minCorner = glm::min(minCorner, points[i]); @@ -149,7 +149,7 @@ btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { // add the points, correcting for margin glm::vec3 relativeScale = (diagonal - glm::vec3(2.0f * margin)) / diagonal; glm::vec3 correctedPoint; - for (int i = 0; i < points.size(); ++i) { + for (size_t i = 0; i < points.size(); ++i) { correctedPoint = (points[i] - center) * relativeScale + center; hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); } @@ -217,7 +217,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { } const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); - int32_t numIndices = triangleIndices.size(); + int32_t numIndices = (int32_t)triangleIndices.size(); if (numIndices < 3) { // not enough indices to make a single triangle return nullptr; @@ -237,7 +237,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { mesh.m_indexType = PHY_INTEGER; mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t); } - mesh.m_numVertices = pointList.size(); + mesh.m_numVertices = (int)pointList.size(); mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices]; mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar); mesh.m_vertexType = PHY_FLOAT; @@ -362,7 +362,7 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); uint32_t numSubShapes = info.getNumSubShapes(); if (numSubShapes == 1) { - if (!pointCollection.isEmpty()) { + if (!pointCollection.empty()) { shape = createConvexHull(pointCollection[0]); } } else { @@ -380,7 +380,7 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) case SHAPE_TYPE_SIMPLE_COMPOUND: { const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); - uint32_t numIndices = triangleIndices.size(); + uint32_t numIndices = (uint32_t)triangleIndices.size(); uint32_t numMeshes = info.getNumSubShapes(); const uint32_t MIN_NUM_SIMPLE_COMPOUND_INDICES = 2; // END_OF_MESH_PART + END_OF_MESH if (numMeshes > 0 && numIndices > MIN_NUM_SIMPLE_COMPOUND_INDICES) { diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index e4257140fd..9e6c5f9898 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -93,7 +93,7 @@ public: * collisions. */ - /**jsdoc + /**jsdoc *

A type of pick.

* * diff --git a/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h b/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h index 3a13652aec..2e0d319b7a 100644 --- a/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h +++ b/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h @@ -24,7 +24,7 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D // Properties are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc - * The TextureCache API manages texture cache resources. + * The MaterialCache API manages material cache resources. * * @namespace MaterialCache * @@ -36,6 +36,10 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 89f21218e6..9b3d0a9bd4 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -27,7 +27,6 @@ using UniformLambdas = std::list>; const size_t MAX_PROCEDURAL_TEXTURE_CHANNELS{ 4 }; /**jsdoc - * An object containing user-defined uniforms for communicating data to shaders. * @typedef {object} ProceduralUniforms */ diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp index a97cb294b4..130f11f3ef 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp @@ -113,9 +113,9 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater /**jsdoc * A material used in a {@link Entities.MaterialResource|MaterialResource}. * @typedef {object} Entities.Material + * @property {string} name="" - A name for the material. Supported by all material models. * @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes. * Supported models are: "hifi_pbr", "hifi_shader_simple". - * @property {string} name="" - A name for the material. Supported by all material models. * @property {ColorFloat|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A * {@link ColorFloat} value is treated as sRGB and must have component values in the range 0.0 – * 1.0. A {@link RGBS} value can be either RGB or sRGB. @@ -144,12 +144,24 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater * value for transparency. * "hifi_pbr" model only. * @property {string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be: - * "OPACITY_MAP_OPAQUE" for ignoring the opacity map information. - * "OPACITY_MAP_MASK" for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque. - * "OPACITY_MAP_BLEND" for using the opacity map for alpha blending the material surface with the background. + *
    + *
  • "OPACITY_MAP_OPAQUE" for ignoring the opacity map information.
  • + *
  • "OPACITY_MAP_MASK" for using the opacityMap as a mask, where only the texel greater + * than opacityCutoff are visible and rendered opaque.
  • + *
  • "OPACITY_MAP_BLEND" for using the opacityMap for alpha blending the material surface + * with the background.
  • + *
* Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. - * @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the Opacity map - * when opacityMapMode is "OPACITY_MAP_MASK", range 0.01.0. + * @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the + * opacityMap when opacityMapMode is "OPACITY_MAP_MASK". Range 0.0 + * – 1.0. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} cullFaceMode="CULL_BACK" - The mode defining which side of the geometry should be rendered. Values can be: + *
    + *
  • "CULL_NONE" to render both sides of the geometry.
  • + *
  • "CULL_FRONT" to cull the front faces of the geometry.
  • + *
  • "CULL_BACK" (the default) to cull the back faces of the geometry.
  • + *
* Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. * @property {string} cullFaceMode - The mode defining which side of the geometry should be rendered. Values can be: *
    @@ -186,7 +198,7 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. * @property {Mat4|string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. - * @property {string} lightmapParams - Parameters for controlling how lightMap is used. + * @property {string} lightmapParams - Parameters for controlling how lightMap is used. * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. *

    Currently not used.

    * @property {string} materialParams - Parameters for controlling the material projection and repetition. diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.h b/libraries/procedural/src/procedural/ProceduralMaterialCache.h index 0a44ccf0ef..bf4b5191c2 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.h +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.h @@ -50,7 +50,7 @@ public: Textures getTextures() { return _textures; } protected: - friend class Geometry; + friend class NetworkModel; Textures _textures; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 7f42b05fa4..4c2c487193 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -32,16 +32,11 @@ bool CauterizedModel::updateGeometry() { bool needsFullUpdate = Model::updateGeometry(); if (_isCauterized && needsFullUpdate) { assert(_cauterizeMeshStates.empty()); - const HFMModel& hfmModel = getHFMModel(); - foreach (const HFMMesh& mesh, hfmModel.meshes) { - Model::MeshState state; - if (_useDualQuaternionSkinning) { - state.clusterDualQuaternions.resize(mesh.clusters.size()); - _cauterizeMeshStates.append(state); - } else { - state.clusterMatrices.resize(mesh.clusters.size()); - _cauterizeMeshStates.append(state); - } + + // initialize the cauterizedDeforemrStates as a copy of the standard deformerStates + _cauterizeMeshStates.resize(_meshStates.size()); + for (int i = 0; i < (int) _meshStates.size(); ++i) { + _cauterizeMeshStates[i] = _meshStates[i]; } } return needsFullUpdate; @@ -50,20 +45,12 @@ bool CauterizedModel::updateGeometry() { void CauterizedModel::createRenderItemSet() { if (_isCauterized) { assert(isLoaded()); - const auto& meshes = _renderGeometry->getMeshes(); - - // all of our mesh vectors must match in size - if (meshes.size() != _meshStates.size()) { - qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; - return; - } // We should not have any existing renderItems if we enter this section of code Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); _modelMeshMaterialNames.clear(); - _modelMeshRenderItemShapes.clear(); Transform transform; transform.setTranslation(_translation); @@ -73,25 +60,17 @@ void CauterizedModel::createRenderItemSet() { offset.setScale(_scale); offset.postTranslate(_offset); - // Run through all of the meshes, and place them into their segregated, but unsorted buckets - int shapeID = 0; - uint32_t numMeshes = (uint32_t)meshes.size(); - for (uint32_t i = 0; i < numMeshes; i++) { - const auto& mesh = meshes.at(i); - if (!mesh) { - continue; - } + Transform::mult(transform, transform, offset); - // Create the render payloads - int numParts = (int)mesh->getNumParts(); - for (int partIndex = 0; partIndex < numParts; partIndex++) { - auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset, _created); - _modelMeshRenderItems << std::static_pointer_cast(ptr); - auto material = getGeometry()->getShapeMaterial(shapeID); - _modelMeshMaterialNames.push_back(material ? material->getName() : ""); - _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); - shapeID++; - } + // Run through all of the meshes, and place them into their segregated, but unsorted buckets + const auto& shapes = _renderGeometry->getHFMModel().shapes; + for (int shapeID = 0; shapeID < (int) shapes.size(); shapeID++) { + const auto& shape = shapes[shapeID]; + + _modelMeshRenderItems << std::make_shared(shared_from_this(), shape.mesh, shape.meshPart, shapeID, transform, offset, _created); + + auto material = getNetworkModel()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); } } else { Model::createRenderItemSet(); @@ -104,28 +83,26 @@ void CauterizedModel::updateClusterMatrices() { if (!_needsUpdateClusterMatrices || !isLoaded()) { return; } + + updateShapeStatesFromRig(); + _needsUpdateClusterMatrices = false; - const HFMModel& hfmModel = getHFMModel(); - for (int i = 0; i < (int)_meshStates.size(); i++) { - Model::MeshState& state = _meshStates[i]; - const HFMMesh& mesh = hfmModel.meshes.at(i); - int meshIndex = i; - - for (int j = 0; j < mesh.clusters.size(); j++) { - const HFMCluster& cluster = mesh.clusters.at(j); - int clusterIndex = j; + for (int skinDeformerIndex = 0; skinDeformerIndex < (int)_meshStates.size(); skinDeformerIndex++) { + MeshState& state = _meshStates[skinDeformerIndex]; + auto numClusters = state.getNumClusters(); + for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { + const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); if (_useDualQuaternionSkinning) { - auto jointPose = _rig.getJointPose(cluster.jointIndex); + auto jointPose = _rig.getJointPose(cbmov.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); - state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); - state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans()); + Transform::mult(clusterTransform, jointTransform, cbmov.inverseBindTransform); + state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); } else { - auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); + auto jointMatrix = _rig.getJointTransform(cbmov.jointIndex); + glm_mat4u_mul(jointMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); } } } @@ -135,6 +112,7 @@ void CauterizedModel::updateClusterMatrices() { AnimPose cauterizePose = _rig.getJointPose(_rig.indexOfJoint("Neck")); cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); + Transform cauterizedDQTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); static const glm::mat4 zeroScale( glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f), @@ -143,32 +121,29 @@ void CauterizedModel::updateClusterMatrices() { glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); auto cauterizeMatrix = _rig.getJointTransform(_rig.indexOfJoint("Neck")) * zeroScale; - for (int i = 0; i < _cauterizeMeshStates.size(); i++) { - Model::MeshState& state = _cauterizeMeshStates[i]; - const HFMMesh& mesh = hfmModel.meshes.at(i); - int meshIndex = i; + for (int skinDeformerIndex = 0; skinDeformerIndex < (int) _cauterizeMeshStates.size(); skinDeformerIndex++) { + Model::MeshState& nonCauterizedState = _meshStates[skinDeformerIndex]; + Model::MeshState& state = _cauterizeMeshStates[skinDeformerIndex]; - for (int j = 0; j < mesh.clusters.size(); j++) { - const HFMCluster& cluster = mesh.clusters.at(j); - int clusterIndex = j; - - if (_useDualQuaternionSkinning) { - if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { - // not cauterized so just copy the value from the non-cauterized version. - state.clusterDualQuaternions[j] = _meshStates[i].clusterDualQuaternions[j]; - } else { - Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); + // Just reset cauterized state with normal state memcpy style + if (_useDualQuaternionSkinning) { + state.clusterDualQuaternions = nonCauterizedState.clusterDualQuaternions; + } else { + state.clusterMatrices = nonCauterizedState.clusterMatrices; + } + + // ANd only cauterize affected joints + auto numClusters = state.getNumClusters(); + for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { + const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); + if (_cauterizeBoneSet.find(cbmov.jointIndex) != _cauterizeBoneSet.end()) { + if (_useDualQuaternionSkinning) { Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); - state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); - state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans()); - } - } else { - if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { - // not cauterized so just copy the value from the non-cauterized version. - state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j]; + Transform::mult(clusterTransform, cauterizedDQTransform, cbmov.inverseBindTransform); + state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); + state.clusterDualQuaternions[clusterIndex].setCauterizationParameters(1.0f, cauterizePose.trans()); } else { - glm_mat4u_mul(cauterizeMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(cauterizeMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); } } } @@ -177,7 +152,7 @@ void CauterizedModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (modelBlender->shouldComputeBlendshapes() && hfmModel.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (modelBlender->shouldComputeBlendshapes() && getHFMModel().hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } @@ -217,65 +192,60 @@ void CauterizedModel::updateRenderItems() { render::Transaction transaction; for (int i = 0; i < (int)self->_modelMeshRenderItemIDs.size(); i++) { - auto itemID = self->_modelMeshRenderItemIDs[i]; - auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - const auto& meshState = self->getMeshState(meshIndex); - const auto& cauterizedMeshState = self->getCauterizeMeshState(meshIndex); + const auto& shapeState = self->getShapeState(i); - bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + auto skinDeformerIndex = shapeState._skinDeformerIndex; + + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(shapeState._meshIndex); bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); - transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, - primitiveMode, renderItemKeyGlobalFlags, enableCauterization](ModelMeshPartPayload& mmppData) { - CauterizedMeshPartPayload& data = static_cast(mmppData); - if (useDualQuaternionSkinning) { - data.updateClusterBuffer(meshState.clusterDualQuaternions, - cauterizedMeshState.clusterDualQuaternions); - data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); - } else { - data.updateClusterBuffer(meshState.clusterMatrices, - cauterizedMeshState.clusterMatrices); - data.computeAdjustedLocalBound(meshState.clusterMatrices); - } + if (skinDeformerIndex != hfm::UNDEFINED_KEY) { - Transform renderTransform = modelTransform; - if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { - const auto& dq = meshState.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = modelTransform.worldTransform(transform); - } - } else { - if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { - renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); - } - } - data.updateTransformForSkinnedMesh(renderTransform, modelTransform); + const auto& meshState = self->getMeshState(skinDeformerIndex); + const auto& cauterizedMeshState = self->getCauterizeMeshState(skinDeformerIndex); - renderTransform = modelTransform; - if (useDualQuaternionSkinning) { - if (cauterizedMeshState.clusterDualQuaternions.size() == 1 || cauterizedMeshState.clusterDualQuaternions.size() == 2) { - const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = modelTransform.worldTransform(Transform(transform)); + transaction.updateItem(itemID, + [modelTransform, shapeState, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, + primitiveMode, renderItemKeyGlobalFlags, enableCauterization](ModelMeshPartPayload& mmppData) { + CauterizedMeshPartPayload& data = static_cast(mmppData); + if (useDualQuaternionSkinning) { + data.updateClusterBuffer(meshState.clusterDualQuaternions, cauterizedMeshState.clusterDualQuaternions); + } else { + data.updateClusterBuffer(meshState.clusterMatrices, cauterizedMeshState.clusterMatrices); } - } else { - if (cauterizedMeshState.clusterMatrices.size() == 1 || cauterizedMeshState.clusterMatrices.size() == 2) { - renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0])); - } - } - data.updateTransformForCauterizedMesh(renderTransform); - data.setEnableCauterization(enableCauterization); - data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); - }); + Transform renderTransform = modelTransform; + // if (meshState.clusterMatrices.size() <= 2) { + // renderTransform = modelTransform.worldTransform(shapeState._rootFromJointTransform); + // } + data.updateTransform(renderTransform); + data.updateTransformForCauterizedMesh(renderTransform); + data.updateTransformAndBound(modelTransform.worldTransform(shapeState._rootFromJointTransform)); + + data.setEnableCauterization(enableCauterization); + data.updateKey(renderItemKeyGlobalFlags); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); + }); + } else { + transaction.updateItem(itemID, + [modelTransform, shapeState, invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, enableCauterization] + (ModelMeshPartPayload& mmppData) { + CauterizedMeshPartPayload& data = static_cast(mmppData); + + Transform renderTransform = modelTransform; + + renderTransform = modelTransform.worldTransform(shapeState._rootFromJointTransform); + data.updateTransform(renderTransform); + data.updateTransformForCauterizedMesh(renderTransform); + + data.setEnableCauterization(enableCauterization); + data.updateKey(renderItemKeyGlobalFlags); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, false); + }); + + } } scene->enqueueTransaction(transaction); diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index 36a96fb006..7d241d7ac6 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -40,7 +40,7 @@ public: protected: std::unordered_set _cauterizeBoneSet; - QVector _cauterizeMeshStates; + std::vector _cauterizeMeshStates; bool _isCauterized { false }; bool _enableCauterization { false }; }; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index ea66ac19ec..e0d23535e4 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -116,7 +116,7 @@ static const uint SHAPE_TANGENT_OFFSET = offsetof(GeometryCache::ShapeVertex, ta std::map, gpu::PipelinePointer> GeometryCache::_webPipelines; std::map, gpu::PipelinePointer> GeometryCache::_gridPipelines; -void GeometryCache::computeSimpleHullPointListForShape(const int entityShape, const glm::vec3 &entityExtents, QVector &outPointList) { +void GeometryCache::computeSimpleHullPointListForShape(const int entityShape, const glm::vec3 &entityExtents, ShapeInfo::PointList &outPointList) { auto geometryCache = DependencyManager::get(); const GeometryCache::Shape geometryShape = GeometryCache::getShapeForEntityShape( entityShape ); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 179d49c076..03865c6cc7 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -155,7 +155,7 @@ public: static GeometryCache::Shape getShapeForEntityShape(int entityShapeEnum); static QString stringFromShape(GeometryCache::Shape geoShape); - static void computeSimpleHullPointListForShape(int entityShape, const glm::vec3 &entityExtents, QVector &outPointList); + static void computeSimpleHullPointListForShape(int entityShape, const glm::vec3 &entityExtents, ShapeInfo::PointList &outPointList); int allocateID() { return _nextID++; } void releaseID(int id); diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 938efbef52..5a8b09b018 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -193,8 +193,8 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c ShapeKey::Builder().withDeformed(), ShapeKey::Builder().withDeformed().withFade(), ShapeKey::Builder().withDeformed().withDualQuatSkinned(), ShapeKey::Builder().withDeformed().withDualQuatSkinned().withFade(), ShapeKey::Builder().withOwnPipeline(), ShapeKey::Builder().withOwnPipeline().withFade(), - ShapeKey::Builder().withOwnPipeline().withDeformed(), ShapeKey::Builder().withOwnPipeline().withDeformed().withFade(), - ShapeKey::Builder().withOwnPipeline().withDeformed().withDualQuatSkinned(), ShapeKey::Builder().withOwnPipeline().withDeformed().withDualQuatSkinned().withFade(), + ShapeKey::Builder().withDeformed().withOwnPipeline(), ShapeKey::Builder().withDeformed().withOwnPipeline().withFade(), + ShapeKey::Builder().withDeformed().withDualQuatSkinned().withOwnPipeline(), ShapeKey::Builder().withDeformed().withDualQuatSkinned().withOwnPipeline().withFade(), }; std::vector> sortedShapeKeys(keys.size()); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 544947ab59..755ae48d58 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -74,11 +74,15 @@ void MeshPartPayload::updateMeshPart(const std::shared_ptr } } -void MeshPartPayload::updateTransform(const Transform& transform, const Transform& offsetTransform) { - _transform = transform; - Transform::mult(_drawTransform, _transform, offsetTransform); +void MeshPartPayload::updateTransform(const Transform& transform) { + _worldFromLocalTransform = transform; _worldBound = _localBound; - _worldBound.transform(_drawTransform); + _worldBound.transform(_worldFromLocalTransform); +} + +void MeshPartPayload::updateTransformAndBound(const Transform& transform) { + _worldBound = _localBound; + _worldBound.transform(transform); } void MeshPartPayload::addMaterial(graphics::MaterialLayer material) { @@ -168,7 +172,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { } void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { - batch.setModelTransform(_drawTransform); + batch.setModelTransform(_worldFromLocalTransform); } @@ -196,7 +200,7 @@ void MeshPartPayload::render(RenderArgs* args) { auto& schema = _drawMaterials.getSchemaBuffer().get(); glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity); outColor = procedural->getColor(outColor); - procedural->prepare(batch, _drawTransform.getTranslation(), _drawTransform.getScale(), _drawTransform.getRotation(), _created, + procedural->prepare(batch, _worldFromLocalTransform.getTranslation(), _worldFromLocalTransform.getScale(), _worldFromLocalTransform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f)); batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); } else { @@ -251,36 +255,21 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in assert(model && model->isLoaded()); - bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); + auto shape = model->getHFMModel().shapes[shapeIndex]; + assert(shape.mesh == meshIndex); + assert(shape.meshPart == partIndex); - auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); + auto& modelMesh = model->getNetworkModel()->getMeshes().at(_meshIndex); _meshNumVertices = (int)modelMesh->getNumVertices(); - const Model::MeshState& state = model->getMeshState(_meshIndex); updateMeshPart(modelMesh, partIndex); - if (useDualQuaternionSkinning) { - computeAdjustedLocalBound(state.clusterDualQuaternions); - } else { - computeAdjustedLocalBound(state.clusterMatrices); - } + Transform renderTransform = transform; + const Model::ShapeState& shapeState = model->getShapeState(shapeIndex); + renderTransform = transform.worldTransform(shapeState._rootFromJointTransform); + updateTransform(renderTransform); - updateTransform(transform, offsetTransform); - Transform renderTransform = transform; - if (useDualQuaternionSkinning) { - if (state.clusterDualQuaternions.size() == 1) { - const auto& dq = state.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = transform.worldTransform(Transform(transform)); - } - } else { - if (state.clusterMatrices.size() == 1) { - renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0])); - } - } - updateTransformForSkinnedMesh(renderTransform, transform); + _deformerIndex = shape.skinDeformer; initCache(model); @@ -304,7 +293,9 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { if (_drawMesh) { auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); - _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); + if (_deformerIndex != hfm::UNDEFINED_KEY) { + _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); + } const HFMModel& hfmModel = model->getHFMModel(); const HFMMesh& mesh = hfmModel.meshes.at(_meshIndex); @@ -313,7 +304,7 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { _hasTangents = !mesh.tangents.isEmpty(); } - auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); + auto networkMaterial = model->getNetworkModel()->getShapeMaterial(_shapeID); if (networkMaterial) { addMaterial(graphics::MaterialLayer(networkMaterial, 0)); } @@ -363,12 +354,6 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector(); glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity); outColor = procedural->getColor(outColor); - procedural->prepare(batch, _drawTransform.getTranslation(), _drawTransform.getScale(), _drawTransform.getRotation(), _created, + procedural->prepare(batch, _worldFromLocalTransform.getTranslation(), _worldFromLocalTransform.getScale(), _worldFromLocalTransform.getRotation(), _created, ProceduralProgramKey(outColor.a < 1.0f, _shapeKey.isDeformed(), _shapeKey.isDualQuatSkinned())); batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); } else { @@ -521,38 +506,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) { args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } -void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { - _adjustedLocalBound = _localBound; - if (clusterMatrices.size() > 0) { - _adjustedLocalBound.transform(clusterMatrices.back()); - - for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) { - AABox clusterBound = _localBound; - clusterBound.transform(clusterMatrices[i]); - _adjustedLocalBound += clusterBound; - } - } -} - -void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions) { - _adjustedLocalBound = _localBound; - if (clusterDualQuaternions.size() > 0) { - Transform rootTransform(clusterDualQuaternions.back().getRotation(), - clusterDualQuaternions.back().getScale(), - clusterDualQuaternions.back().getTranslation()); - _adjustedLocalBound.transform(rootTransform); - - for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) { - AABox clusterBound = _localBound; - Transform transform(clusterDualQuaternions[i].getRotation(), - clusterDualQuaternions[i].getScale(), - clusterDualQuaternions[i].getTranslation()); - clusterBound.transform(transform); - _adjustedLocalBound += clusterBound; - } - } -} - void ModelMeshPartPayload::setBlendshapeBuffer(const std::unordered_map& blendshapeBuffers, const QVector& blendedMeshSizes) { if (_meshIndex < blendedMeshSizes.length() && blendedMeshSizes.at(_meshIndex) == _meshNumVertices) { auto blendshapeBuffer = blendshapeBuffers.find(_meshIndex); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index ee205bd778..92dbae2e01 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -38,7 +38,8 @@ public: virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); virtual void notifyLocationChanged() {} - void updateTransform(const Transform& transform, const Transform& offsetTransform); + void updateTransform(const Transform& transform); + void updateTransformAndBound(const Transform& transform ); // Render Item interface virtual render::ItemKey getKey() const; @@ -52,13 +53,11 @@ public: virtual void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const; // Payload resource cached values - Transform _drawTransform; - Transform _transform; + Transform _worldFromLocalTransform; int _partIndex = 0; bool _hasColorAttrib { false }; graphics::Box _localBound; - graphics::Box _adjustedLocalBound; mutable graphics::Box _worldBound; std::shared_ptr _drawMesh; @@ -106,7 +105,6 @@ public: // dual quaternion skinning void updateClusterBuffer(const std::vector& clusterDualQuaternions); - void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); // Render Item interface render::ShapeKey getShapeKey() const override; @@ -119,12 +117,6 @@ public: void bindMesh(gpu::Batch& batch) override; void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override; - // matrix palette skinning - void computeAdjustedLocalBound(const std::vector& clusterMatrices); - - // dual quaternion skinning - void computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); - gpu::BufferPointer _clusterBuffer; enum class ClusterBufferType { Matrices, DualQuaternions }; @@ -132,6 +124,7 @@ public: int _meshIndex; int _shapeID; + uint32_t _deformerIndex; bool _isSkinned{ false }; bool _isBlendShaped { false }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d3f2a04d24..7a371af202 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -44,7 +44,7 @@ using namespace std; int nakedModelPointerTypeId = qRegisterMetaType(); -int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); +int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); int vec3VectorTypeId = qRegisterMetaType>(); int normalTypeVecTypeId = qRegisterMetaType>("QVector"); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; @@ -74,7 +74,7 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride, uint setSnapModelToRegistrationPoint(true, glm::vec3(0.5f)); - connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished); + connect(&_renderWatcher, &ModelResourceWatcher::finished, this, &Model::loadURLFinished); } Model::~Model() { @@ -154,7 +154,7 @@ void Model::setOffset(const glm::vec3& offset) { } void Model::calculateTextureInfo() { - if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { + if (!_hasCalculatedTextureInfo && isLoaded() && getNetworkModel()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { size_t textureSize = 0; int textureCount = 0; bool allTexturesLoaded = true; @@ -181,15 +181,15 @@ int Model::getRenderInfoTextureCount() { } bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { - if (!getGeometry()) { + if (!getNetworkModel()) { return true; } const HFMModel& hfmModel = getHFMModel(); - const auto& networkMeshes = getGeometry()->getMeshes(); + const auto& networkMeshes = getNetworkModel()->getMeshes(); // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. - if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size() || meshIndex >= (int)_meshStates.size()) { + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size()) { _needsFixupInScene = true; // trigger remove/add cycle invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid return true; @@ -231,46 +231,45 @@ void Model::updateRenderItems() { render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { - auto itemID = self->_modelMeshRenderItemIDs[i]; - auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - const auto& meshState = self->getMeshState(meshIndex); + const auto& shapeState = self->getShapeState(i); - bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); - bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); + auto skinDeformerIndex = shapeState._skinDeformerIndex; - transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, - invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { - if (useDualQuaternionSkinning) { - data.updateClusterBuffer(meshState.clusterDualQuaternions); - data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); - } else { - data.updateClusterBuffer(meshState.clusterMatrices); - data.computeAdjustedLocalBound(meshState.clusterMatrices); - } + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(shapeState._meshIndex); - Transform renderTransform = modelTransform; + if (skinDeformerIndex != hfm::UNDEFINED_KEY) { + const auto& meshState = self->getMeshState(skinDeformerIndex); + bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); - if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) { - const auto& dq = meshState.clusterDualQuaternions[0]; - Transform transform(dq.getRotation(), - dq.getScale(), - dq.getTranslation()); - renderTransform = modelTransform.worldTransform(Transform(transform)); + transaction.updateItem(itemID, [modelTransform, shapeState, meshState, useDualQuaternionSkinning, + invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { + if (useDualQuaternionSkinning) { + data.updateClusterBuffer(meshState.clusterDualQuaternions); + } else { + data.updateClusterBuffer(meshState.clusterMatrices); } - } else { - if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) { - renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); - } - } - data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - data.setCauterized(cauterized); - data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); - }); + Transform renderTransform = modelTransform; + data.updateTransform(renderTransform); + data.updateTransformAndBound(modelTransform.worldTransform(shapeState._rootFromJointTransform)); + + data.setCauterized(cauterized); + data.updateKey(renderItemKeyGlobalFlags); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); + }); + } else { + transaction.updateItem(itemID, [modelTransform, shapeState, invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags](ModelMeshPartPayload& data) { + + Transform renderTransform = modelTransform; + renderTransform = modelTransform.worldTransform(shapeState._rootFromJointTransform); + data.updateTransform(renderTransform); + + data.updateKey(renderItemKeyGlobalFlags); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, false); + }); + } } AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); @@ -291,6 +290,15 @@ void Model::reset() { } } +void Model::updateShapeStatesFromRig() { + for (auto& shape : _shapeStates) { + uint32_t jointId = shape._jointIndex; + if (jointId < (uint32_t) _rig.getJointStateCount()) { + shape._rootFromJointTransform = _rig.getJointTransform(jointId); + } + } +} + bool Model::updateGeometry() { bool needFullUpdate = false; @@ -306,14 +314,27 @@ bool Model::updateGeometry() { assert(_meshStates.empty()); const HFMModel& hfmModel = getHFMModel(); - int i = 0; - foreach (const HFMMesh& mesh, hfmModel.meshes) { - MeshState state; - state.clusterDualQuaternions.resize(mesh.clusters.size()); - state.clusterMatrices.resize(mesh.clusters.size()); - _meshStates.push_back(state); - i++; + + const auto& shapes = hfmModel.shapes; + _shapeStates.resize(shapes.size()); + for (uint32_t s = 0; s < (uint32_t) shapes.size(); ++s) { + auto& shapeState = _shapeStates[s]; + shapeState._jointIndex = shapes[s].joint; + shapeState._meshIndex = shapes[s].mesh; + shapeState._meshPartIndex = shapes[s].meshPart; + shapeState._skinDeformerIndex = shapes[s].skinDeformer; } + updateShapeStatesFromRig(); + + const auto& hfmSkinDeformers = hfmModel.skinDeformers; + for (uint32_t i = 0; i < (uint32_t) hfmSkinDeformers.size(); i++) { + const auto& dynT = hfmSkinDeformers[i]; + MeshState state; + state.clusterDualQuaternions.resize(dynT.clusters.size()); + state.clusterMatrices.resize(dynT.clusters.size()); + _meshStates.push_back(state); + } + needFullUpdate = true; emit rigReady(); } @@ -641,8 +662,8 @@ glm::mat4 Model::getWorldToHFMMatrix() const { // TODO: deprecate and remove MeshProxyList Model::getMeshes() const { MeshProxyList result; - const Geometry::Pointer& renderGeometry = getGeometry(); - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); + const NetworkModel::Pointer& renderGeometry = getNetworkModel(); + const NetworkModel::GeometryMeshes& meshes = renderGeometry->getMeshes(); if (!isLoaded()) { return result; @@ -711,9 +732,9 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe render::Transaction transaction; for (int i = 0; i < (int) _modelMeshRenderItemIDs.size(); i++) { auto itemID = _modelMeshRenderItemIDs[i]; - auto shape = _modelMeshRenderItemShapes[i]; + auto& shape = _shapeStates[i]; // TODO: check to see if .partIndex matches too - if (shape.meshIndex == meshIndex) { + if (shape._meshIndex == (uint32_t) meshIndex) { transaction.updateItem(itemID, [=](ModelMeshPartPayload& data) { data.updateMeshPart(mesh, partIndex); }); @@ -732,7 +753,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe for (int partID = 0; partID < numParts; partID++) { HFMMeshPart part; part.triangleIndices = buffer_helpers::bufferToVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); - mesh.parts << part; + mesh.parts.push_back(part); } { foreach (const glm::vec3& vertex, mesh.vertices) { @@ -743,7 +764,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex); } } - hfmModel.meshes << mesh; + hfmModel.meshes.push_back(mesh); } calculateTriangleSets(hfmModel); } @@ -760,9 +781,9 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { } const HFMModel& hfmModel = getHFMModel(); - int numberOfMeshes = hfmModel.meshes.size(); + uint32_t numberOfMeshes = (uint32_t)hfmModel.meshes.size(); int shapeID = 0; - for (int i = 0; i < numberOfMeshes; i++) { + for (uint32_t i = 0; i < numberOfMeshes; i++) { const HFMMesh& hfmMesh = hfmModel.meshes.at(i); if (auto mesh = hfmMesh._mesh) { result.append(mesh); @@ -770,7 +791,7 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { auto& materialName = _modelMeshMaterialNames[shapeID]; - result.appendMaterial(graphics::MaterialLayer(getGeometry()->getShapeMaterial(shapeID), 0), shapeID, materialName); + result.appendMaterial(graphics::MaterialLayer(getNetworkModel()->getShapeMaterial(shapeID), 0), shapeID, materialName); { std::unique_lock lock(_materialMappingMutex); @@ -793,77 +814,69 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { void Model::calculateTriangleSets(const HFMModel& hfmModel) { PROFILE_RANGE(render, __FUNCTION__); - int numberOfMeshes = hfmModel.meshes.size(); + uint32_t meshInstanceCount = 0; + uint32_t lastMeshForInstanceCount = hfm::UNDEFINED_KEY; + for (const auto& shape : hfmModel.shapes) { + if (shape.mesh != lastMeshForInstanceCount) { + ++meshInstanceCount; + } + lastMeshForInstanceCount = shape.mesh; + } _triangleSetsValid = true; _modelSpaceMeshTriangleSets.clear(); - _modelSpaceMeshTriangleSets.resize(numberOfMeshes); + _modelSpaceMeshTriangleSets.reserve(meshInstanceCount); - for (int i = 0; i < numberOfMeshes; i++) { - const HFMMesh& mesh = hfmModel.meshes.at(i); + uint32_t lastMeshForTriangleBuilding = hfm::UNDEFINED_KEY; + glm::mat4 lastTransformForTriangleBuilding { 0 }; + std::vector transformedPoints; + for (const auto& shape : hfmModel.shapes) { + const uint32_t meshIndex = shape.mesh; + const hfm::Mesh& mesh = hfmModel.meshes.at(meshIndex); + const auto& triangleListMesh = mesh.triangleListMesh; + const glm::vec2 part = triangleListMesh.parts[shape.meshPart]; + glm::mat4 worldFromMeshTransform; + if (shape.joint != hfm::UNDEFINED_KEY) { + // globalTransform includes hfmModel.offset, + // which includes the scaling, rotation, and translation specified by the FST, + // and the scaling from the unit conversion in FBX. + // This can't change at runtime, so we can safely store these in our TriangleSet. + worldFromMeshTransform = hfmModel.joints[shape.joint].globalTransform; + } - const int numberOfParts = mesh.parts.size(); - auto& meshTriangleSets = _modelSpaceMeshTriangleSets[i]; - meshTriangleSets.resize(numberOfParts); + if (meshIndex != lastMeshForTriangleBuilding || worldFromMeshTransform != lastTransformForTriangleBuilding) { + lastMeshForTriangleBuilding = meshIndex; + lastTransformForTriangleBuilding = worldFromMeshTransform; + _modelSpaceMeshTriangleSets.emplace_back(); + _modelSpaceMeshTriangleSets.back().reserve(mesh.parts.size()); - for (int j = 0; j < numberOfParts; j++) { - const HFMMeshPart& part = mesh.parts.at(j); - - auto& partTriangleSet = meshTriangleSets[j]; - - const int INDICES_PER_TRIANGLE = 3; - const int INDICES_PER_QUAD = 4; - const int TRIANGLES_PER_QUAD = 2; - - // tell our triangleSet how many triangles to expect. - int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; - int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; - int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; - partTriangleSet.reserve(totalTriangles); - - auto meshTransform = hfmModel.offset * mesh.modelTransform; - - if (part.quadIndices.size() > 0) { - int vIndex = 0; - for (int q = 0; q < numberOfQuads; q++) { - int i0 = part.quadIndices[vIndex++]; - int i1 = part.quadIndices[vIndex++]; - int i2 = part.quadIndices[vIndex++]; - int i3 = part.quadIndices[vIndex++]; - - // track the model space version... these points will be transformed by the FST's offset, - // which includes the scaling, rotation, and translation specified by the FST/FBX, - // this can't change at runtime, so we can safely store these in our TriangleSet - glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); - glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); - glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); - glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f)); - - Triangle tri1 = { v0, v1, v3 }; - Triangle tri2 = { v1, v2, v3 }; - partTriangleSet.insert(tri1); - partTriangleSet.insert(tri2); + transformedPoints = triangleListMesh.vertices; + if (worldFromMeshTransform != glm::mat4()) { + for (auto& point : transformedPoints) { + point = glm::vec3(worldFromMeshTransform * glm::vec4(point, 1.0f)); } } + } + auto& meshTriangleSets = _modelSpaceMeshTriangleSets.back(); + meshTriangleSets.emplace_back(); + auto& partTriangleSet = meshTriangleSets.back(); - if (part.triangleIndices.size() > 0) { - int vIndex = 0; - for (int t = 0; t < numberOfTris; t++) { - int i0 = part.triangleIndices[vIndex++]; - int i1 = part.triangleIndices[vIndex++]; - int i2 = part.triangleIndices[vIndex++]; + const static size_t INDICES_PER_TRIANGLE = 3; + const size_t triangleCount = (size_t)(part.y) / INDICES_PER_TRIANGLE; + partTriangleSet.reserve(triangleCount); + const size_t indexStart = (uint32_t)part.x; + const size_t indexEnd = indexStart + (triangleCount * INDICES_PER_TRIANGLE); + for (size_t i = indexStart; i < indexEnd; i += INDICES_PER_TRIANGLE) { + const int i0 = triangleListMesh.indices[i]; + const int i1 = triangleListMesh.indices[i + 1]; + const int i2 = triangleListMesh.indices[i + 2]; - // track the model space version... these points will be transformed by the FST's offset, - // which includes the scaling, rotation, and translation specified by the FST/FBX, - // this can't change at runtime, so we can safely store these in our TriangleSet - glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); - glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); - glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + const glm::vec3 v0 = transformedPoints[i0]; + const glm::vec3 v1 = transformedPoints[i1]; + const glm::vec3 v2 = transformedPoints[i2]; - Triangle tri = { v0, v1, v2 }; - partTriangleSet.insert(tri); - } - } + const Triangle tri = { v0, v1, v2 }; + partTriangleSet.insert(tri); } } } @@ -875,8 +888,8 @@ void Model::updateRenderItemsKey(const render::ScenePointer& scene) { } auto renderItemsKey = _renderItemKeyGlobalFlags; render::Transaction transaction; - foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [renderItemsKey](ModelMeshPartPayload& data) { + for(auto itemID: _modelMeshRenderItemIDs) { + transaction.updateItem(itemID, [renderItemsKey](ModelMeshPartPayload& data) { data.updateKey(renderItemsKey); }); } @@ -953,8 +966,8 @@ void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) { return; } render::Transaction transaction; - foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [cauterized](ModelMeshPartPayload& data) { + for (auto itemID : _modelMeshRenderItemIDs) { + transaction.updateItem(itemID, [cauterized](ModelMeshPartPayload& data) { data.setCauterized(cauterized); }); } @@ -986,7 +999,9 @@ bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters, BlendShapeOperator modelBlendshapeOperator) { + if (!_addedToScene && isLoaded()) { + updateGeometry(); updateClusterMatrices(); if (_modelMeshRenderItems.empty()) { createRenderItemSet(); @@ -997,26 +1012,25 @@ bool Model::addToScene(const render::ScenePointer& scene, bool somethingAdded = false; - if (_modelMeshRenderItemsMap.empty()) { + if (_modelMeshRenderItemIDs.empty()) { bool hasTransparent = false; size_t verticesCount = 0; foreach(auto renderItem, _modelMeshRenderItems) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); - if (_modelMeshRenderItemsMap.empty() && statusGetters.size()) { + if (_modelMeshRenderItemIDs.empty() && statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } transaction.resetItem(item, renderPayload); hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); verticesCount += renderItem.get()->getVerticesCount(); - _modelMeshRenderItemsMap.insert(item, renderPayload); _modelMeshRenderItemIDs.emplace_back(item); } - somethingAdded = !_modelMeshRenderItemsMap.empty(); + somethingAdded = !_modelMeshRenderItemIDs.empty(); _renderInfoVertexCount = verticesCount; - _renderInfoDrawCalls = _modelMeshRenderItemsMap.count(); + _renderInfoDrawCalls = (uint32_t) _modelMeshRenderItemIDs.size(); _renderInfoHasTransparent = hasTransparent; } @@ -1031,14 +1045,12 @@ bool Model::addToScene(const render::ScenePointer& scene, } void Model::removeFromScene(const render::ScenePointer& scene, render::Transaction& transaction) { - foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.removeItem(item); + for (auto itemID: _modelMeshRenderItemIDs) { + transaction.removeItem(itemID); } _modelMeshRenderItemIDs.clear(); - _modelMeshRenderItemsMap.clear(); _modelMeshRenderItems.clear(); _modelMeshMaterialNames.clear(); - _modelMeshRenderItemShapes.clear(); _priorityMap.clear(); _addedToScene = false; @@ -1217,7 +1229,7 @@ void Model::setURL(const QUrl& url) { invalidCalculatedMeshBoxes(); deleteGeometry(); - auto resource = DependencyManager::get()->getGeometryResource(url); + auto resource = DependencyManager::get()->getModelResource(url); if (resource) { resource->setLoadPriority(this, _loadingPriority); _renderWatcher.setResource(resource); @@ -1406,32 +1418,32 @@ void Model::updateClusterMatrices() { return; } + updateShapeStatesFromRig(); + _needsUpdateClusterMatrices = false; - const HFMModel& hfmModel = getHFMModel(); - for (int i = 0; i < (int) _meshStates.size(); i++) { - MeshState& state = _meshStates[i]; - int meshIndex = i; - const HFMMesh& mesh = hfmModel.meshes.at(i); - for (int j = 0; j < mesh.clusters.size(); j++) { - const HFMCluster& cluster = mesh.clusters.at(j); - int clusterIndex = j; + + for (int skinDeformerIndex = 0; skinDeformerIndex < (int)_meshStates.size(); skinDeformerIndex++) { + MeshState& state = _meshStates[skinDeformerIndex]; + auto numClusters = state.getNumClusters(); + for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { + const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); if (_useDualQuaternionSkinning) { - auto jointPose = _rig.getJointPose(cluster.jointIndex); + auto jointPose = _rig.getJointPose(cbmov.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); - state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); + Transform::mult(clusterTransform, jointTransform, cbmov.inverseBindTransform); + state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); } else { - auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); + auto jointMatrix = _rig.getJointTransform(cbmov.jointIndex); + glm_mat4u_mul(jointMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); } } } // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (modelBlender->shouldComputeBlendshapes() && hfmModel.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (modelBlender->shouldComputeBlendshapes() && getHFMModel().hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } @@ -1439,6 +1451,7 @@ void Model::updateClusterMatrices() { void Model::deleteGeometry() { _deleteGeometryCounter++; + _shapeStates.clear(); _meshStates.clear(); _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); @@ -1472,20 +1485,12 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { void Model::createRenderItemSet() { assert(isLoaded()); - const auto& meshes = _renderGeometry->getMeshes(); - - // all of our mesh vectors must match in size - if (meshes.size() != _meshStates.size()) { - qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! " << meshes.size() << _meshStates.size() << " We will not segregate mesh groups yet."; - return; - } // We should not have any existing renderItems if we enter this section of code Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); _modelMeshMaterialNames.clear(); - _modelMeshRenderItemShapes.clear(); Transform transform; transform.setTranslation(_translation); @@ -1496,28 +1501,19 @@ void Model::createRenderItemSet() { offset.postTranslate(_offset); // Run through all of the meshes, and place them into their segregated, but unsorted buckets - int shapeID = 0; - uint32_t numMeshes = (uint32_t)meshes.size(); - for (uint32_t i = 0; i < numMeshes; i++) { - const auto& mesh = meshes.at(i); - if (!mesh) { - continue; - } + const auto& shapes = _renderGeometry->getHFMModel().shapes; + for (uint32_t shapeID = 0; shapeID < shapes.size(); shapeID++) { + const auto& shape = shapes[shapeID]; - // Create the render payloads - int numParts = (int)mesh->getNumParts(); - for (int partIndex = 0; partIndex < numParts; partIndex++) { - _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset, _created); - auto material = getGeometry()->getShapeMaterial(shapeID); - _modelMeshMaterialNames.push_back(material ? material->getName() : ""); - _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); - shapeID++; - } + _modelMeshRenderItems << std::make_shared(shared_from_this(), shape.mesh, shape.meshPart, shapeID, transform, offset, _created); + + auto material = getNetworkModel()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); } } bool Model::isRenderable() const { - return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); + return (!_shapeStates.empty()) || (isLoaded() && _renderGeometry->getMeshes().empty()); } std::set Model::getMeshIDsFromMaterialID(QString parentMaterialName) { @@ -1573,11 +1569,11 @@ void Model::applyMaterialMapping() { PrimitiveMode primitiveMode = getPrimitiveMode(); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; auto modelMeshRenderItemIDs = _modelMeshRenderItemIDs; - auto modelMeshRenderItemShapes = _modelMeshRenderItemShapes; + auto shapeStates = _shapeStates; std::unordered_map shouldInvalidatePayloadShapeKeyMap; - for (auto& shape : _modelMeshRenderItemShapes) { - shouldInvalidatePayloadShapeKeyMap[shape.meshIndex] = shouldInvalidatePayloadShapeKey(shape.meshIndex); + for (auto& shape : _shapeStates) { + shouldInvalidatePayloadShapeKeyMap[shape._meshIndex] = shouldInvalidatePayloadShapeKey(shape._meshIndex); } auto& materialMapping = getMaterialMapping(); @@ -1600,7 +1596,7 @@ void Model::applyMaterialMapping() { std::weak_ptr weakSelf = shared_from_this(); auto materialLoaded = [networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning, - modelMeshRenderItemIDs, modelMeshRenderItemShapes, shouldInvalidatePayloadShapeKeyMap, weakSelf]() { + modelMeshRenderItemIDs, shapeStates, shouldInvalidatePayloadShapeKeyMap, weakSelf]() { std::shared_ptr self = weakSelf.lock(); if (!self || networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) { return; @@ -1626,7 +1622,7 @@ void Model::applyMaterialMapping() { for (auto shapeID : shapeIDs) { if (shapeID < modelMeshRenderItemIDs.size()) { auto itemID = modelMeshRenderItemIDs[shapeID]; - auto meshIndex = modelMeshRenderItemShapes[shapeID].meshIndex; + auto meshIndex = shapeStates[shapeID]._meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKeyMap.at(meshIndex); graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID)); { @@ -1664,7 +1660,7 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; - auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; + auto meshIndex = _shapeStates[shapeID]._meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { @@ -1686,7 +1682,7 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string auto itemID = _modelMeshRenderItemIDs[shapeID]; auto renderItemsKey = _renderItemKeyGlobalFlags; PrimitiveMode primitiveMode = getPrimitiveMode(); - auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; + auto meshIndex = _shapeStates[shapeID]._meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; transaction.updateItem(itemID, [material, renderItemsKey, @@ -1701,14 +1697,13 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); } -class CollisionRenderGeometry : public Geometry { +class CollisionRenderGeometry : public NetworkModel { public: CollisionRenderGeometry(graphics::MeshPointer mesh) { _hfmModel = std::make_shared(); std::shared_ptr meshes = std::make_shared(); meshes->push_back(mesh); _meshes = meshes; - _meshParts = std::shared_ptr(); } }; @@ -1859,7 +1854,7 @@ void Blender::run() { bool Model::maybeStartBlender() { if (isLoaded()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), getGeometry()->getConstHFMModelPointer(), + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), getNetworkModel()->getConstHFMModelPointer(), ++_blendNumber, _blendshapeCoefficients)); return true; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 85aaa33bcb..864de79c67 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -180,7 +180,7 @@ public: virtual void updateClusterMatrices(); /// Returns a reference to the shared geometry. - const Geometry::Pointer& getGeometry() const { return _renderGeometry; } + const NetworkModel::Pointer& getNetworkModel() const { return _renderGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); @@ -299,6 +299,16 @@ public: int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } + class ShapeState { + public: + glm::mat4 _rootFromJointTransform; + uint32_t _jointIndex{ hfm::UNDEFINED_KEY }; + uint32_t _meshIndex{ hfm::UNDEFINED_KEY }; + uint32_t _meshPartIndex{ hfm::UNDEFINED_KEY }; + uint32_t _skinDeformerIndex{ hfm::UNDEFINED_KEY }; + }; + const ShapeState& getShapeState(int index) { return _shapeStates.at(index); } + class TransformDualQuaternion { public: TransformDualQuaternion() {} @@ -341,12 +351,13 @@ public: public: std::vector clusterDualQuaternions; std::vector clusterMatrices; - }; + uint32_t getNumClusters() const { return (uint32_t) std::max(clusterMatrices.size(), clusterMatrices.size()); } + }; const MeshState& getMeshState(int index) { return _meshStates.at(index); } uint32_t getGeometryCounter() const { return _deleteGeometryCounter; } - const QMap& getRenderItems() const { return _modelMeshRenderItemsMap; } + BlendShapeOperator getModelBlendshapeOperator() const { return _modelBlendshapeOperator; } void renderDebugMeshBoxes(gpu::Batch& batch, bool forward); @@ -393,9 +404,9 @@ protected: /// \return true if joint exists bool getJointPosition(int jointIndex, glm::vec3& position) const; - Geometry::Pointer _renderGeometry; // only ever set by its watcher + NetworkModel::Pointer _renderGeometry; // only ever set by its watcher - GeometryResourceWatcher _renderWatcher; + ModelResourceWatcher _renderWatcher; SpatiallyNestable* _spatiallyNestableOverride; @@ -421,6 +432,10 @@ protected: bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to + + std::vector _shapeStates; + void updateShapeStatesFromRig(); + std::vector _meshStates; virtual void initJointStates(); @@ -465,10 +480,7 @@ protected: static AbstractViewStateInterface* _viewState; QVector> _modelMeshRenderItems; - QMap _modelMeshRenderItemsMap; render::ItemIDs _modelMeshRenderItemIDs; - using ShapeInfo = struct { int meshIndex; }; - std::vector _modelMeshRenderItemShapes; std::vector _modelMeshMaterialNames; bool _addedToScene { false }; // has been added to scene @@ -520,7 +532,7 @@ private: }; Q_DECLARE_METATYPE(ModelPointer) -Q_DECLARE_METATYPE(Geometry::WeakPointer) +Q_DECLARE_METATYPE(NetworkModel::WeakPointer) Q_DECLARE_METATYPE(BlendshapeOffset) /// Handle management of pending models that need blending diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index fe005df2d4..dbfa23a143 100755 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -258,8 +258,8 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con ShapeKey::Builder().withDeformed(), ShapeKey::Builder().withDeformed().withFade(), ShapeKey::Builder().withDeformed().withDualQuatSkinned(), ShapeKey::Builder().withDeformed().withDualQuatSkinned().withFade(), ShapeKey::Builder().withOwnPipeline(), ShapeKey::Builder().withOwnPipeline().withFade(), - ShapeKey::Builder().withOwnPipeline().withDeformed(), ShapeKey::Builder().withOwnPipeline().withDeformed().withFade(), - ShapeKey::Builder().withOwnPipeline().withDeformed().withDualQuatSkinned(), ShapeKey::Builder().withOwnPipeline().withDeformed().withDualQuatSkinned().withFade(), + ShapeKey::Builder().withDeformed().withOwnPipeline(), ShapeKey::Builder().withDeformed().withOwnPipeline().withFade(), + ShapeKey::Builder().withDeformed().withDualQuatSkinned().withOwnPipeline(), ShapeKey::Builder().withDeformed().withDualQuatSkinned().withOwnPipeline().withFade(), }; std::vector> sortedShapeKeys(keys.size()); diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 186f9e682a..7a58498e50 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -41,37 +41,36 @@ void SoftAttachmentModel::updateClusterMatrices() { _needsUpdateClusterMatrices = false; - const HFMModel& hfmModel = getHFMModel(); - for (int i = 0; i < (int) _meshStates.size(); i++) { - MeshState& state = _meshStates[i]; - const HFMMesh& mesh = hfmModel.meshes.at(i); - int meshIndex = i; - for (int j = 0; j < mesh.clusters.size(); j++) { - const HFMCluster& cluster = mesh.clusters.at(j); + for (int skinDeformerIndex = 0; skinDeformerIndex < (int)_meshStates.size(); skinDeformerIndex++) { + MeshState& state = _meshStates[skinDeformerIndex]; + auto numClusters = state.getNumClusters(); + for (uint32_t clusterIndex = 0; clusterIndex < numClusters; clusterIndex++) { + const auto& cbmov = _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(skinDeformerIndex, clusterIndex); - int clusterIndex = j; // TODO: cache these look-ups as an optimization - int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); - glm::mat4 jointMatrix; + int jointIndexOverride = getJointIndexOverride(cbmov.jointIndex); + auto rig = &_rigOverride; if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); - } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); + rig = &_rig; } + if (_useDualQuaternionSkinning) { - glm::mat4 m; - glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, m); - state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m); + auto jointPose = rig->getJointPose(cbmov.jointIndex); + Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cbmov.inverseBindTransform); + state.clusterDualQuaternions[clusterIndex] = Model::TransformDualQuaternion(clusterTransform); } else { - glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); + auto jointMatrix = rig->getJointTransform(cbmov.jointIndex); + glm_mat4u_mul(jointMatrix, cbmov.inverseBindMatrix, state.clusterMatrices[clusterIndex]); } } } // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (modelBlender->shouldComputeBlendshapes() && hfmModel.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (modelBlender->shouldComputeBlendshapes() && getHFMModel().hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 0fefff1c4c..9052cb89e9 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -3,18 +3,8 @@ set(TARGET_NAME script-engine) setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets) target_zlib() - if (NOT ANDROID) - - add_dependency_external_projects(quazip) - find_package(QuaZip REQUIRED) - target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) - - if (WIN32) - add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) - endif () - + target_quazip() endif () link_hifi_libraries(shared networking shaders material-networking model-networking recording avatars fbx entities controllers animation audio midi) diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 33be9de2ad..f088ad7a38 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -419,7 +419,7 @@ void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue sc * false to upload and store the data without gzip compression. Synonym: compressed. * @property {string|ArrayBuffer} data - The content to upload. * @property {string} [path] - A user-friendly path for the file in the asset server. May have a leading - * "atp:". IF not specified, no path-to-hash mapping is set. + * "atp:". If not specified, no path-to-hash mapping is set. *

    Note: The asset server destroys any unmapped SHA256-named file at server restart. Either set the mapping path * with this property or use {@link Assets.setMapping} to set a path-to-hash mapping for the uploaded file.

    */ diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.h b/libraries/script-engine/src/ConsoleScriptingInterface.h index deb0d126f9..7eec44d103 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.h +++ b/libraries/script-engine/src/ConsoleScriptingInterface.h @@ -26,25 +26,190 @@ #include #include +/**jsdoc + * The console API provides program logging facilities. + * + * @namespace console + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + */ // Scriptable interface of "console" object. Used exclusively in the JavaScript API class ConsoleScriptingInterface : public QObject, protected QScriptable { Q_OBJECT public: + + /**jsdoc + * Logs an "INFO" message to the program log and triggers {@link Script.infoMessage}. + * The message logged is "INFO -" followed by the message values separated by spaces. + * @function console.info + * @param {...*} [message] - The message values to log. + */ static QScriptValue info(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Logs a message to the program log and triggers {@link Script.printedMessage}. + * The message logged is the message values separated by spaces. + *

    If a {@link console.group} is in effect, the message is indented by an amount proportional to the group level.

    + * @function console.log + * @param {...*} [message] - The message values to log. + * @example
+ * Script.printedMessage.connect(function (message, scriptName) { + * console.info("Console.log message:", "\"" + message + "\"", "in", "[" + scriptName + "]"); + * }); + * + * console.log("string", 7, true); + * + * // string 7 true + * // INFO - Console.log message: "string 7 true" in [console.js] + */ static QScriptValue log(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Logs a message to the program log and triggers {@link Script.printedMessage}. + * The message logged is the message values separated by spaces. + * @function console.debug + * @param {...*} [message] - The message values to log. + */ static QScriptValue debug(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Logs a "WARNING" message to the program log and triggers {@link Script.warningMessage}. + * The message logged is "WARNING - " followed by the message values separated by spaces. + * @function console.warn + * @param {...*} [message] - The message values to log. + */ static QScriptValue warn(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Logs an "ERROR" message to the program log and triggers {@link Script.errorMessage}. + * The message logged is "ERROR - " followed by the message values separated by spaces. + * @function console.error + * @param {...*} [message] - The message values to log. + */ static QScriptValue error(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * A synonym of {@link console.error}. + * Logs an "ERROR" message to the program log and triggers {@link Script.errorMessage}. + * The message logged is "ERROR - " followed by the message values separated by spaces. + * @function console.exception + * @param {...*} [message] - The message values to log. + */ static QScriptValue exception(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Logs an "ERROR" message to the program log and triggers {@link Script.errorMessage}, if a test condition fails. + * The message logged is "ERROR - Assertion failed : " followed by the message values separated by spaces. + *

Note: Script execution continues whether or not the test condition fails.

+ * @function console.assert + * @param {boolean} assertion - The test condition value. + * @param {...*} [message] - The message values to log if the assertion evaluates to false. + * @example + * Script.errorMessage.connect(function (message, scriptName) { + * console.info("Error message logged:", "\"" + message + "\"", "in", "[" + scriptName + "]"); + * }); + * + * console.assert(5 === 5, "5 equals 5.", "This assertion will succeed."); // No log output. + * console.assert(5 > 6, "5 is not greater than 6.", "This assertion will fail."); // Log output is generated. + * console.info("Script continues running."); + * + * // ERROR - Assertion failed : 5 is not greater than 6. This assertion will fail. + * // INFO - Error message logged: "Assertion failed : 5 is not greater than 6. This assertion will fail." in [console.js] + * // INFO - Script continues running. + */ + // Note: Is registered in the script engine as "assert" static QScriptValue assertion(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Logs a message to the program log and triggers {@link Script.printedMessage}, then starts indenting subsequent + * {@link console.log} messages until a {@link console.groupEnd}. Groups may be nested. + * @function console.group + * @param {*} message - The message value to log. + * @example + * console.group("Group 1"); + * console.log("Sentence 1"); + * console.log("Sentence 2"); + * console.group("Group 2"); + * console.log("Sentence 3"); + * console.log("Sentence 4"); + * console.groupEnd(); + * console.log("Sentence 5"); + * console.groupEnd(); + * console.log("Sentence 6"); + * + * //Group 1 + * // Sentence 1 + * // Sentence 2 + * // Group 2 + * // Sentence 3 + * // Sentence 4 + * // Sentence 5 + * //Sentence 6 + */ static QScriptValue group(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Has the same behavior as {@link console.group}. + * Logs a message to the program log and triggers {@link Script.printedMessage}, then starts indenting subsequent + * {@link console.log} messages until a {@link console.groupEnd}. Groups may be nested. + * @function console.groupCollapsed + * @param {*} message - The message value to log. + */ static QScriptValue groupCollapsed(QScriptContext* context, QScriptEngine* engine); + + /**jsdoc + * Finishes a group of indented {@link console.log} messages. + * @function console.groupEnd + */ static QScriptValue groupEnd(QScriptContext* context, QScriptEngine* engine); public slots: + + /**jsdoc + * Starts a timer, logs a message to the program log, and triggers {@link Script.printedMessage}. The message logged has + * the timer name and "Timer started". + * @function console.time + * @param {string} name - The name of the timer. + * @example + * function sleep(milliseconds) { + * var start = new Date().getTime(); + * for (var i = 0; i < 1e7; i++) { + * if ((new Date().getTime() - start) > milliseconds) { + * break; + * } + * } + * } + * + * console.time("MyTimer"); + * sleep(1000); // Do some processing. + * console.timeEnd("MyTimer"); + * + * // MyTimer: Timer started + * // MyTimer: 1001ms */ void time(QString labelName); + + /**jsdoc + * Finishes a timer, logs a message to the program log, and triggers {@link Script.printedMessage}. The message logged has + * the timer name and the number of milliseconds since the timer was started. + * @function console.timeEnd + * @param {string} name - The name of the timer. + */ void timeEnd(QString labelName); + + /**jsdoc + * Logs the JavaScript call stack at the point of call to the program log. + * @function console.trace + */ void trace(); + + /**jsdoc + * Clears the Developer > Scripting > Script Log debug window. + * @function console.clear + */ void clear(); private: diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/script-engine/src/ModelScriptingInterface.h index 3c239f006f..39170bb370 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ b/libraries/script-engine/src/ModelScriptingInterface.h @@ -17,19 +17,79 @@ #include class QScriptEngine; +/**jsdoc + * The Model API provides the ability to manipulate meshes. You can get the meshes for an entity using + * {@link Entities.getMeshes}, or create a new mesh using {@link Model.newMesh}. + *

See also, the {@link Graphics} API.

+ * + * @namespace Model + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @deprecated This API is deprecated. Use the {@link Graphics} API instead. + */ class ModelScriptingInterface : public QObject { Q_OBJECT public: ModelScriptingInterface(QObject* parent); + /**jsdoc + * Exports meshes to an OBJ format model. + * @function Model.meshToOBJ + * @param {MeshProxy[]} meshes - The meshes to export. + * @returns {string} The OBJ format representation of the meshes. + */ Q_INVOKABLE QString meshToOBJ(MeshProxyList in); + + /**jsdoc + * Combines multiple meshes into one. + * @function Model.appendMeshes + * @param {MeshProxy[]} meshes - The meshes to combine. + * @returns {MeshProxy} The combined mesh. + */ Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in); + + /**jsdoc + * Transforms the vertices in a mesh. + * @function Model.transformMesh + * @param {Mat4} transform - The transform to apply. + * @param {MeshProxy} mesh - The mesh to apply the transform to. + * @returns {MeshProxy|boolean} The transformed mesh, if valid. false if an error. + */ Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); + + /**jsdoc + * Creates a new mesh. + * @function Model.newMesh + * @param {Vec3[]} vertices - The vertices in the mesh. + * @param {Vec3[]} normals - The normals in the mesh. + * @param {MeshFace[]} faces - The faces in the mesh. + * @returns {MeshProxy} A new mesh. + */ Q_INVOKABLE QScriptValue newMesh(const QVector& vertices, const QVector& normals, const QVector& faces); + + /**jsdoc + * Gets the number of vertices in a mesh. + * @function Model.getVertexCount + * @param {MeshProxy} mesh - The mesh to count the vertices in. + * @returns {number|boolean} The number of vertices in the mesh, if valid. false if an error. + */ Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy); + + /**jsdoc + * Gets the position of a vertex in a mesh. + * @function Model.getVertex + * @param {MeshProxy} mesh - The mesh. + * @param {number} index - The index of the vertex to get. + * @returns {Vec3|boolean} The local position of the vertex relative to the mesh, if valid. false if an error. + */ Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 98f1f3082f..3b2a122e71 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -736,6 +736,13 @@ void ScriptEngine::init() { QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor); globalObject().setProperty("WebSocket", webSocketConstructorValue); + /**jsdoc + * Prints a message to the program log and emits {@link Script.printedMessage}. + * The message logged is the message values separated by spaces. + *

Alternatively, you can use {@link Script.print} or one of the {@link console} API methods.

+ * @function print + * @param {...*} [message] - The message values to print. + */ globalObject().setProperty("print", newFunction(debugPrint)); QScriptValue audioEffectOptionsConstructorValue = newFunction(AudioEffectOptions::constructor); @@ -1012,6 +1019,12 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& }); // Two common cases of event handler, differing only in argument signature. + + /**jsdoc + * Called when an entity event occurs on an entity as registered with {@link Script.addEventHandler}. + * @callback Script~entityEventCallback + * @param {Uuid} entityID - The ID of the entity the event has occured on. + */ using SingleEntityHandler = std::function; auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { return [this, eventName](const EntityItemID& entityItemID) { @@ -1019,6 +1032,12 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& }; }; + /**jsdoc + * Called when a pointer event occurs on an entity as registered with {@link Script.addEventHandler}. + * @callback Script~pointerEventCallback + * @param {Uuid} entityID - The ID of the entity the event has occurred on. + * @param {PointerEvent} pointerEvent - Details of the event. + */ using PointerHandler = std::function; auto makePointerHandler = [this](QString eventName) -> PointerHandler { return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { @@ -1028,6 +1047,13 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& }; }; + /**jsdoc + * Called when a collision event occurs on an entity as registered with {@link Script.addEventHandler}. + * @callback Script~collisionEventCallback + * @param {Uuid} entityA - The ID of one entity in the collision. + * @param {Uuid} entityB - The ID of the other entity in the collision. + * @param {Collision} collisionEvent - Details of the collision. + */ using CollisionHandler = std::function; auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler { return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { @@ -1037,28 +1063,39 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& }; /**jsdoc - *

The name of an entity event. When the entity event occurs, any function that has been registered for that event via - * {@link Script.addEventHandler} is called with parameters per the entity event.

+ *

The name of an entity event. When the entity event occurs, any function that has been registered for that event + * via {@link Script.addEventHandler} is called with parameters per the entity event.

*
Log some values.Demonstrate assertion behavior.Nested groups.Time some processing.
* - * + * * * - * - * - * - * - * - * - * - * - * - * - * - * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * *
Event NameEntity Event
Event NameCallback TypeEntity Event
"enterEntity"{@link Entities.enterEntity}
"leaveEntity"{@link Entities.leaveEntity}
"mousePressOnEntity"{@link Entities.mousePressOnEntity}
"mouseMoveOnEntity"{@link Entities.mouseMoveOnEntity}
"mouseReleaseOnEntity"{@link Entities.mouseReleaseOnEntity}
"clickDownOnEntity"{@link Entities.clickDownOnEntity}
"holdingClickOnEntity"{@link Entities.holdingClickOnEntity}
"clickReleaseOnEntity"{@link Entities.clickReleaseOnEntity}
"hoverEnterEntity"{@link Entities.hoverEnterEntity}
"hoverOverEntity"{@link Entities.hoverOverEntity}
"hoverLeaveEntity"{@link Entities.hoverLeaveEntity}
"collisionWithEntity"{@link Entities.collisionWithEntity}
"enterEntity"{@link Script~entityEventCallback|entityEventCallback}{@link Entities.enterEntity}
"leaveEntity"{@link Script~entityEventCallback|entityEventCallback}{@link Entities.leaveEntity}
"mousePressOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mousePressOnEntity}
"mouseMoveOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mouseMoveOnEntity}
"mouseReleaseOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mouseReleaseOnEntity}
"clickDownOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.clickDownOnEntity}
"holdingClickOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.holdingClickOnEntity}
"clickReleaseOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.clickReleaseOnEntity}
"hoverEnterEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverEnterEntity}
"hoverOverEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverOverEntity}
"hoverLeaveEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverLeaveEntity}
"collisionWithEntity"{@link Script~collisionEventCallback|collisionEventCallback}{@link Entities.collisionWithEntity}
- * * @typedef {string} Script.EntityEvent */ connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 0ae913431f..4265390995 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -320,13 +320,13 @@ public: // NOTE - these are intended to be public interfaces available to scripts /**jsdoc - * Adds a function to the list of functions called when an entity event occurs on a particular entity. + * Adds a function to the list of functions called when a particular event occurs on a particular entity. *

See also, the {@link Entities} API.

* @function Script.addEventHandler * @param {Uuid} entityID - The ID of the entity. - * @param {Script.EntityEvent} eventName - The name of the entity event. - * @param {function} handler - The function to call when the entity event occurs on the entity. It can be either the name - * of a function or an in-line definition. + * @param {Script.EntityEvent} eventName - The name of the event. + * @param {Script~entityEventCallback|Script~pointerEventCallback|Script~collisionEventCallback} handler - The function to + * call when the event occurs on the entity. It can be either the name of a function or an in-line definition. * @example Report when a mouse press occurs on a particular entity. * var entityID = Entities.addEntity({ * type: "Box", @@ -499,17 +499,11 @@ public: Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } /**jsdoc - * Prints a message to the program log. - *

Alternatively, you can use {@link print}, {@link console.log}, or one of the other {@link console} methods.

+ * Prints a message to the program log and emits {@link Script.printedMessage}. + *

Alternatively, you can use {@link print} or one of the {@link console} API methods.

* @function Script.print * @param {string} message - The message to print. */ - /**jsdoc - * Prints a message to the program log. - *

This is an alias of {@link Script.print}.

- * @function print - * @param {string} message - The message to print. - */ Q_INVOKABLE void print(const QString& message); /**jsdoc @@ -763,7 +757,8 @@ signals: /**jsdoc * Triggered when the script prints a message to the program log via {@link print}, {@link Script.print}, - * {@link console.log}, or {@link console.debug}. + * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or + * {@link console.timeEnd}. * @function Script.printedMessage * @param {string} message - The message. * @param {string} scriptName - The name of the script that generated the message. @@ -772,7 +767,8 @@ signals: void printedMessage(const QString& message, const QString& scriptName); /**jsdoc - * Triggered when the script generates an error or {@link console.error} is called. + * Triggered when the script generates an error, {@link console.error} or {@link console.exception} is called, or + * {@link console.assert} is called and fails. * @function Script.errorMessage * @param {string} message - The error message. * @param {string} scriptName - The name of the script that generated the error message. diff --git a/libraries/script-engine/src/ScriptsModel.h b/libraries/script-engine/src/ScriptsModel.h index bd6c9687c1..0412bbf0fe 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -87,7 +87,6 @@ public: * * * @class ScriptsModel - * @hideconstructor * * @hifi-interface diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index 9a2a51e0b7..00d379aff4 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -76,6 +76,18 @@ void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reas _webSocket->close(closeCode, reason); } +/**jsdoc + * Called when the connection closes. + * @callback WebSocket~onCloseCallback + * @param {WebSocket.CloseData} data - Information on the connection closure. + */ +/**jsdoc + * Information on a connection being closed. + * @typedef {object} WebSocket.CloseData + * @property {WebSocket.CloseCode} code - The reason why the connection was closed. + * @property {string} reason - Description of the reason why the connection was closed. + * @property {boolean} wasClean - true if the connection closed cleanly, false if it didn't. + */ void WebSocketClass::handleOnClose() { bool hasError = (_webSocket->error() != QAbstractSocket::UnknownSocketError); if (_onCloseEvent.isFunction()) { @@ -89,12 +101,73 @@ void WebSocketClass::handleOnClose() { } } +/**jsdoc + * Called when an error occurs. + * @callback WebSocket~onErrorCallback + * @param {WebSocket.SocketError} error - The error. + */ +/**jsdoc + *

The type of socket error.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0ConnectionRefusedErrorThe connection was refused or timed out.
1RemoteHostClosedErrorThe remote host closed the connection.
2HostNotFoundErrorThe host address was not found.
3SocketAccessErrorThe socket operation failed because the application doesn't have + * the necessary privileges.
4SocketResourceErrorThe local system ran out of resources (e.g., too many + * sockets).
5SocketTimeoutErrorThe socket operation timed out.
6DatagramTooLargeErrorThe datagram was larger than the OS's limit.
7NetworkErrorAn error occurred with the network.
8AddressInUseErrorThe is already in use and cannot be reused.
9SocketAddressNotAvailableErrorThe address specified does not belong to the + * host.
10UnsupportedSocketOperationErrorThe requested socket operation is not supported + * by the local OS.
11ProxyAuthenticationRequiredErrorThe socket is using a proxy and requires + * authentication.
12SslHandshakeFailedErrorThe SSL/TLS handshake failed.
13UnfinishedSocketOperationErrorThe last operation has not finished yet.
14ProxyConnectionRefusedErrorCould not contact the proxy server because connection + * was denied.
15ProxyConnectionClosedErrorThe connection to the proxy server was unexpectedly + * closed.
16ProxyConnectionTimeoutErrorThe connection to the proxy server timed + * out.
17ProxyNotFoundErrorThe proxy address was not found.
18ProxyProtocolErrorConnection to the proxy server failed because the server + * response could not be understood.
19OperationErrorAn operation failed because the socket state did not permit + * it.
20SslInternalErrorInternal error in the SSL library being used.
21SslInvalidUserDataErrorError in the SSL library because of invalid + * data.
22TemporaryErrorA temporary error occurred.
-1UnknownSocketErrorAn unknown error occurred.
+ * @typedef {number} WebSocket.SocketError + */ void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { if (_onErrorEvent.isFunction()) { _onErrorEvent.call(); } } +/**jsdoc + * Triggered when a message is received. + * @callback WebSocket~onMessageCallback + * @param {WebSocket.MessageData} message - The message received. + */ +/**jsdoc + * A message received on a WebSocket connection. + * @typedef {object} WebSocket.MessageData + * @property {string} data - The message content. + */ void WebSocketClass::handleOnMessage(const QString& message) { if (_onMessageEvent.isFunction()) { QScriptValueList args; @@ -124,6 +197,10 @@ void WebSocketClass::handleOnBinaryMessage(const QByteArray& message) { } } +/**jsdoc + * Called when the connection opens. + * @callback WebSocket~onOpenCallback + */ void WebSocketClass::handleOnOpen() { if (_onOpenEvent.isFunction()) { _onOpenEvent.call(); diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index 048ba4bc10..3430068eee 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -16,6 +16,68 @@ #include #include +/**jsdoc + * Provides a bi-directional, event-driven communication session between the script and another WebSocket connection. It is a + * near-complete implementation of the WebSocket API described in the Mozilla docs: + * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket. + * + *

Create using new WebSocket(...) in Interface, client entity, avatar, and server entity scripts, or the + * {@link WebSocketServer} class in server entity and assignment client scripts. + * + *

Note: Does not support secure, wss: protocol.

+ * + * @class WebSocket + * @param {string|WebSocket} urlOrWebSocket - The URL to connect to or an existing {@link WebSocket} to reuse the connection of. + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {string} binaryType="blob" - Not used. + * @property {number} bufferedAmount=0 - Not implemented. Read-only. + * @property {string} extensions="" - Not implemented. Read-only. + * + * @property {WebSocket~onOpenCallback} onopen - Function called when the connection opens. + * @property {WebSocket~onMessageCallback} onmessage - Function called when a message is received. + * @property {WebSocket~onErrorCallback} onerror - Function called when an error occurs. + * @property {WebSocket~onCloseCallback} onclose - Function called when the connection closes. + * + * @property {string} protocol="" - Not implemented. Read-only. + * @property {WebSocket.ReadyState} readyState - The state of the connection. Read-only. + * @property {string} url - The URL to connect to. Read-only. + * + * @property {WebSocket.ReadyState} CONNECTING - The connection is opening. Read-only. + * @property {WebSocket.ReadyState} OPEN - The connection is open. Read-only. + * @property {WebSocket.ReadyState} CLOSING - The connection is closing. Read-only. + * @property {WebSocket.ReadyState} CLOSED - The connection is closed. Read-only. + * + * @example Echo a message off websocket.org. + * print("Create WebSocket"); + * var WEBSOCKET_PING_URL = "ws://echo.websocket.org"; + * var webSocket = new WebSocket(WEBSOCKET_PING_URL); + * + * webSocket.onclose = function (data) { + * print("WebSocket closed"); + * print("Ready state =", webSocket.readyState); // 3 + * }; + * + * webSocket.onmessage = function (data) { + * print("Message received:", data.data); + * + * print("Close WebSocket"); + * webSocket.close(); + * }; + * + * webSocket.onopen = function () { + * print("WebSocket opened"); + * print("Ready state =", webSocket.readyState); // 1 + * + * print("Send a test message"); + * webSocket.send("Test message"); + * }; + */ class WebSocketClass : public QObject { Q_OBJECT Q_PROPERTY(QString binaryType READ getBinaryType WRITE setBinaryType) @@ -43,6 +105,21 @@ public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + /**jsdoc + * The state of a WebSocket connection. + * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0CONNECTINGThe connection is opening.
1OPENThe connection is open.
2CLOSINGThe connection is closing.
3CLOSEDThe connection is closed.
+ * @typedef {number} WebSocket.ReadyState + */ enum ReadyState { CONNECTING = 0, OPEN, @@ -100,8 +177,44 @@ public: QScriptValue getOnOpen() { return _onOpenEvent; } public slots: + + /**jsdoc + * Sends a message on the connection. + * @function WebSocket.send + * @param {string|object} message - The message to send. If an object, it is converted to a string. + */ void send(QScriptValue message); + /**jsdoc + * Closes the connection. + * @function WebSocket.close + * @param {WebSocket.CloseCode} [closeCode=1000] - The reason for closing. + * @param {string} [reason=""] - A description of the reason for closing. + */ + /**jsdoc + * The reason why the connection was closed. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
1000NormalNormal closure.
1001GoingAwayGoing away.
1002ProtocolErrorProtocol error.
1003DatatypeNotSupportedUnsupported data.
1004Reserved1004Reserved.
1005MissingStatusCodeNo status received.
1006AbnormalDisconnectionabnormal closure.
1007WrongDatatypeInvalid frame payload data.
1008PolicyViolatedPolicy violation.
1009TooMuchDataMessage too big.
1010MissingExtensionMandatory extension missing.
1011BadOperationInternal server error.
1015TlsHandshakeFailedTLS handshake failed.
+ * @typedef {number} WebSocket.CloseCode + */ void close(); void close(QWebSocketProtocol::CloseCode closeCode); void close(QWebSocketProtocol::CloseCode closeCode, QString reason); diff --git a/libraries/script-engine/src/WebSocketServerClass.h b/libraries/script-engine/src/WebSocketServerClass.h index 972bf9c032..fff33d5bfb 100644 --- a/libraries/script-engine/src/WebSocketServerClass.h +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -17,6 +17,62 @@ #include #include "WebSocketClass.h" +/**jsdoc + * Manages {@link WebSocket}s in server entity and assignment client scripts. + * + *

Create using new WebSocketServer(...).

+ * + * @class WebSocketServer + * + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {string} url - The URL that the server is listening on. Read-only. + * @property {number} port - The port that the server is listening on. Read-only. + * @property {boolean} listening - true if the server is listening for incoming connections, false if + * it isn't. Read-only. + * + * @example
+ * // Server entity script. Echoes received message back to sender. + * (function () { + * print("Create WebSocketServer"); + * var webSocketServer = new WebSocketServer(); + * print("Server url:", webSocketServer.url); + * + * function onNewConnection(webSocket) { + * print("New connection"); + * + * webSocket.onmessage = function (message) { + * print("Message received:", message.data); + * + * var returnMessage = message.data + " back!"; + * print("Echo a message back:", returnMessage); + * webSocket.send(message.data + " back!"); + * }; + * } + * + * webSocketServer.newConnection.connect(onNewConnection); + * }) + * + * @example + * // Interface script. Bounces message off server entity script. + * // Use the server URL reported by the server entity script. + * var WEBSOCKET_PING_URL = "ws://127.0.0.1:nnnnn"; + * var TEST_MESSAGE = "Hello"; + * + * print("Create WebSocket"); + * var webSocket = new WebSocket(WEBSOCKET_PING_URL); + * + * webSocket.onmessage = function(data) { + * print("Message received:", data.data); + * }; + * + * webSocket.onopen = function() { + * print("WebSocket opened"); + * print("Send test message:", TEST_MESSAGE); + * webSocket.send(TEST_MESSAGE); + * }; + */ class WebSocketServerClass : public QObject { Q_OBJECT Q_PROPERTY(QString url READ getURL) @@ -34,6 +90,11 @@ public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); public slots: + + /**jsdoc + * Closes all connections and closes the WebSocketServer. + * @function WebSocketServer.close + */ void close(); private: @@ -45,6 +106,13 @@ private slots: void onNewConnection(); signals: + + /**jsdoc + * Triggered when there is a new connection. + * @function WebSocketServer.newConnection + * @param {WebSocket} webSocket - The {@link WebSocket} for the new connection. + * @returns {Signal} + */ void newConnection(WebSocketClass* client); }; diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index e62726f5d0..b0ceca5758 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -109,6 +109,10 @@ QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { return QScriptValue::NullValue; } +/**jsdoc + * Called when the request's ready state changes. + * @callback XMLHttpRequest~onReadyStateChangeCallback + */ void XMLHttpRequestClass::setReadyState(ReadyState readyState) { if (readyState != _readyState) { _readyState = readyState; @@ -184,6 +188,10 @@ void XMLHttpRequestClass::doSend() { } } +/**jsdoc + * Called when the request times out. + * @callback XMLHttpRequest~onTimeoutCallback + */ void XMLHttpRequestClass::requestTimeout() { if (_onTimeout.isFunction()) { _onTimeout.call(QScriptValue::NullValue); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index d7f3c2e059..3ab7d38dda 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -21,6 +21,134 @@ #include #include +/* +XMlHttpRequest object +XMlHttpRequest.objectName string +XMlHttpRequest.response undefined +XMlHttpRequest.responseText string +XMlHttpRequest.responseType string +XMlHttpRequest.status number +XMlHttpRequest.statusText string +XMlHttpRequest.readyState number +XMlHttpRequest.errorCode number +XMlHttpRequest.timeout number +XMlHttpRequest.UNSENT number +XMlHttpRequest.OPENED number +XMlHttpRequest.HEADERS_RECEIVED number +XMlHttpRequest.LOADING number +XMlHttpRequest.DONE number +XMlHttpRequest.ontimeout object +XMlHttpRequest.onreadystatechange object +XMlHttpRequest.destroyed(QObject*) function +XMlHttpRequest.destroyed() function +XMlHttpRequest.objectNameChanged(QString) function +XMlHttpRequest.deleteLater() function +XMlHttpRequest.requestComplete() function +XMlHttpRequest.abort() function +XMlHttpRequest.setRequestHeader(QString,QString) function +XMlHttpRequest.open(QString,QString,bool,QString,QString) function +XMlHttpRequest.open(QString,QString,bool,QString) function +XMlHttpRequest.open(QString,QString,bool) function +XMlHttpRequest.open(QString,QString) function +XMlHttpRequest.send() function +XMlHttpRequest.send(QScriptValue) function +XMlHttpRequest.getAllResponseHeaders() function +XMlHttpRequest.getResponseHeader(QString) function +*/ + +/**jsdoc + * Provides a means to interact with web servers. It is a near-complete implementation of the XMLHttpRequest API described in + * the Mozilla docs: + * https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest. + * + *

Create using new XMLHttpRequest(...).

+ * + * @class XMLHttpRequest + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {*} response - The response data. + * Read-only. + * @property {string} responseText - The response data as text. + * Read-only. + * @property {string} responseType - The response type required or received (e.g., "text", "json", + * "arraybuffer", ...). + * @property {number} status - The HTTP response status + * code (100599). + * Read-only. + * @property {string} statusText - The HTTP response status text. + * Read-only. + * @property {XMLHttpRequest.ReadyState} readyState - The status of the request. + * Read-only. + * @property {XMLHttpRequest.NetworkError} errorCode - The network result of the request: including, 0 (NoError) + * if there was no error, 4 (TimeoutError) if the request timed out. + * Read-only. + * @property {number} timeout - The time a request can take before timing out, in ms. + * + * @property {XMLHttpRequest.ReadyState} UNSENT - Request has been created; {@link XMLHttpRequest.open} not called yet. + * Read-only. + * @property {XMLHttpRequest.ReadyState} OPENED - {@link XMLHttpRequest.open} has been called. + * Read-only. + * @property {XMLHttpRequest.ReadyState} HEADERS_RECEIVED - {@link XMLHttpRequest.send} has been called; headers and status + * are available. + * Read-only. + * @property {XMLHttpRequest.ReadyState} LOADING - Downloading; {@link XMLHttpRequest|XMLHttpRequest.responseText} has partial + * data. + * Read-only. + * @property {XMLHttpRequest.ReadyState} DONE - Operation complete. + * Read-only. + * + * @property {XMLHttpRequest~onTimeoutCallback} ontimeout - Function called when the request times out. + *

Note: This is called in addition to any function set for onreadystatechange.

+ * @property {XMLHttpRequest~onReadyStateChangeCallback} onreadystatechange - Function called when the request's ready state + * changes. + * + * @example
+ * var URL = "https://www.highfidelity.com/"; + * + * var req = new XMLHttpRequest(); + * req.onreadystatechange = function () { + * if (req.readyState === req.DONE) { + * if (req.status === 200) { + * print("Success"); + * print("Content type:", req.getResponseHeader("content-type")); + * print("Content:", req.responseText.slice(0, 100), "..."); + * + * } else { + * print("Error", req.status, req.statusText); + * } + * + * req = null; + * } + * }; + * + * req.open("GET", URL); + * req.send(); + * + * @example + * var URL = "https://www.highfidelity.com/"; + * + * var req = new XMLHttpRequest(); + * req.requestComplete.connect(function () { + * if (req.status === 200) { + * print("Success"); + * print("Content type:", req.getResponseHeader("content-type")); + * print("Content:", req.responseText.slice(0, 100), "..."); + * + * } else { + * print("Error", req.status, req.statusText); + * } + * + * req = null; + * }); + * + * req.open("GET", URL); + * req.send(); + */ class XMLHttpRequestClass : public QObject { Q_OBJECT Q_PROPERTY(QScriptValue response READ getResponse) @@ -46,6 +174,26 @@ public: ~XMLHttpRequestClass(); static const int MAXIMUM_REDIRECTS = 5; + + /**jsdoc + *

The state of an XMLHttpRequest.

+ *
Echo a message back to sender.Get a web page's HTML.Get a web page's HTML — alternative method.
+ * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0UNSENTRequest has been created; {@link XMLHttpRequest.open} not called + * yet.
1OPENED{@link XMLHttpRequest.open} has been called.
2HEADERS_RECEIVED{@link XMLHttpRequest.send} has been called; headers and + * status are available.
3LOADINGDownloading; {@link XMLHttpRequest|XMLHttpRequest.responseText} has + * partial data.
4DONEOperation complete.
+ * @typedef {number} XMLHttpRequest.ReadyState + */ enum ReadyState { UNSENT = 0, OPENED, @@ -79,16 +227,66 @@ public: void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; } public slots: + + /**jsdoc + * Aborts the request. + * @function XMLHttpRequest.abort + */ void abort(); + + /**jsdoc + * Sets the value of an HTTP request header. Must be called after {@link XMLHttpRequest.open} but before + * {@link XMLHttpRequest.send}; + * @function XMLHttpRequest.setRequestHeader + * @param {string} name - The name of the header to set. + * @param {string} value - The value of the header. + */ void setRequestHeader(const QString& name, const QString& value); + + /**jsdoc + * Initializes a request. + * @function XMLHttpRequest.open + * @param {string} method - The HTTP request method + * to use, e.g., "GET", "POST", "PUT", or "DELETE". + * @param {string} url - The URL to send the request to. + * @param {boolean} [async=true] - true if the method returns without waiting for the response, + * false if the method returns only once the request is complete. + * @param {string} [username=""] - User name for authentication. + * @param {string} [password=""] - Password for authentication. + */ void open(const QString& method, const QString& url, bool async = true, const QString& username = "", const QString& password = ""); + + /**jsdoc + * Sends the request to the server. + * @function XMLHttpRequest.send + * @param {*} [data] - The data to send. + */ void send(); void send(const QScriptValue& data); + + /**jsdoc + * Gets the response headers. + * @function XMLHttpRequest.getAllResponseHeaders + * @returns {string} The response headers, separated by "\n" characters. + */ QScriptValue getAllResponseHeaders() const; + + /**jsdoc + * Gets a response header. + * @function XMLHttpRequest.getResponseHeader + * @param {string} name - + * @returns {string} The response header. + */ QScriptValue getResponseHeader(const QString& name) const; signals: + + /**jsdoc + * Triggered when the request is complete — with or without error (incl. timeout). + * @function XMLHttpRequest.requestComplete + * @returns {Signal} + */ void requestComplete(); private: @@ -111,7 +309,58 @@ private: QScriptValue _onTimeout { QScriptValue::NullValue }; QScriptValue _onReadyStateChange { QScriptValue::NullValue }; ReadyState _readyState { XMLHttpRequestClass::UNSENT }; + + /**jsdoc + *

The type of network error.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + + * + *
ValueNameDescription
0NoErrorNo error.
1ConnectionRefusedErrorThe server refused the connection.
2RemoteHostClosedErrorThe server closed the connection.
3HostNotFoundErrorHost name not found.
4TimeoutErrorConnection timed out
5OperationCanceledErrorOperation canceled by + * {@link XMLHttpRequest.abort}.
6SslHandshakeFailedErrorSSL/TLS handshake failed.
7TemporaryNetworkFailureErrorTemporarily disconnected from the + * network.
8NetworkSessionFailedErrorDisconnection from the network.
9BackgroundRequestNotAllowedErrorBackground request not allowed.
10TooManyRedirectsErrorToo many redirects.
11InsecureRedirectErrorRedirect from secure to insecure protocol.
101ProxyConnectionRefusedErrorConnection to proxy server refused.
102ProxyConnectionClosedErrorProxy server closed the connection.
103ProxyNotFoundErrorProxy host name not found.
104ProxyTimeoutErrorProxy connection timed out.
105ProxyAuthenticationRequiredErrorProxy requires authentication.
201ContentAccessDeniedAccess denied.
202ContentOperationNotPermittedErrorOperation not permitted.
203ContentNotFoundErrorContent not found.
204AuthenticationRequiredErrorAuthentication required.
205ContentReSendErrorResend failed.
206ContentConflictErrorResource state conflict.
207ContentGoneErrorResource no longer available.
401InternalServerErrorInternal server error.
402OperationNotImplementedErrorOperation not supported.
403ServiceUnavailableErrorRequest not able to be handled at this + * time.
301ProtocolUnknownErrorProtocol unknown.
302ProtocolInvalidOperationErrorOperation invalid fro protocol.
99UnknownNetworkErrorUnknown network-related error.
199UnknownProxyErrorUnknown proxy-related error.
299UnknownContentErrorUnknown content-related error.
399ProtocolFailureProtocol error.
499UnknownServerErrorUnknown server response error.
+ * @typedef {number} XMLHttpRequest.NetworkError + */ QNetworkReply::NetworkError _errorCode { QNetworkReply::NoError }; + int _timeout { 0 }; QTimer _timer; int _numRedirects { 0 }; diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index cfb4bb6398..5787295da6 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -392,4 +392,14 @@ inline glm::vec4 extractFov( const glm::mat4& m) { return result; } +inline bool operator<(const glm::vec3& lhs, const glm::vec3& rhs) { + return (lhs.x < rhs.x) || ( + (lhs.x == rhs.x) && ( + (lhs.y < rhs.y) || ( + (lhs.y == rhs.y) && (lhs.z < rhs.z) + ) + ) + ); +} + #endif // hifi_GLMHelpers_h diff --git a/libraries/shared/src/JointData.h b/libraries/shared/src/JointData.h index 7a2420262a..329c2dd151 100644 --- a/libraries/shared/src/JointData.h +++ b/libraries/shared/src/JointData.h @@ -16,6 +16,7 @@ public: // Used by the avatar mixer to describe a single joint // Translations relative to their parent joint // Rotations are absolute (i.e. not relative to parent) and are in rig space. +// No JSDoc because it's not provided as a type to the script engine. class JointData { public: glm::quat rotation; diff --git a/libraries/shared/src/PhysicsHelpers.cpp b/libraries/shared/src/PhysicsHelpers.cpp index 092b9d078a..b7f5242429 100644 --- a/libraries/shared/src/PhysicsHelpers.cpp +++ b/libraries/shared/src/PhysicsHelpers.cpp @@ -10,10 +10,12 @@ // #include "PhysicsHelpers.h" -#include "NumericalConstants.h" + #include +#include "NumericalConstants.h" #include "PhysicsCollisionGroups.h" +#include "SharedUtil.h" // This chunk of code was copied from Bullet-2.82, so we include the Bullet license here: /* @@ -91,7 +93,7 @@ int32_t Physics::getDefaultCollisionMask(int32_t group) { QUuid _sessionID; void Physics::setSessionUUID(const QUuid& sessionID) { - _sessionID = sessionID; + _sessionID = sessionID.isNull() ? AVATAR_SELF_ID : sessionID; } const QUuid& Physics::getSessionUUID() { diff --git a/libraries/shared/src/PickFilter.h b/libraries/shared/src/PickFilter.h index 5e5f5db3bc..d3d6673f50 100644 --- a/libraries/shared/src/PickFilter.h +++ b/libraries/shared/src/PickFilter.h @@ -10,6 +10,7 @@ #define hifi_PickFilter_h #include +#include // adebug class PickFilter { public: @@ -60,6 +61,8 @@ public: // NOT YET IMPLEMENTED PICK_ALL_INTERSECTIONS, // if not set, returns closest intersection, otherwise, returns list of all intersections + PICK_BYPASS_IGNORE, // for debug purposes + NUM_FLAGS, // Not a valid flag }; typedef std::bitset Flags; @@ -93,6 +96,8 @@ public: bool doesWantAllIntersections() const { return _flags[PICK_ALL_INTERSECTIONS]; } + bool bypassIgnore() const { return _flags[PICK_BYPASS_IGNORE]; } + // Helpers for RayPickManager Flags getEntityFlags() const { unsigned int toReturn = 0; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 87cd269c97..7faaebbae9 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -1306,6 +1306,11 @@ void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { } +/**jsdoc + * A triangle in a mesh. + * @typedef {object} MeshFace + * @property {number[]} vertices - The indexes of the three vertices that make up the face. + */ QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { QScriptValue obj = engine->newObject(); obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 3b47bb70c6..a7b7c4edf4 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -687,8 +687,10 @@ namespace graphics { using MeshPointer = std::shared_ptr; /**jsdoc - * A handle for a mesh in an entity, such as returned by {@link Entities.getMeshes}. + * A mesh, such as returned by {@link Entities.getMeshes} or {@link Model} API functions. + * * @class MeshProxy + * @hideconstructor * * @hifi-interface * @hifi-client-entity @@ -705,16 +707,16 @@ public: virtual MeshPointer getMeshPointer() const = 0; /**jsdoc - * Get the number of vertices in the mesh. + * Gets the number of vertices in the mesh. * @function MeshProxy#getNumVertices * @returns {number} Integer number of vertices in the mesh. */ Q_INVOKABLE virtual int getNumVertices() const = 0; /**jsdoc - * Get the position of a vertex in the mesh. + * Gets the position of a vertex in the mesh. * @function MeshProxy#getPos - * @param {number} index - Integer index of the mesh vertex. + * @param {number} index - Integer index of the vertex. * @returns {Vec3} Local position of the vertex relative to the mesh. */ Q_INVOKABLE virtual glm::vec3 getPos(int index) const = 0; diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index c60d1c2574..cb9ad41fd0 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -189,7 +189,7 @@ uint32_t ShapeInfo::getNumSubShapes() const { return 0; case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_SIMPLE_COMPOUND: - return _pointCollection.size(); + return (uint32_t)_pointCollection.size(); case SHAPE_TYPE_MULTISPHERE: case SHAPE_TYPE_SIMPLE_HULL: case SHAPE_TYPE_STATIC_MESH: @@ -200,10 +200,10 @@ uint32_t ShapeInfo::getNumSubShapes() const { } } -int ShapeInfo::getLargestSubshapePointCount() const { - int numPoints = 0; - for (int i = 0; i < _pointCollection.size(); ++i) { - int n = _pointCollection[i].size(); +uint32_t ShapeInfo::getLargestSubshapePointCount() const { + uint32_t numPoints = 0; + for (uint32_t i = 0; i < (uint32_t)_pointCollection.size(); ++i) { + uint32_t n = (uint32_t)_pointCollection[i].size(); if (n > numPoints) { numPoints = n; } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 6b0f981b24..676f38d087 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -12,7 +12,7 @@ #ifndef hifi_ShapeInfo_h #define hifi_ShapeInfo_h -#include +#include #include #include #include @@ -53,11 +53,11 @@ class ShapeInfo { public: - using PointList = QVector; - using PointCollection = QVector; - using TriangleIndices = QVector; + using PointList = std::vector; + using PointCollection = std::vector; + using TriangleIndices = std::vector; using SphereData = glm::vec4; - using SphereCollection = QVector; + using SphereCollection = std::vector; static QString getNameForShapeType(ShapeType type); static ShapeType getShapeTypeForName(QString string); @@ -85,7 +85,7 @@ public: TriangleIndices& getTriangleIndices() { return _triangleIndices; } const TriangleIndices& getTriangleIndices() const { return _triangleIndices; } - int getLargestSubshapePointCount() const; + uint32_t getLargestSubshapePointCount() const; float computeVolume() const; diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h index c19babbc7f..8300359b65 100644 --- a/libraries/shared/src/SpatialParentFinder.h +++ b/libraries/shared/src/SpatialParentFinder.h @@ -21,7 +21,7 @@ using SpatiallyNestableWeakPointer = std::weak_ptr; using SpatiallyNestablePointer = std::shared_ptr; class SpatialParentTree { public: - virtual SpatiallyNestablePointer findByID(const QUuid& id) const { return nullptr; } + virtual SpatiallyNestablePointer findByID(const QUuid& id) const = 0; }; class SpatialParentFinder : public Dependency { diff --git a/libraries/shared/src/VariantMapToScriptValue.cpp b/libraries/shared/src/VariantMapToScriptValue.cpp index b3c02d99f4..156a438bd7 100644 --- a/libraries/shared/src/VariantMapToScriptValue.cpp +++ b/libraries/shared/src/VariantMapToScriptValue.cpp @@ -28,7 +28,7 @@ QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine) break; case QVariant::String: case QVariant::Url: - return scriptEngine.newVariant(qValue); + return qValue.toString(); break; case QVariant::Map: { QVariantMap childMap = qValue.toMap(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index f0b27904ae..34184057e0 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -33,13 +33,20 @@ #include "MainWindow.h" /**jsdoc + * The OffscreenFlags API enables gamepad joystick navigation of UI. + * + *

This API currently has no effect and is not used.

+ * * @namespace OffscreenFlags * * @hifi-interface * @hifi-client-entity * @hifi-avatar - * @property {boolean} navigationFocused - * @property {boolean} navigationFocusDisabled + * + * @property {boolean} navigationFocused - true if UI has joystick navigation focus, false if it + * doesn't. + * @property {boolean} navigationFocusDisabled - true if UI joystick navigation focus is disabled, + * false if it isn't. */ // Needs to match the constants in resources/qml/Global.js @@ -72,12 +79,14 @@ public: signals: /**jsdoc + * Triggered when the value of the navigationFocused property changes. * @function OffscreenFlags.navigationFocusedChanged * @returns {Signal} */ void navigationFocusedChanged(); /**jsdoc + * Triggered when the value of the navigationFocusDisabled property changes. * @function OffscreenFlags.navigationFocusDisabledChanged * @returns {Signal} */ diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 770f8ec965..feca148614 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -12,17 +12,27 @@ #include "QmlWindowClass.h" /**jsdoc + * A OverlayWebWindow displays an HTML window inside Interface. + * + *

Create using new OverlayWebWindow(...).

+ * * @class OverlayWebWindow - * @param {OverlayWindow.Properties} [properties=null] + * @param {string|OverlayWindow.Properties} [titleOrProperties="WebWindow"] - The window's title or initial property values. + * @param {string} [source="about:blank"] - The URL of the HTML to display. Not used unless the first parameter is the window + * title. + * @param {number} [width=0] - The width of the window interior, in pixels. Not used unless the first parameter is the window + * title. + * @param {number} [height=0] - The height of the window interior, in pixels. Not used unless the first parameter is the + * window title. * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {string} url - Read-only. - * @property {Vec2} position - * @property {Vec2} size - * @property {boolean} visible + * @property {string} url - The URL of the HTML displayed in the window. Read-only. + * @property {Vec2} position - The position of the window, in pixels. + * @property {Vec2} size - The size of the window interior, in pixels. + * @property {boolean} visible - true if the window is visible, false if it isn't. * * @borrows OverlayWindow.initQml as initQml * @borrows OverlayWindow.isVisible as isVisible @@ -35,9 +45,9 @@ * @borrows OverlayWindow.raise as raise * @borrows OverlayWindow.close as close * @borrows OverlayWindow.getEventBridge as getEventBridge - * @borrows OverlayWindow.sendToQml as sendToQml - * @borrows OverlayWindow.clearDebugWindow as clearDebugWindow - * @borrows OverlayWindow.emitScriptEvent as emitScriptEvent + * @comment OverlayWindow.sendToQml - Not applicable to OverlayWebWindow; documented separately. + * @comment OverlayWindow.clearDebugWindow - Not applicable to OverlayWebWindow; documented separately. + * @comment OverlayWindow.emitScriptEvent - Documented separately. * @borrows OverlayWindow.emitWebEvent as emitWebEvent * @borrows OverlayWindow.visibleChanged as visibleChanged * @borrows OverlayWindow.positionChanged as positionChanged @@ -45,14 +55,87 @@ * @borrows OverlayWindow.moved as moved * @borrows OverlayWindow.resized as resized * @borrows OverlayWindow.closed as closed - * @borrows OverlayWindow.fromQml as fromQml + * @comment OverlayWindow.fromQml - Not applicable to OverlayWebWindow; documented separately. * @borrows OverlayWindow.scriptEventReceived as scriptEventReceived - * @borrows OverlayWindow.webEventReceived as webEventReceived + * @comment OverlayWindow.webEventReceived - Documented separately. * @borrows OverlayWindow.hasMoved as hasMoved * @borrows OverlayWindow.hasClosed as hasClosed * @borrows OverlayWindow.qmlToScript as qmlToScript */ +/**jsdoc + * @function OverlayWebWindow.clearDebugWindow + * @deprecated This method is deprecated and will be removed. + */ + +/**jsdoc + * @function OverlayWebWindow.sendToQML + * @param {string | object} message - Message. + * @deprecated This method is deprecated and will be removed. + */ + +/**jsdoc + * @function OverlayWebWindow.fromQML + * @param {object} message - Message. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + +/**jsdoc + * Sends a message to the HTML page. To receive the message, the HTML page's script must connect to the EventBridge + * that is automatically provided for the script: + *
EventBridge.scriptEventReceived.connect(function(message) {
+ *     ...
+ * });
+ * @function OverlayWebWindow.emitScriptEvent + * @param {string|object} message - The message to send to the embedded HTML page. + * @example Send and receive messages with an HTML window. + * // JavaScript file. + * + * var overlayWebWindow = new OverlayWebWindow({ + * title: "Overlay Web Window", + * source: Script.resolvePath("OverlayWebWindow.html"), + * width: 400, + * height: 300 + * }); + * + * overlayWebWindow.webEventReceived.connect(function (message) { + * print("Message received: " + message); + * }); + * + * Script.setTimeout(function () { + * overlayWebWindow.emitScriptEvent("Hello world!"); + * }, 2000); + * + * @example + * // HTML file, "OverlayWebWindow.html". + * + * + * + * + * + * + * + *

...

+ * + * + * + */ + +/**jsdoc + * Triggered when a message from the HTML page is received. The HTML page can send a message by calling: + *
EventBridge.emitWebEvent(message);
+ * @function OverlayWebWindow.webEventReceived + * @param {string|object} message - The message received. + * @returns {Signal} + */ + + // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT @@ -74,24 +157,29 @@ public: public slots: /**jsdoc + * Gets the URL of the HTML displayed. * @function OverlayWebWindow.getURL - * @returns {string} + * @returns {string} - The URL of the HTML page displayed. */ QString getURL(); + /**jsdoc + * Loads HTML into the window, replacing current window content. * @function OverlayWebWindow.setURL - * @param {string} url + * @param {string} url - The URL of the HTML to display. */ void setURL(const QString& url); /**jsdoc + * Injects a script into the HTML page, replacing any currently injected script. * @function OverlayWebWindow.setScriptURL - * @param {string} script + * @param {string} url - The URL of the script to inject. */ void setScriptURL(const QString& script); signals: /**jsdoc + * Triggered when the window's URL changes. * @function OverlayWebWindow.urlChanged * @returns {Signal} */ diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index abce5479c4..13a289a5fd 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -90,12 +90,13 @@ QmlWindowClass::QmlWindowClass(bool restricted) : _restricted(restricted) { } /**jsdoc + * Properties used to initialize an {@link OverlayWindow} or {@link OverlayWebWindow}. * @typedef {object} OverlayWindow.Properties - * @property {string} title - * @property {string} source - * @property {number} width - * @property {number} height - * @property {boolean} visible + * @property {string} [title="WebWindow] - The window title. + * @property {string} [source] - The source of the QML or HTML to display. + * @property {number} [width=0] - The width of the window interior, in pixels. + * @property {number} [height=0] - The height of the window interior, in pixels. + * @property {boolean} [visible=true] - true if the window should be visible, false if it shouldn't. */ void QmlWindowClass::initQml(QVariantMap properties) { #ifndef DISABLE_QML diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 8aad9a8b36..3a2f202bd3 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -20,16 +20,29 @@ class QScriptEngine; class QScriptContext; /**jsdoc + * A OverlayWindow displays a QML window inside Interface. + * + *

The QML can optionally include a WebView control that embeds an HTML-based windows. (The WebView + * control is defined by a "WebView.qml" file included in the Interface install.) Alternatively, an {@link OverlayWebWindow} + * can be used for HTML-based windows.

+ * + *

Create using new OverlayWindow(...).

+ * * @class OverlayWindow - * @param {OverlayWindow.Properties} [properties=null] + * @param {string|OverlayWindow.Properties} [titleOrProperties="WebWindow"] - The window's title or initial property values. + * @param {string} [source] - The source of the QML to display. Not used unless the first parameter is the window title. + * @param {number} [width=0] - The width of the window interior, in pixels. Not used unless the first parameter is the window + * title. + * @param {number} [height=0] - The height of the window interior, in pixels. Not used unless the first parameter is the + * window title. * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {Vec2} position - * @property {Vec2} size - * @property {boolean} visible + * @property {Vec2} position - The position of the window, in pixels. + * @property {Vec2} size - The size of the window interior, in pixels. + * @property {boolean} visible - true if the window is visible, false if it isn't. */ // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping @@ -55,8 +68,10 @@ public: /**jsdoc * @function OverlayWindow.initQml - * @param {OverlayWindow.Properties} properties + * @param {OverlayWindow.Properties} properties - Properties. + * @deprecated This method is deprecated and will be removed. */ + // FIXME: This shouldn't be in the API. It is an internal function used in object construction. Q_INVOKABLE virtual void initQml(QVariantMap properties); QQuickItem* asQuickItem() const; @@ -66,150 +81,220 @@ public: public slots: /**jsdoc + * Gets whether the window is shown or hidden. * @function OverlayWindow.isVisible - * @returns {boolean} + * @returns {boolean} code>true if the window is shown, false if it is hidden. */ bool isVisible(); /**jsdoc + * Shows or hides the window. * @function OverlayWindow.setVisible - * @param {boolean} visible + * @param {boolean} visible - code>true to show the window, false to hide it. */ void setVisible(bool visible); /**jsdoc + * Gets the position of the window. * @function OverlayWindow.getPosition - * @returns {Vec2} + * @returns {Vec2} The position of the window, in pixels. */ glm::vec2 getPosition(); /**jsdoc + * Sets the position of the window, from a {@link Vec2}. * @function OverlayWindow.setPosition - * @param {Vec2} position + * @param {Vec2} position - The position of the window, in pixels. */ void setPosition(const glm::vec2& position); /**jsdoc + * Sets the position of the window, from a pair of numbers. * @function OverlayWindow.setPosition - * @param {number} x - * @param {number} y + * @param {number} x - The x position of the window, in pixels. + * @param {number} y - The y position of the window, in pixels. */ void setPosition(int x, int y); /**jsdoc + * Gets the size of the window interior. * @function OverlayWindow.getSize - * @returns {Vec2} + * @returns {Vec2} The size of the window interior, in pixels. */ glm::vec2 getSize(); /**jsdoc + * Sets the size of the window interior, from a {@link Vec2}. * @function OverlayWindow.setSize - * @param {Vec2} size + * @param {Vec2} size - The size of the window interior, in pixels. */ void setSize(const glm::vec2& size); /**jsdoc + * Sets the size of the window interior, from a pair of numbers. * @function OverlayWindow.setSize - * @param {number} width - * @param {number} height + * @param {number} width - The width of the window interior, in pixels. + * @param {number} height - The height of the window interior, in pixels. */ void setSize(int width, int height); /**jsdoc + * Sets the window title. * @function OverlayWindow.setTitle - * @param {string} title + * @param {string} title - The window title. */ void setTitle(const QString& title); - /**jsdoc + * Raises the window to the top. * @function OverlayWindow.raise */ Q_INVOKABLE void raise(); /**jsdoc + * Closes the window. + *

Note: The window also closes when the script ends.

* @function OverlayWindow.close */ Q_INVOKABLE void close(); /**jsdoc * @function OverlayWindow.getEventBridge - * @returns {object} + * @returns {object} Object. + * @deprecated This method is deprecated and will be removed. */ + // Shouldn't be in the API: It returns the OverlayWindow object, not the even bridge. Q_INVOKABLE QObject* getEventBridge() { return this; }; /**jsdoc + * Sends a message to the QML. To receive the message, the QML must implement a function: + *
function fromScript(message) {
+     *   ...
+     * }
* @function OverlayWindow.sendToQml - * @param {object} message + * @param {string | object} message - The message to send to the QML. + * @example Send and receive messages with a QML window. + * // JavaScript file. + * + * var overlayWindow = new OverlayWindow({ + * title: "Overlay Window", + * source: Script.resolvePath("OverlayWindow.qml"), + * width: 400, + * height: 300 + * }); + * + * overlayWindow.fromQml.connect(function (message) { + * print("Message received: " + message); + * }); + * + * Script.setTimeout(function () { + * overlayWindow.sendToQml("Hello world!"); + * }, 2000); + * + * @example + * // QML file, "OverlayWindow.qml". + * + * import QtQuick 2.5 + * import QtQuick.Controls 1.4 + * + * Rectangle { + * width: parent.width + * height: parent.height + * + * function fromScript(message) { + * text.text = message; + * sendToScript("Hello back!"); + * } + * + * Label{ + * id: text + * anchors.centerIn : parent + * text : "..." + * } + * } */ // Scripts can use this to send a message to the QML object void sendToQml(const QVariant& message); /**jsdoc + * Calls a clearWindow() function if present in the QML. * @function OverlayWindow.clearDebugWindow */ void clearDebugWindow(); - /**jsdoc + * Sends a message to an embedded HTML web page. To receive the message, the HTML page's script must connect to the + * EventBridge that is automatically provided for the script: + *
EventBridge.scriptEventReceived.connect(function(message) {
+     *     ...
+     * });
* @function OverlayWindow.emitScriptEvent - * @param {object} message + * @param {string|object} message - The message to send to the embedded HTML page. */ // QmlWindow content may include WebView requiring EventBridge. void emitScriptEvent(const QVariant& scriptMessage); /**jsdoc * @function OverlayWindow.emitWebEvent - * @param {object} message + * @param {object|string} message - The message. + * @deprecated This function is deprecated and will be removed. */ void emitWebEvent(const QVariant& webMessage); signals: /**jsdoc + * Triggered when the window is hidden or shown. * @function OverlayWindow.visibleChanged * @returns {Signal} */ void visibleChanged(); /**jsdoc + * Triggered when the window changes position. * @function OverlayWindow.positionChanged * @returns {Signal} */ void positionChanged(); /**jsdoc + * Triggered when the window changes size. * @function OverlayWindow.sizeChanged * @returns {Signal} */ void sizeChanged(); /**jsdoc + * Triggered when the window changes position. * @function OverlayWindow.moved - * @param {Vec2} position + * @param {Vec2} position - The position of the window, in pixels. * @returns {Signal} */ void moved(glm::vec2 position); /**jsdoc + * Triggered when the window changes size. * @function OverlayWindow.resized - * @param {Size} size + * @param {Size} size - The size of the window interior, in pixels. * @returns {Signal} */ void resized(QSizeF size); /**jsdoc + * Triggered when the window is closed. * @function OverlayWindow.closed * @returns {Signal} */ void closed(); /**jsdoc + * Triggered when a message from the QML page is received. The QML page can send a message (string or object) by calling: + *
sendToScript(message);
* @function OverlayWindow.fromQml - * @param {object} message + * @param {object} message - The message received. * @returns {Signal} */ // Scripts can connect to this signal to receive messages from the QML object @@ -218,15 +303,18 @@ signals: /**jsdoc * @function OverlayWindow.scriptEventReceived - * @param {object} message + * @param {object} message - The message. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ // QmlWindow content may include WebView requiring EventBridge. void scriptEventReceived(const QVariant& message); /**jsdoc + * Triggered when a message from an embedded HTML page is received. The HTML page can send a message by calling: + *
EventBridge.emitWebEvent(message);
* @function OverlayWindow.webEventReceived - * @param {object} message + * @param {string|object} message - The message received. * @returns {Signal} */ void webEventReceived(const QVariant& message); @@ -235,22 +323,25 @@ protected slots: /**jsdoc * @function OverlayWindow.hasMoved - * @param {Vec2} position - * @returns {Signal} + * @param {Vec2} position - Position. + * @deprecated This method is deprecated and will be removed. */ + // Shouldn't be in the API: it is just connected to in order to emit a signal. void hasMoved(QVector2D); /**jsdoc * @function OverlayWindow.hasClosed - * @returns {Signal} + * @deprecated This method is deprecated and will be removed. */ + // Shouldn't be in the API: it is just connected to in order to emit a signal. void hasClosed(); /**jsdoc * @function OverlayWindow.qmlToScript - * @param {object} message - * @returns {Signal} + * @param {object} message - Message. + * @deprecated This method is deprecated and will be removed. */ + // Shouldn't be in the API: it is just connected to in order to emit a signal. void qmlToScript(const QVariant& message); protected: diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index dd5ec2a845..f8e4189d12 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -217,6 +217,7 @@ Q_DECLARE_METATYPE(TabletButtonsProxyModel*); *

Retrieve an existing tablet or create a new tablet using {@link Tablet.getTablet}.

* * @class TabletProxy + * @hideconstructor * * @hifi-interface * @hifi-client-entity @@ -422,6 +423,12 @@ public: *
EventBridge.scriptEventReceived.connect(function(message) {
      *     ...
      * });
+ *

Warning: The EventBridge object is not necessarily set up immediately ready for the web + * page's script to use. A simple workaround that normally works is to add a delay before calling + * EventBridge.scriptEventReceived.connect(...). A better solution is to periodically call + * EventBridge.scriptEventReceived.connect(...) and then EventBridge.emitWebEvent(...) to send a + * message to the Interface script, and have that send a message back using emitScriptEvent(...); when the + * return message is received, the EventBridge is ready for use.

* @function TabletProxy#emitScriptEvent * @param {string|object} message - The message to send to the web page. */ @@ -586,6 +593,7 @@ Q_DECLARE_METATYPE(TabletProxy*); *

Create a new button using {@link TabletProxy#addButton}.

* * @class TabletButtonProxy + * @hideconstructor * * @hifi-interface * @hifi-client-entity diff --git a/libraries/ui/src/ui/types/FileTypeProfile.cpp b/libraries/ui/src/ui/types/FileTypeProfile.cpp index 615e80b85c..3b9ed74200 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.cpp +++ b/libraries/ui/src/ui/types/FileTypeProfile.cpp @@ -11,6 +11,8 @@ #include "FileTypeProfile.h" +#include +#include #include #include "RequestFilters.h" @@ -18,14 +20,28 @@ #if !defined(Q_OS_ANDROID) static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; +static std::set FileTypeProfile_instances; +static std::mutex FileTypeProfile_mutex; + FileTypeProfile::FileTypeProfile(QQmlContext* parent) : ContextAwareProfile(parent) { static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; setHttpUserAgent(WEB_ENGINE_USER_AGENT); + setStorageName(QML_WEB_ENGINE_STORAGE_NAME); + setOffTheRecord(false); + auto requestInterceptor = new RequestInterceptor(this); setRequestInterceptor(requestInterceptor); + + std::lock_guard lock(FileTypeProfile_mutex); + FileTypeProfile_instances.insert(this); +} + +FileTypeProfile::~FileTypeProfile() { + std::lock_guard lock(FileTypeProfile_mutex); + FileTypeProfile_instances.erase(this); } void FileTypeProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { @@ -37,5 +53,11 @@ void FileTypeProfile::registerWithContext(QQmlContext* context) { context->setContextProperty("FileTypeProfile", new FileTypeProfile(context)); } +void FileTypeProfile::clearCache() { + std::lock_guard lock(FileTypeProfile_mutex); + foreach (auto instance, FileTypeProfile_instances) { + instance->clearHttpCache(); + } +} #endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeProfile.h b/libraries/ui/src/ui/types/FileTypeProfile.h index 7ddfdd0aed..8d8b921846 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.h +++ b/libraries/ui/src/ui/types/FileTypeProfile.h @@ -25,8 +25,12 @@ class FileTypeProfile : public ContextAwareProfile { public: static void registerWithContext(QQmlContext* parent); + static void clearCache(); + protected: FileTypeProfile(QQmlContext* parent); + virtual ~FileTypeProfile(); + class RequestInterceptor : public Parent::RequestInterceptor { public: RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {} diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp index 3c5701cc52..3f2d50e8fa 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp @@ -11,6 +11,8 @@ #include "HFWebEngineProfile.h" +#include +#include #include #include "RequestFilters.h" @@ -19,12 +21,24 @@ static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; +static std::set HFWebEngineProfile_instances; +static std::mutex HFWebEngineProfile_mutex; + HFWebEngineProfile::HFWebEngineProfile(QQmlContext* parent) : Parent(parent) { setStorageName(QML_WEB_ENGINE_STORAGE_NAME); + setOffTheRecord(false); // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user setRequestInterceptor(new RequestInterceptor(this)); + + std::lock_guard lock(HFWebEngineProfile_mutex); + HFWebEngineProfile_instances.insert(this); +} + +HFWebEngineProfile::~HFWebEngineProfile() { + std::lock_guard lock(HFWebEngineProfile_mutex); + HFWebEngineProfile_instances.erase(this); } void HFWebEngineProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { @@ -35,4 +49,11 @@ void HFWebEngineProfile::registerWithContext(QQmlContext* context) { context->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(context)); } +void HFWebEngineProfile::clearCache() { + std::lock_guard lock(HFWebEngineProfile_mutex); + foreach (auto instance, HFWebEngineProfile_instances) { + instance->clearHttpCache(); + } +} + #endif diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.h b/libraries/ui/src/ui/types/HFWebEngineProfile.h index 6b84ad6f80..872cb31901 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.h +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.h @@ -23,8 +23,12 @@ class HFWebEngineProfile : public ContextAwareProfile { public: static void registerWithContext(QQmlContext* parent); + static void clearCache(); + protected: HFWebEngineProfile(QQmlContext* parent); + virtual ~HFWebEngineProfile(); + class RequestInterceptor : public Parent::RequestInterceptor { public: RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {} diff --git a/libraries/workload/src/workload/Space.cpp b/libraries/workload/src/workload/Space.cpp index f045c8311f..5704ba8c4d 100644 --- a/libraries/workload/src/workload/Space.cpp +++ b/libraries/workload/src/workload/Space.cpp @@ -127,6 +127,18 @@ uint32_t Space::copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const { return numCopied; } +uint32_t Space::copySelectedProxyValues(Proxy::Vector& proxies, const workload::indexed_container::Indices& indices) const { + std::unique_lock lock(_proxiesMutex); + uint32_t numCopied = 0; + for (auto index : indices) { + if (isAllocatedID(index) && (index < (Index)_proxies.size())) { + proxies.push_back(_proxies[index]); + ++numCopied; + } + } + return numCopied; +} + const Owner Space::getOwner(int32_t proxyID) const { std::unique_lock lock(_proxiesMutex); if (isAllocatedID(proxyID) && (proxyID < (Index)_proxies.size())) { diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index 7dcb2217f7..d189d48156 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -47,6 +47,7 @@ public: void categorizeAndGetChanges(std::vector& changes); uint32_t copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const; + uint32_t copySelectedProxyValues(Proxy::Vector& proxies, const workload::indexed_container::Indices& indices) const; const Owner getOwner(int32_t proxyID) const; uint8_t getRegion(int32_t proxyID) const; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c88bb8a00d..1448e14c72 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -36,6 +36,12 @@ if (NOT SERVER_ONLY AND NOT ANDROID) add_subdirectory(${DIR}) set(DIR "hifiLeapMotion") add_subdirectory(${DIR}) + + if (WIN32) + set(DIR "hifiOsc") + add_subdirectory(${DIR}) + endif() + endif() # server-side plugins diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt index 7d2616e4d6..ad4f78698c 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -6,7 +6,7 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -if (APPLE OR WIN32) +if (WIN32) set(TARGET_NAME hifiNeuron) setup_hifi_plugin(Qml) diff --git a/plugins/hifiOsc/CMakeLists.txt b/plugins/hifiOsc/CMakeLists.txt new file mode 100644 index 0000000000..cb8b437ab6 --- /dev/null +++ b/plugins/hifiOsc/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Created by Anthony Thibault on 2019/8/24 +# Copyright 2019 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# + +set(TARGET_NAME hifiOsc) +setup_hifi_plugin(Qml) +link_hifi_libraries(shared controllers ui plugins input-plugins display-plugins) +target_liblo() + + diff --git a/plugins/hifiOsc/src/OscPlugin.cpp b/plugins/hifiOsc/src/OscPlugin.cpp new file mode 100644 index 0000000000..788342971d --- /dev/null +++ b/plugins/hifiOsc/src/OscPlugin.cpp @@ -0,0 +1,636 @@ +// +// OscPlugin.cpp +// +// Created by Anthony Thibault on 2019/8/24 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OscPlugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(inputplugins) +Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") + +const char* OscPlugin::NAME = "Open Sound Control (OSC)"; +const char* OscPlugin::OSC_ID_STRING = "Open Sound Control (OSC)"; +const bool DEFAULT_ENABLED = false; + +enum class FaceCap { + BrowsU_C = 0, + BrowsD_L, + BrowsD_R, + BrowsU_L, + BrowsU_R, + EyeUp_L, + EyeUp_R, + EyeDown_L, + EyeDown_R, + EyeIn_L, + EyeIn_R, + EyeOut_L, + EyeOut_R, + EyeBlink_L, + EyeBlink_R, + EyeSquint_L, + EyeSquint_R, + EyeOpen_L, + EyeOpen_R, + Puff, + CheekSquint_L, + CheekSquint_R, + NoseSneer_L, + NoseSneer_R, + JawOpen, + JawFwd, + JawLeft, + JawRight, + LipsFunnel, + LipsPucker, + MouthLeft, + MouthRight, + LipsUpperClose, + LipsLowerClose, + MouthShrugUpper, + MouthShrugLower, + MouthClose, + MouthSmile_L, + MouthSmile_R, + MouthFrown_L, + MouthFrown_R, + MouthDimple_L, + MouthDimple_R, + MouthUpperUp_L, + MouthUpperUp_R, + MouthLowerDown_L, + MouthLowerDown_R, + MouthPress_L, + MouthPress_R, + LipsStretch_L, + LipsStretch_R, + TongueOut, + BlendshapeCount +}; + +// used to mirror left/right shapes from FaceCap. +// i.e. right and left shapes are swapped. +FaceCap faceMirrorMap[(int)FaceCap::BlendshapeCount] = { + FaceCap::BrowsU_C, + FaceCap::BrowsD_R, + FaceCap::BrowsD_L, + FaceCap::BrowsU_R, + FaceCap::BrowsU_L, + FaceCap::EyeUp_R, + FaceCap::EyeUp_L, + FaceCap::EyeDown_R, + FaceCap::EyeDown_L, + FaceCap::EyeIn_R, + FaceCap::EyeIn_L, + FaceCap::EyeOut_R, + FaceCap::EyeOut_L, + FaceCap::EyeBlink_R, + FaceCap::EyeBlink_L, + FaceCap::EyeSquint_R, + FaceCap::EyeSquint_L, + FaceCap::EyeOpen_R, + FaceCap::EyeOpen_L, + FaceCap::Puff, + FaceCap::CheekSquint_R, + FaceCap::CheekSquint_L, + FaceCap::NoseSneer_R, + FaceCap::NoseSneer_L, + FaceCap::JawOpen, + FaceCap::JawFwd, + FaceCap::JawRight, + FaceCap::JawLeft, + FaceCap::LipsFunnel, + FaceCap::LipsPucker, + FaceCap::MouthRight, + FaceCap::MouthLeft, + FaceCap::LipsUpperClose, + FaceCap::LipsLowerClose, + FaceCap::MouthShrugUpper, + FaceCap::MouthShrugLower, + FaceCap::MouthClose, + FaceCap::MouthSmile_R, + FaceCap::MouthSmile_L, + FaceCap::MouthFrown_R, + FaceCap::MouthFrown_L, + FaceCap::MouthDimple_R, + FaceCap::MouthDimple_L, + FaceCap::MouthUpperUp_R, + FaceCap::MouthUpperUp_L, + FaceCap::MouthLowerDown_R, + FaceCap::MouthLowerDown_L, + FaceCap::MouthPress_R, + FaceCap::MouthPress_L, + FaceCap::LipsStretch_R, + FaceCap::LipsStretch_L, + FaceCap::TongueOut +}; + +static const char* STRINGS[FaceCap::BlendshapeCount] = { + "BrowsU_C", + "BrowsD_L", + "BrowsD_R", + "BrowsU_L", + "BrowsU_R", + "EyeUp_L", + "EyeUp_R", + "EyeDown_L", + "EyeDown_R", + "EyeIn_L", + "EyeIn_R", + "EyeOut_L", + "EyeOut_R", + "EyeBlink_L", + "EyeBlink_R", + "EyeSquint_L", + "EyeSquint_R", + "EyeOpen_L", + "EyeOpen_R", + "Puff", + "CheekSquint_L", + "CheekSquint_R", + "NoseSneer_L", + "NoseSneer_R", + "JawOpen", + "JawFwd", + "JawLeft", + "JawRight", + "LipsFunnel", + "LipsPucker", + "MouthLeft", + "MouthRight", + "LipsUpperClose", + "LipsLowerClose", + "MouthShrugUpper", + "MouthShrugLower", + "MouthClose", + "MouthSmile_L", + "MouthSmile_R", + "MouthFrown_L", + "MouthFrown_R", + "MouthDimple_L", + "MouthDimple_R", + "MouthUpperUp_L", + "MouthUpperUp_R", + "MouthLowerDown_L", + "MouthLowerDown_R", + "MouthPress_L", + "MouthPress_R", + "LipsStretch_L", + "LipsStretch_R", + "TongueOut" +}; + +static enum controller::StandardAxisChannel CHANNELS[FaceCap::BlendshapeCount] = { + controller::BROWSU_C, + controller::BROWSD_L, + controller::BROWSD_R, + controller::BROWSU_L, + controller::BROWSU_R, + controller::EYEUP_L, + controller::EYEUP_R, + controller::EYEDOWN_L, + controller::EYEDOWN_R, + controller::EYEIN_L, + controller::EYEIN_R, + controller::EYEOUT_L, + controller::EYEOUT_R, + controller::EYEBLINK_L, + controller::EYEBLINK_R, + controller::EYESQUINT_L, + controller::EYESQUINT_R, + controller::EYEOPEN_L, + controller::EYEOPEN_R, + controller::PUFF, + controller::CHEEKSQUINT_L, + controller::CHEEKSQUINT_R, + controller::NOSESNEER_L, + controller::NOSESNEER_R, + controller::JAWOPEN, + controller::JAWFWD, + controller::JAWLEFT, + controller::JAWRIGHT, + controller::LIPSFUNNEL, + controller::LIPSPUCKER, + controller::MOUTHLEFT, + controller::MOUTHRIGHT, + controller::LIPSUPPERCLOSE, + controller::LIPSLOWERCLOSE, + controller::MOUTHSHRUGUPPER, + controller::MOUTHSHRUGLOWER, + controller::MOUTHCLOSE, + controller::MOUTHSMILE_L, + controller::MOUTHSMILE_R, + controller::MOUTHFROWN_L, + controller::MOUTHFROWN_R, + controller::MOUTHDIMPLE_L, + controller::MOUTHDIMPLE_R, + controller::MOUTHUPPERUP_L, + controller::MOUTHUPPERUP_R, + controller::MOUTHLOWERDOWN_L, + controller::MOUTHLOWERDOWN_R, + controller::MOUTHPRESS_L, + controller::MOUTHPRESS_R, + controller::LIPSSTRETCH_L, + controller::LIPSSTRETCH_R, + controller::TONGUEOUT +}; + + +void OscPlugin::init() { + + _inputDevice = std::make_shared(); + _inputDevice->setContainer(this); + + { + std::lock_guard guard(_dataMutex); + _blendshapeValues.assign((int)FaceCap::BlendshapeCount, 0.0f); + _blendshapeValidFlags.assign((int)FaceCap::BlendshapeCount, false); + _headRot = glm::quat(); + _headRotValid = false; + _headTransTarget = extractTranslation(_lastInputCalibrationData.defaultHeadMat); + _headTransSmoothed = extractTranslation(_lastInputCalibrationData.defaultHeadMat); + _headTransValid = false; + _eyeLeftRot = glm::quat(); + _eyeLeftRotValid = false; + _eyeRightRot = glm::quat(); + _eyeRightRotValid = false; + } + + loadSettings(); + + auto preferences = DependencyManager::get(); + static const QString OSC_PLUGIN { OscPlugin::NAME }; + { + auto getter = [this]()->bool { return _enabled; }; + auto setter = [this](bool value) { + _enabled = value; + saveSettings(); + if (!_enabled) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->clearState(); + restartServer(); + }); + } + }; + auto preference = new CheckPreference(OSC_PLUGIN, "Enabled", getter, setter); + preferences->addPreference(preference); + } + { + auto debugGetter = [this]()->bool { return _debug; }; + auto debugSetter = [this](bool value) { + _debug = value; + saveSettings(); + }; + auto preference = new CheckPreference(OSC_PLUGIN, "Extra Debugging", debugGetter, debugSetter); + preferences->addPreference(preference); + } + + { + static const int MIN_PORT_NUMBER { 0 }; + static const int MAX_PORT_NUMBER { 65535 }; + + auto getter = [this]()->float { return (float)_serverPort; }; + auto setter = [this](float value) { + _serverPort = (int)value; + saveSettings(); + restartServer(); + }; + auto preference = new SpinnerPreference(OSC_PLUGIN, "Server Port", getter, setter); + + preference->setMin(MIN_PORT_NUMBER); + preference->setMax(MAX_PORT_NUMBER); + preference->setStep(1); + preferences->addPreference(preference); + } +} + +bool OscPlugin::isSupported() const { + // networking/UDP is pretty much always available... + return true; +} + +static void errorHandlerFunc(int num, const char* msg, const char* path) { + qDebug(inputplugins) << "OscPlugin: server error" << num << "in path" << path << ":" << msg; +} + +static int genericHandlerFunc(const char* path, const char* types, lo_arg** argv, + int argc, void* data, void* user_data) { + + OscPlugin* container = reinterpret_cast(user_data); + assert(container); + + QString key(path); + std::lock_guard guard(container->_dataMutex); + + // Special case: decode blendshapes from face-cap iPhone app. + // http://www.bannaflak.com/face-cap/livemode.html + if (path[0] == '/' && path[1] == 'W' && argc == 2 && types[0] == 'i' && types[1] == 'f') { + int index = argv[0]->i; + if (index >= 0 && index < (int)FaceCap::BlendshapeCount) { + int mirroredIndex = (int)faceMirrorMap[index]; + container->_blendshapeValues[mirroredIndex] = argv[1]->f; + container->_blendshapeValidFlags[mirroredIndex] = true; + } + } + + // map /HT to head translation + if (path[0] == '/' && path[1] == 'H' && path[2] == 'T' && + types[0] == 'f' && types[1] == 'f' && types[2] == 'f') { + glm::vec3 trans(-argv[0]->f, -argv[1]->f, argv[2]->f); // in cm + + // convert trans into a delta (in meters) from the sweet spot of the iphone camera. + const float CM_TO_METERS = 0.01f; + const glm::vec3 FACE_CAP_HEAD_SWEET_SPOT(0.0f, 0.0f, -45.0f); + glm::vec3 delta = (trans - FACE_CAP_HEAD_SWEET_SPOT) * CM_TO_METERS; + + container->_headTransTarget = extractTranslation(container->_lastInputCalibrationData.defaultHeadMat) + delta; + container->_headTransValid = true; + } + + // map /HR to head rotation + if (path[0] == '/' && path[1] == 'H' && path[2] == 'R' && path[3] == 0 && + types[0] == 'f' && types[1] == 'f' && types[2] == 'f') { + glm::vec3 euler(-argv[0]->f, -argv[1]->f, argv[2]->f); + container->_headRot = glm::quat(glm::radians(euler)) * Quaternions::Y_180; + container->_headRotValid = true; + } + + // map /ELR to left eye rot + if (path[0] == '/' && path[1] == 'E' && path[2] == 'L' && path[3] == 'R' && + types[0] == 'f' && types[1] == 'f') { + glm::vec3 euler(argv[0]->f, -argv[1]->f, 0.0f); + container->_eyeLeftRot = container->_headRot * glm::quat(glm::radians(euler)); + container->_eyeLeftRotValid = true; + } + + // map /ERR to right eye rot + if (path[0] == '/' && path[1] == 'E' && path[2] == 'R' && path[3] == 'R' && + types[0] == 'f' && types[1] == 'f') { + glm::vec3 euler((float)argv[0]->f, (float)-argv[1]->f, 0.0f); + container->_eyeRightRot = container->_headRot * glm::quat(glm::radians(euler)); + container->_eyeRightRotValid = true; + } + + // AJT: TODO map /STRINGS[i] to _blendshapeValues[i] + + if (container->_debug) { + for (int i = 0; i < argc; i++) { + switch (types[i]) { + case 'i': + // int32 + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] =" << argv[i]->i; + break; + case 'f': + // float32 + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] =" << argv[i]->f32; + break; + case 's': + // OSC-string + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'b': + // OSC-blob + break; + case 'h': + // 64 bit big-endian two's complement integer + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] =" << argv[i]->h; + break; + case 't': + // OSC-timetag + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'd': + // 64 bit ("double") IEEE 754 floating point number + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] =" << argv[i]->d; + break; + case 'S': + // Alternate type represented as an OSC-string (for example, for systems that differentiate "symbols" from "strings") + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'c': + // an ascii character, sent as 32 bits + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] =" << argv[i]->c; + break; + case 'r': + // 32 bit RGBA color + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'm': + // 4 byte MIDI message. Bytes from MSB to LSB are: port id, status byte, data1, data2 + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'T': + // true + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'F': + // false + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'N': + // nil + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case 'I': + // inf + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case '[': + // Indicates the beginning of an array. The tags following are for data in the Array until a close brace tag is reached. + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + case ']': + // Indicates the end of an array. + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = "; + break; + default: + qDebug(inputplugins) << "OscPlugin: " << path << "[" << i << "] = " << types[i]; + break; + } + } + } + + return 1; +} + + +bool OscPlugin::activate() { + InputPlugin::activate(); + + loadSettings(); + + if (_enabled) { + + qDebug(inputplugins) << "OscPlugin: activated"; + + // register with userInputMapper + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(_inputDevice); + + return startServer(); + } + return false; +} + +void OscPlugin::deactivate() { + qDebug(inputplugins) << "OscPlugin: deactivated, _oscServerThread =" << _oscServerThread; + + if (_oscServerThread) { + stopServer(); + } +} + +void OscPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + if (!_enabled) { + return; + } + + _lastInputCalibrationData = inputCalibrationData; + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData); + }); +} + +void OscPlugin::saveSettings() const { + Settings settings; + QString idString = getID(); + settings.beginGroup(idString); + { + settings.setValue(QString("enabled"), _enabled); + settings.setValue(QString("extraDebug"), _debug); + settings.setValue(QString("serverPort"), _serverPort); + } + settings.endGroup(); +} + +void OscPlugin::loadSettings() { + Settings settings; + QString idString = getID(); + settings.beginGroup(idString); + { + _enabled = settings.value("enabled", QVariant(DEFAULT_ENABLED)).toBool(); + _debug = settings.value("extraDebug", QVariant(DEFAULT_ENABLED)).toBool(); + _serverPort = settings.value("serverPort", QVariant(DEFAULT_OSC_SERVER_PORT)).toInt(); + } + settings.endGroup(); +} + +bool OscPlugin::startServer() { + if (_oscServerThread) { + qWarning(inputplugins) << "OscPlugin: (startServer) server is already running, _oscServerThread =" << _oscServerThread; + return true; + } + + // start a new server on specified port + const size_t BUFFER_SIZE = 64; + char serverPortString[BUFFER_SIZE]; + snprintf(serverPortString, BUFFER_SIZE, "%d", _serverPort); + _oscServerThread = lo_server_thread_new(serverPortString, errorHandlerFunc); + + qDebug(inputplugins) << "OscPlugin: server started on port" << serverPortString << ", _oscServerThread =" << _oscServerThread; + + // add method that will match any path and args + // NOTE: callback function will be called on the OSC thread, not the appliation thread. + lo_server_thread_add_method(_oscServerThread, NULL, NULL, genericHandlerFunc, (void*)this); + + lo_server_thread_start(_oscServerThread); + + return true; +} + +void OscPlugin::stopServer() { + if (!_oscServerThread) { + qWarning(inputplugins) << "OscPlugin: (stopServer) server is already shutdown."; + } + + // stop and free server + lo_server_thread_stop(_oscServerThread); + lo_server_thread_free(_oscServerThread); + _oscServerThread = nullptr; +} + +void OscPlugin::restartServer() { + if (_oscServerThread) { + stopServer(); + } + startServer(); +} + +// +// InputDevice +// + +controller::Input::NamedVector OscPlugin::InputDevice::getAvailableInputs() const { + static controller::Input::NamedVector availableInputs; + if (availableInputs.size() == 0) { + for (int i = 0; i < (int)FaceCap::BlendshapeCount; i++) { + availableInputs.push_back(makePair(CHANNELS[i], STRINGS[i])); + } + } + availableInputs.push_back(makePair(controller::HEAD, "Head")); + availableInputs.push_back(makePair(controller::LEFT_EYE, "LeftEye")); + availableInputs.push_back(makePair(controller::RIGHT_EYE, "RightEye")); + return availableInputs; +} + +QString OscPlugin::InputDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/osc.json"; + return MAPPING_JSON; +} + +void OscPlugin::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + std::lock_guard guard(_container->_dataMutex); + for (int i = 0; i < (int)FaceCap::BlendshapeCount; i++) { + if (_container->_blendshapeValidFlags[i]) { + _axisStateMap[CHANNELS[i]] = controller::AxisValue(_container->_blendshapeValues[i], 0, true); + } + } + if (_container->_headRotValid && _container->_headTransValid) { + const float SMOOTH_TIMESCALE = 2.0f; + float tau = deltaTime / SMOOTH_TIMESCALE; + _container->_headTransSmoothed = lerp(_container->_headTransSmoothed, _container->_headTransTarget, tau); + glm::vec3 delta = _container->_headTransSmoothed - _container->_headTransTarget; + glm::vec3 trans = extractTranslation(inputCalibrationData.defaultHeadMat) + delta; + + controller::Pose sensorSpacePose(trans, _container->_headRot); + _poseStateMap[controller::HEAD] = sensorSpacePose.transform(sensorToAvatarMat); + } + if (_container->_eyeLeftRotValid) { + controller::Pose sensorSpacePose(vec3(0.0f), _container->_eyeLeftRot); + _poseStateMap[controller::LEFT_EYE] = sensorSpacePose.transform(sensorToAvatarMat); + } + if (_container->_eyeRightRotValid) { + controller::Pose sensorSpacePose(vec3(0.0f), _container->_eyeRightRot); + _poseStateMap[controller::RIGHT_EYE] = sensorSpacePose.transform(sensorToAvatarMat); + } +} + +void OscPlugin::InputDevice::clearState() { + std::lock_guard guard(_container->_dataMutex); + for (int i = 0; i < (int)FaceCap::BlendshapeCount; i++) { + _axisStateMap[CHANNELS[i]] = controller::AxisValue(0.0f, 0, false); + } + _poseStateMap[controller::HEAD] = controller::Pose(); + _poseStateMap[controller::LEFT_EYE] = controller::Pose(); + _poseStateMap[controller::RIGHT_EYE] = controller::Pose(); +} + diff --git a/plugins/hifiOsc/src/OscPlugin.h b/plugins/hifiOsc/src/OscPlugin.h new file mode 100644 index 0000000000..3ce198b625 --- /dev/null +++ b/plugins/hifiOsc/src/OscPlugin.h @@ -0,0 +1,95 @@ +// +// OscPlugin.h +// +// Created by Anthony Thibault on 2019/8/24 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OscPlugin_h +#define hifi_OscPlugin_h + +#include +#include +#include + +#include "lo/lo.h" + +// OSC (Open Sound Control) input plugin. +class OscPlugin : public InputPlugin { + Q_OBJECT +public: + + // Plugin functions + virtual void init() override; + virtual bool isSupported() const override; + virtual const QString getName() const override { return NAME; } + const QString getID() const override { return OSC_ID_STRING; } + + virtual bool activate() override; + virtual void deactivate() override; + + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + + virtual void saveSettings() const override; + virtual void loadSettings() override; + + bool startServer(); + void stopServer(); + void restartServer(); + +protected: + + class InputDevice : public controller::InputDevice { + public: + friend class OscPlugin; + + InputDevice() : controller::InputDevice("OSC") {} + + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + virtual void focusOutEvent() override {}; + + void clearState(); + void setContainer(OscPlugin* container) { _container = container; } + + OscPlugin* _container { nullptr }; + }; + + std::shared_ptr _inputDevice; + + static const char* NAME; + static const char* OSC_ID_STRING; + + bool _enabled { false }; + mutable bool _initialized { false }; + + lo_server_thread _oscServerThread { nullptr }; + +public: + bool _debug { false }; + enum Constants { DEFAULT_OSC_SERVER_PORT = 7700 }; + int _serverPort { DEFAULT_OSC_SERVER_PORT }; + controller::InputCalibrationData _lastInputCalibrationData; + + std::vector _blendshapeValues; + std::vector _blendshapeValidFlags; + glm::quat _headRot; + bool _headRotValid { false }; + glm::vec3 _headTransTarget; + glm::vec3 _headTransSmoothed; + bool _headTransValid { false }; + glm::quat _eyeLeftRot; + bool _eyeLeftRotValid { false }; + glm::quat _eyeRightRot; + bool _eyeRightRotValid { false }; + std::mutex _dataMutex; +}; + +#endif // hifi_OscPlugin_h + diff --git a/plugins/hifiOsc/src/OscProvider.cpp b/plugins/hifiOsc/src/OscProvider.cpp new file mode 100644 index 0000000000..0d4c582d16 --- /dev/null +++ b/plugins/hifiOsc/src/OscProvider.cpp @@ -0,0 +1,49 @@ +// +// Created by Anthony Thibault on 2019/8/25 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include + +#include +#include + +#include "OscPlugin.h" + +class OscProvider : public QObject, public InputProvider +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json") + Q_INTERFACES(InputProvider) + +public: + OscProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~OscProvider() {} + + virtual InputPluginList getInputPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + InputPluginPointer plugin(new OscPlugin()); + if (plugin->isSupported()) { + _inputPlugins.push_back(plugin); + } + }); + return _inputPlugins; + } + + virtual void destroyInputPlugins() override { + _inputPlugins.clear(); + } + +private: + InputPluginList _inputPlugins; +}; + +#include "OscProvider.moc" diff --git a/plugins/hifiOsc/src/plugin.json b/plugins/hifiOsc/src/plugin.json new file mode 100644 index 0000000000..d977e34a1c --- /dev/null +++ b/plugins/hifiOsc/src/plugin.json @@ -0,0 +1,4 @@ +{ + "name":"Osc", + "version": 1 +} diff --git a/plugins/hifiSdl2/CMakeLists.txt b/plugins/hifiSdl2/CMakeLists.txt index 8b2bb114a0..c68723a10a 100644 --- a/plugins/hifiSdl2/CMakeLists.txt +++ b/plugins/hifiSdl2/CMakeLists.txt @@ -6,7 +6,9 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -set(TARGET_NAME hifiSdl2) -setup_hifi_plugin(Qml) -link_hifi_libraries(shared controllers ui plugins input-plugins script-engine) -target_sdl2() +if (NOT APPLE) + set(TARGET_NAME hifiSdl2) + setup_hifi_plugin(Qml) + link_hifi_libraries(shared controllers ui plugins input-plugins script-engine) + target_sdl2() +endif() \ No newline at end of file diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index 33d27c4e9d..bd3fbb439f 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -9,7 +9,7 @@ # Windows doesn't need this, and building it currently make Linux unstable. # if (NOT WIN32) -if (APPLE) +if (FALSE AND APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() diff --git a/prebuild.py b/prebuild.py index bd4c74bd64..03677f21d7 100644 --- a/prebuild.py +++ b/prebuild.py @@ -20,6 +20,7 @@ import hifi_singleton import hifi_utils import hifi_android import hifi_vcpkg +import hifi_qt import argparse import concurrent @@ -119,9 +120,30 @@ def main(): logger.info('sha=%s' % headSha()) logger.info('start') - # Only allow one instance of the program to run at a time + # OS dependent information + system = platform.system() + if 'Windows' == system and 'CI_BUILD' in os.environ and os.environ["CI_BUILD"] == "Github": + logger.info("Downloading NSIS") + with timer('NSIS'): + hifi_utils.downloadAndExtract('https://athena-public.s3.amazonaws.com/dependencies/NSIS-hifi-plugins-1.0.tgz', "C:/Program Files (x86)") + + qtInstallPath = '' + # If not android, install our Qt build + if not args.android: + qt = hifi_qt.QtDownloader(args) + qtInstallPath = qt.cmakePath + with hifi_singleton.Singleton(qt.lockFile) as lock: + with timer('Qt'): + qt.installQt() + qt.writeConfig() + pm = hifi_vcpkg.VcpkgRepo(args) + if qtInstallPath != '': + pm.writeVar('QT_CMAKE_PREFIX_PATH', qtInstallPath) + + # Only allow one instance of the program to run at a time with hifi_singleton.Singleton(pm.lockFile) as lock: + with timer('Bootstraping'): if not pm.upToDate(): pm.bootstrap() @@ -135,7 +157,7 @@ def main(): # * build host tools, like spirv-cross and scribe # * build client dependencies like openssl and nvtt with timer('Setting up dependencies'): - pm.setupDependencies() + pm.setupDependencies(qt=qtInstallPath) # wipe out the build directories (after writing the tag, since failure # here shouldn't invalidte the vcpkg install) diff --git a/screenshare/.gitignore b/screenshare/.gitignore new file mode 100644 index 0000000000..e978d75d04 --- /dev/null +++ b/screenshare/.gitignore @@ -0,0 +1,4 @@ +hifi-screenshare-*/ +hifi-screenshare*.zip +screenshare*.zip +screenshare-*/ diff --git a/screenshare/CMakeLists.txt b/screenshare/CMakeLists.txt new file mode 100644 index 0000000000..d91d8c50b1 --- /dev/null +++ b/screenshare/CMakeLists.txt @@ -0,0 +1,31 @@ +set(TARGET_NAME screenshare) + +add_custom_target(${TARGET_NAME}-npm-install + COMMAND npm install + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +add_custom_target(${TARGET_NAME} + COMMAND npm run packager -- --out ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${TARGET_NAME}-npm-install +) + +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "screenshare") +set_target_properties(${TARGET_NAME}-npm-install PROPERTIES FOLDER "hidden/screenshare") + +if (WIN32) + set(PACKAGED_SCREENSHARE_FOLDER "hifi-screenshare-win32-x64") + set(SCREENSHARE_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_SCREENSHARE_FOLDER}") + install( + DIRECTORY "${SCREENSHARE_DESTINATION}/" + DESTINATION ${SCREENSHARE_INSTALL_DIR} + ) + + set(EXECUTABLE_PATH "${SCREENSHARE_DESTINATION}/${SCREENSHARE_EXEC_NAME}") + optional_win_executable_signing() +endif() + +# DO build the Screenshare Electron app when building the `ALL_BUILD` target. +# DO build the Screenshare Electron app when a user selects "Build Solution" from within Visual Studio. +set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL FALSE EXCLUDE_FROM_DEFAULT_BUILD FALSE) +set_target_properties(${TARGET_NAME}-npm-install PROPERTIES EXCLUDE_FROM_ALL FALSE EXCLUDE_FROM_DEFAULT_BUILD FALSE) diff --git a/screenshare/README.md b/screenshare/README.md new file mode 100644 index 0000000000..e57f37adc1 --- /dev/null +++ b/screenshare/README.md @@ -0,0 +1,16 @@ +# Screen Sharing within High Fidelity +This Screen Share app, built using Electron, allows for easy desktop screen sharing when used in conjuction with various scripts in the `hifi-content` repository. + +# Screen Sharing Source Files +## `packager.js` +Calling npm run packager will use this file to create the actual Electron `hifi-screenshare` executable. +It will kick out a folder `hifi-screenshare-` which contains an executable. + +## `src/screenshareApp.js` +The main process file to configure the electron app. + +## `src/screenshareMainProcess.js` +The render file to display the app's UI. + +## `screenshareApp.html` +The HTML that displays the screen selection UI and the confirmation screen UI. \ No newline at end of file diff --git a/screenshare/package-lock.json b/screenshare/package-lock.json new file mode 100644 index 0000000000..c7d92d3e17 --- /dev/null +++ b/screenshare/package-lock.json @@ -0,0 +1,2289 @@ +{ + "name": "highfidelity_screenshare", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@electron/get": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.5.0.tgz", + "integrity": "sha512-tafxBz6n08G6SX961F/h8XFtpB/DdwRvJJoDeOH9x78jDSCMQ2G/rRWqSwLFp9oeMFBJf0Pf5Kkw6TKt5w9TWg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^9.6.0", + "sanitize-filename": "^1.6.2", + "sumchecker": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "sumchecker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.0.tgz", + "integrity": "sha512-yreseuC/z4iaodVoq07XULEOO9p4jnQazO7mbrnDSvWAU/y2cbyIKs+gWJptfcGu9R+1l27K8Rkj0bfvqnBpgQ==", + "dev": true, + "requires": { + "debug": "^4.1.0" + } + } + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/node": { + "version": "10.14.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.21.tgz", + "integrity": "sha512-nuFlRdBiqbF+PJIEVxm2jLFcQWN7q7iWEJGsBV4n7v1dbI9qXB8im2pMMKMCUZe092sQb5SQft2DHfuQGK5hqQ==", + "dev": true + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "asar": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/asar/-/asar-2.0.1.tgz", + "integrity": "sha512-Vo9yTuUtyFahkVMFaI6uMuX6N7k5DWa6a/8+7ov0/f8Lq9TVR0tUjzSzxQSxT1Y+RJIZgnP7BVb6Uhi+9cjxqA==", + "dev": true, + "requires": { + "chromium-pickle-js": "^0.2.0", + "commander": "^2.20.0", + "cuint": "^0.2.2", + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "tmp-promise": "^1.0.5" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "author-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", + "integrity": "sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-zip": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/cross-zip/-/cross-zip-2.1.6.tgz", + "integrity": "sha512-xLIETNkzRcU6jGRzenJyRFxahbtP4628xEKMTI/Ql0Vu8m4h8M7uRLVi7E5OYHuJ6VQPsG4icJumKAFUvfm0+A==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "defer-to-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.2.tgz", + "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "electron": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/electron/-/electron-6.0.12.tgz", + "integrity": "sha512-70ODZa1RP6K0gE9IV9YLCXPSyhLjXksCuYSSPb3MljbfwfHo5uE6X0CGxzm+54YuPdE2e7EPnWZxOOsJYrS5iQ==", + "dev": true, + "requires": { + "@types/node": "^10.12.18", + "electron-download": "^4.1.0", + "extract-zip": "^1.0.3" + } + }, + "electron-download": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", + "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", + "dev": true, + "requires": { + "debug": "^3.0.0", + "env-paths": "^1.0.0", + "fs-extra": "^4.0.1", + "minimist": "^1.2.0", + "nugget": "^2.0.1", + "path-exists": "^3.0.0", + "rc": "^1.2.1", + "semver": "^5.4.1", + "sumchecker": "^2.0.2" + } + }, + "electron-notarize": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.1.1.tgz", + "integrity": "sha512-TpKfJcz4LXl5jiGvZTs5fbEx+wUFXV5u8voeG5WCHWfY/cdgdD8lDZIZRqLVOtR3VO+drgJ9aiSHIO9TYn/fKg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "fs-extra": "^8.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "electron-osx-sign": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.14.tgz", + "integrity": "sha512-72vtrz9I3dOeFDaNvO5thwIjrimDiXMmYEbN0hEBqnvcSSMOWugjim2wiY9ox3dhuBFUhxp3owmuZCoH3Ij08A==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "compare-version": "^0.1.2", + "debug": "^2.6.8", + "isbinaryfile": "^3.0.2", + "minimist": "^1.2.0", + "plist": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "electron-packager": { + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-14.0.6.tgz", + "integrity": "sha512-X+ikV+TnnNkIrK93vOjsjPeykCQBFxBS7LXKMTE1s62rXWirGMdjWL+edVkBOMRkH0ROJyFmWM28Dpj6sfEg+A==", + "dev": true, + "requires": { + "@electron/get": "^1.3.0", + "asar": "^2.0.1", + "cross-zip": "^2.1.5", + "debug": "^4.0.1", + "electron-notarize": "^0.1.1", + "electron-osx-sign": "^0.4.11", + "fs-extra": "^8.1.0", + "galactus": "^0.2.1", + "get-package-info": "^1.0.0", + "junk": "^3.1.0", + "parse-author": "^2.0.0", + "plist": "^3.0.0", + "rcedit": "^2.0.0", + "resolve": "^1.1.6", + "sanitize-filename": "^1.6.0", + "semver": "^6.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "flora-colossus": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.1.tgz", + "integrity": "sha512-d+9na7t9FyH8gBJoNDSi28mE4NgQVGGvxQ4aHtFRetjyh5SXjuus+V5EZaxFmFdXVemSOrx0lsgEl/ZMjnOWJA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "fs-extra": "^7.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "galactus": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.1.tgz", + "integrity": "sha1-y+0tIKQMH1Z5o1kI4rlBVzPnjbk=", + "dev": true, + "requires": { + "debug": "^3.1.0", + "flora-colossus": "^1.0.0", + "fs-extra": "^4.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-package-info": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", + "integrity": "sha1-ZDJ5ZWPigRPNlHTbvQAFKYWkmZw=", + "dev": true, + "requires": { + "bluebird": "^3.1.1", + "debug": "^2.2.0", + "lodash.get": "^4.0.0", + "read-pkg-up": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", + "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "nugget": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", + "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", + "dev": true, + "requires": { + "debug": "^2.1.3", + "minimist": "^1.1.0", + "pretty-bytes": "^1.0.2", + "progress-stream": "^1.1.0", + "request": "^2.45.0", + "single-line-log": "^1.1.2", + "throttleit": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-author": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", + "integrity": "sha1-00YL8d3Q367tQtp1QkLmX7aEqB8=", + "dev": true, + "requires": { + "author-regex": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", + "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==", + "dev": true, + "requires": { + "base64-js": "^1.2.3", + "xmlbuilder": "^9.0.7", + "xmldom": "0.1.x" + } + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.1.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", + "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", + "dev": true, + "requires": { + "speedometer": "~0.1.2", + "through2": "~0.2.3" + } + }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "rcedit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-2.0.0.tgz", + "integrity": "sha512-XcFGyEBjhWSsud+R8elwQtGBbVkCf7tAiad+nXo5jc6l2rMf46NfGNwjnmBNneBIZDfq+Npf8lwP371JTONfrw==", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "single-line-log": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", + "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", + "dev": true, + "requires": { + "string-width": "^1.0.1" + } + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "speedometer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", + "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "sumchecker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", + "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", + "dev": true, + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9", + "xtend": "~2.1.1" + } + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + }, + "tmp-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", + "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "tmp": "0.1.0" + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.0.tgz", + "integrity": "sha512-/is78VKbKs70bVZH7w4YaZea6xcJWOAwkhbR0CFuZBmYtfTYF0xjGJF43AYd8g2Uii1yJwmS5GR2vBmrc32sbg==", + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + } + } +} diff --git a/screenshare/package.json b/screenshare/package.json new file mode 100644 index 0000000000..372679082f --- /dev/null +++ b/screenshare/package.json @@ -0,0 +1,27 @@ +{ + "name": "highfidelity_screenshare", + "version": "1.0.0", + "description": "High Fidelity Screenshare", + "main": "src/screenshareMainProcess.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "packager": "node packager.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/highfidelity/hifi.git" + }, + "author": "High Fidelity", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/highfidelity/hifi/issues" + }, + "homepage": "https://github.com/highfidelity/hifi#readme", + "devDependencies": { + "electron": "^6.0.12", + "electron-packager": "^14.0.6" + }, + "dependencies": { + "yargs": "^14.2.0" + } +} diff --git a/screenshare/packager.js b/screenshare/packager.js new file mode 100644 index 0000000000..7a0baebaf0 --- /dev/null +++ b/screenshare/packager.js @@ -0,0 +1,50 @@ +var packager = require('electron-packager'); +var osType = require('os').type(); +var argv = require('yargs').argv; + +var platform = null; +if (osType == "Darwin" || osType == "Linux") { + platform = osType.toLowerCase(); +} else if (osType == "Windows_NT") { + platform = "win32" +} + +var NAME = "hifi-screenshare"; +var options = { + dir: __dirname, + name: NAME, + version: "0.1.0", + overwrite: true, + prune: true, + arch: "x64", + platform: platform, + ignore: "electron-packager|README.md|CMakeLists.txt|packager.js|.gitignore" +}; + +// setup per OS options +if (osType == "Darwin") { + options["app-bundle-id"] = "com.highfidelity.hifi-screenshare"; +} else if (osType == "Windows_NT") { + options["version-string"] = { + CompanyName: "High Fidelity, Inc.", + FileDescription: "High Fidelity Screenshare", + ProductName: NAME, + OriginalFilename: NAME + ".exe" + } +} + +// check if we were passed a custom out directory, pass it along if so +if (argv.out) { + options.out = argv.out +} + +// call the packager to produce the executable +packager(options) + .then(appPath => { + console.log("Wrote new app to " + appPath); + }) + .catch(error => { + console.error("There was an error writing the packaged console: " + error.message); + process.exit(1); + }); + \ No newline at end of file diff --git a/screenshare/src/resources/Graphik-Regular.ttf b/screenshare/src/resources/Graphik-Regular.ttf new file mode 100644 index 0000000000..001faa7f47 Binary files /dev/null and b/screenshare/src/resources/Graphik-Regular.ttf differ diff --git a/screenshare/src/resources/interface.png b/screenshare/src/resources/interface.png new file mode 100644 index 0000000000..f90cbe591c Binary files /dev/null and b/screenshare/src/resources/interface.png differ diff --git a/screenshare/src/screenshareApp.html b/screenshare/src/screenshareApp.html new file mode 100644 index 0000000000..6268b581e4 --- /dev/null +++ b/screenshare/src/screenshareApp.html @@ -0,0 +1,52 @@ + + + + + + + +
+

Share your screen

+

Please select the content you'd like to share.

+
+ +
+
+
+
+
+
+
+
+ + + + + diff --git a/screenshare/src/screenshareApp.js b/screenshare/src/screenshareApp.js new file mode 100644 index 0000000000..6a33b827d4 --- /dev/null +++ b/screenshare/src/screenshareApp.js @@ -0,0 +1,312 @@ +'use strict'; +// screenshareApp.js +// +// Created by Milad Nazeri, Rebecca Stankus, and Zach Fox 2019/11/13 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const { remote } = require('electron'); + +// Helpers +function handleError(error) { + if (error) { + console.error(error); + } +} + + +// When an application is picked, make sure we clear out the previous pick, toggle the page, +// and add the correct source +let currentScreensharePickID = ""; +function screensharePicked(id) { + currentScreensharePickID = id; + document.getElementById("share_pick").innerHTML = ""; + togglePage(); + addSource(sourceMap[id], "share_pick"); +} + + +// Once we have confirmed that we want to share, prepare the tokbox publishing initiating +// and toggle back to the selects page +function screenConfirmed(isConfirmed) { + document.getElementById("selects").innerHTML = ""; + if (isConfirmed === true){ + onAccessApproved(currentScreensharePickID); + } + togglePage(); +} + + +// Hide/show the select page or the confirmation page +let currentPage = "mainPage"; +function togglePage(){ + if (currentPage === "mainPage") { + currentPage = "confirmationPage"; + document.getElementById("select_screen").style.display = "none"; + document.getElementById("subtitle").innerHTML = "Confirm that you'd like to share this content."; + document.getElementById("confirmation_screen").style.display = "block"; + } else { + showSources(); + currentPage = "mainPage"; + document.getElementById("select_screen").style.display = "block"; + document.getElementById("subtitle").innerHTML = "Please select the content you'd like to share."; + document.getElementById("confirmation_screen").style.display = "none"; + } +} + + +// UI + +// Render the html properly and append that to the correct parent +function addSource(source, type) { + let renderedHTML = renderSourceHTML(source); + if (type === "selects") { + document.getElementById("selects").appendChild(renderedHTML); + } else { + document.getElementById("share_pick").appendChild(renderedHTML); + document.getElementById("content_name").innerHTML = source.name; + } +} + + +// Get the html created from the source. Alter slightly depending on whether this source +// is on the selects screen, or the confirmation screen. Mainly removing highlighting. +// If there is an app Icon, then add it. +function renderSourceHTML(source) { + let type = currentPage === "confirmationPage" ? "share_pick" : "selects"; + let sourceBody = document.createElement('div') + let thumbnail = source.thumbnail.toDataURL(); + sourceBody.classList.add("box") + if (type === "share_pick") { + sourceBody.style.marginLeft = "0px"; + } + + let image = ""; + if (source.appIcon) { + image = ``; + } + sourceBody.innerHTML = ` +
+ ${image} + ${source.name} +
+
+ +
+ ` + return sourceBody; +} + + +// Separate out the screenshares and applications +// Make sure the screens are labeled in order +// Concact the two arrays back together and return +function sortSources() { + let screenSources = []; + let applicationSources = []; + // Difference with Mac selects: + // 1 screen = "Enitre Screen", more than one like PC "Screen 1, Screen 2..." + screenshareSourceArray.forEach((source) => { + if (source.name.match(/(entire )?screen( )?([0-9]?)/i)) { + screenSources.push(source); + } else { + applicationSources.push(source) + } + }); + screenSources.sort((a, b) => { + let aNumber = a.name.replace(/[^\d]/, ""); + let bNumber = b.name.replace(/[^\d]/, ""); + return aNumber - bNumber; + }); + let finalSources = [...screenSources, ...applicationSources]; + return finalSources; +} + + +// Setup sorting the selection array, add individual sources, and update the sourceMap +function addSources() { + screenshareSourceArray = sortSources(); + for (let i = 0; i < screenshareSourceArray.length; i++) { + addSource(screenshareSourceArray[i], "selects"); + sourceMap[screenshareSourceArray[i].id] = screenshareSourceArray[i]; + } +} + + +// 1. Get the screens and window that are available from electron +// 2. Remove the screenshare app itself +// 3. Create a source map to help grab the correct source when picked +// 4. push all the sources for sorting to the source array +// 5. Add thse sources +const electron = require('electron'); +const SCREENSHARE_TITLE = "Screen share"; +const SCREENSHARE_TITLE_REGEX = new RegExp("^" + SCREENSHARE_TITLE + "$"); +const IMAGE_WIDTH = 265; +const IMAGE_HEIGHT = 165; +let screenshareSourceArray = []; +let sourceMap = {}; +function showSources() { + screenshareSourceArray = []; + electron.desktopCapturer.getSources({ + types:['window', 'screen'], + thumbnailSize: { + width: IMAGE_WIDTH, + height: IMAGE_HEIGHT + }, + fetchWindowIcons: true + }, (error, sources) => { + if (error) { + console.log("Error getting sources", error); + } + for (let source of sources) { + if (source.name.match(SCREENSHARE_TITLE_REGEX)){ + continue; + } + sourceMap[source.id] = source; + screenshareSourceArray.push(source); + } + addSources(); + }); +} + + +// Stop the localstream and end the tokrok publishing +let localStream; +let desktopSharing; +function stopSharing() { + desktopSharing = false; + + if (localStream) { + localStream.getTracks()[0].stop(); + localStream = null; + } + + document.getElementById('screenshare').style.display = "none"; + stopTokBoxPublisher(); +} + + +// Callback to start publishing after we have setup the chromium stream +function gotStream(stream) { + if (localStream) { + stopSharing(); + } + + localStream = stream; + startTokboxPublisher(localStream); + + stream.onended = () => { + if (desktopSharing) { + togglePage(); + } + }; +} + + +// After we grant access to electron, create a stream and using the callback +// start the tokbox publisher +function onAccessApproved(desktop_id) { + if (!desktop_id) { + console.log('Desktop Capture access rejected.'); + return; + } + + + + document.getElementById('screenshare').style.visibility = "block"; + desktopSharing = true; + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: desktop_id, + maxWidth: 1280, + maxHeight: 720, + maxFrameRate: 7 + } + } + }, gotStream, handleError); + remote.getCurrentWindow().minimize(); +} + + +// Tokbox + +// Once we have the connection info, this will create the session which will allow +// us to publish a stream when we are ready +function initializeTokboxSession() { + session = OT.initSession(projectAPIKey, sessionID); + session.on('sessionDisconnected', (event) => { + console.log('You were disconnected from the session.', event.reason); + }); + + // Connect to the session + session.connect(token, (error) => { + if (error) { + handleError(error); + } + }); +} + + +// Init the tokbox publisher with our newly created stream +var publisher; +function startTokboxPublisher(stream) { + publisher = document.createElement("div"); + var publisherOptions = { + audioFallbackEnabled: false, + audioSource: null, + fitMode: 'contain', + frameRate: 7, + height: 720, + insertMode: 'append', + publishAudio: false, + videoSource: stream.getVideoTracks()[0], + width: 1280 + }; + + publisher = OT.initPublisher(publisher, publisherOptions, function(error){ + if (error) { + console.log("ERROR: " + error); + } else { + session.publish(publisher, function(error) { + if (error) { + console.log("ERROR FROM Session.publish: " + error); + return; + } + }) + } + }); +} + + +// Kills the streaming being sent to tokbox +function stopTokBoxPublisher() { + publisher.destroy(); +} + + +// When the app is ready, we get this info from the command line arguments. +const ipcRenderer = electron.ipcRenderer; +let projectAPIKey; +let sessionID; +let token; +let session; +ipcRenderer.on('connectionInfo', function(event, message) { + const connectionInfo = JSON.parse(message); + projectAPIKey = connectionInfo.projectAPIKey; + sessionID = connectionInfo.sessionID; + token = connectionInfo.token; + + initializeTokboxSession(); +}); + + +// Show the initial sources after the dom has loaded +// Sources come from electron.desktopCapturer +document.addEventListener("DOMContentLoaded", () => { + showSources(); +}); diff --git a/screenshare/src/screenshareMainProcess.js b/screenshare/src/screenshareMainProcess.js new file mode 100644 index 0000000000..5dce65a783 --- /dev/null +++ b/screenshare/src/screenshareMainProcess.js @@ -0,0 +1,74 @@ +'use strict'; +// screenshareMainProcess.js +// +// Milad Nazeri and Zach Fox 2019/11/13 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const {app, BrowserWindow, ipcMain} = require('electron'); +const gotTheLock = app.requestSingleInstanceLock() +const argv = require('yargs').argv; + + +const connectionInfo = { + token: argv.token || "token", + projectAPIKey: argv.projectAPIKey || "projectAPIKey", + sessionID: argv.sessionID || "sessionID" +} + + +// Mac and PC need slightly different width and height sizes. +const osType = require('os').type(); +let width; +let height; +if (osType == "Darwin" || osType == "Linux") { + width = 960; + height = 660; +} else if (osType == "Windows_NT") { + width = 973; + height = 740; +} + + +if (!gotTheLock) { + console.log("Another instance of the screenshare is already running - this instance will quit."); + app.exit(0); + return; +} + +let window; +const zoomFactor = 1.0; +function createWindow(){ + window = new BrowserWindow({ + backgroundColor: "#000000", + width: width, + height: height, + center: true, + frame: true, + useContentSize: true, + zoomFactor: zoomFactor, + resizable: false, + webPreferences: { + nodeIntegration: true + }, + icon: __dirname + `/resources/interface.png`, + skipTaskbar: false, + title: "Screen share" + }); + window.loadURL('file://' + __dirname + '/screenshareApp.html'); + window.setMenu(null); + + window.webContents.on("did-finish-load", () => { + window.webContents.send('connectionInfo', JSON.stringify(connectionInfo)); + }); +} + + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +app.on('ready', function() { + createWindow(); + window.webContents.send('connectionInfo', JSON.stringify(connectionInfo)) +}); diff --git a/screenshare/src/styles.css b/screenshare/src/styles.css new file mode 100644 index 0000000000..78dca032fa --- /dev/null +++ b/screenshare/src/styles.css @@ -0,0 +1,217 @@ +body { + background-color: black; + box-sizing: border-box; + font-family: "Graphik"; + margin: 0px 22px 10px 22px; + } + +#confirmation_screen { + width: 100%; + display: flex; + text-align: center; + justify-content: center; + align-items: center; +} + +#confirmation_text { + margin-top: 65px; + font-size: 25px; + line-height: 25px; +} + +#confirmation_text p { + margin: 0px; +} + +#button_selection { + margin-top: 25px; + width: 100%; + display: flex; + text-align: center; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.button_confirmation { + margin: 4px; + cursor: pointer; + width: 300px; + height: 32px; + line-height: 32px; + text-align: center; + vertical-align: middle; + color: white +} + +.grey_background { + background-color: #191919; +} + +#no { + color: rgba(1, 152, 203,0.7); +} + +#no:hover { + color: rgba(1, 152, 203,1); +} + +#yes { + outline: solid white 2px; +} + +#yes:hover { + background: #0198CB; +} + +yes:hover + #yes_background { + display: block; +} + +#share_pick { + margin-top: 60px; +} + +.text_title { + color: white; +} + +@font-face { + font-family: "Graphik"; + src: url("./resources/Graphik-Regular.ttf"); +} + +#title { + margin-top: 21px; +} + +h1 { + line-height: 48px; + font-size: 48px; + margin: 0px; +} + +h3 { + line-height: 24px; + font-size: 24px; + margin: 9px 0px 0px 0px; +} + +#publisher { + visibility: hidden; + width: 0px; + height: 0px; + bottom: 10px; + left: 10px; + z-index: 100; + border: 3px solid white; + border-radius: 3px; +} + +#selects { + margin-right: 19px; + padding-left: 3px; +} + +.screen_label { + max-width: 220px; + font-size: 25px; + line-height: 25px; + color: white; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.icon { + display: inline-block; + background: #000000; + width: 20px; + height: 20px; + margin-right: 15px; +} + +.box { + height: 165px; + width: 265px; + display: inline-block; + margin-left: 35px; + margin-top: 60px; + cursor: pointer; +} + +.box:nth-child(1) { + margin-top: 0 !important; +} + +.box:nth-child(2) { + margin-top: 0 !important; +} + +.box:nth-child(3) { + margin-top: 0 !important; +} + +.box:nth-child(3n) { + margin-right: 0 !important; +} + +.box:nth-child(3n+1) { + margin-left: 0 !important; +} + +.heading { + height: 35px; + display: flex; + align-items: center; +} + +.image { + width: 265px; + height: 165px; + max-height: 165px; + max-width: 265px; + margin: 0px; +} + +.image:hover { + outline: solid white 3px; +} + +.image_no_hover { + width: 265px; + height: 165px; + max-height: 165px; + max-width: 265px; +} + +img { + width: 265px; + height: 165px; + margin: 0px; +} + +.scrollbar { + float: right; + height: 500px; + width: 100%; + overflow-y: scroll; + margin-top: 40px; +} + +#style-1::-webkit-scrollbar { + width: 9px; + overflow: scroll; + overflow-x: hidden; +} + +#style-1::-webkit-scrollbar-thumb { + background-color: #0198CB; + width: 9px; +} + +#style-1::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + background-color: #848484; + width: 9px; +} diff --git a/scripts/developer/automaticLookAt.js b/scripts/developer/automaticLookAt.js new file mode 100644 index 0000000000..542d0df0d8 --- /dev/null +++ b/scripts/developer/automaticLookAt.js @@ -0,0 +1,1344 @@ +// +// automaticLookAt.js +// This script controls the avatar's look-at-target for the head and eyes, according to other avatar's actions +// It tries to simulate human interaction during group conversations +// +// Created by Luis Cuenca on 11/11/19 +// Copyright 2019 High Fidelity, Inc. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + ////////////////////////////////////////////// + // debugger.js /////// + ////////////////////////////////////////////// + + var TEXT_BOX_WIDTH = 350; + var TEXT_BOX_MIN_HEIGHT = 40; + var TEXT_BOX_TOP_MARGIN = 100; + var TEXT_CAPTION_HEIGHT = 30; + var TEXT_CAPTION_COLOR = { red: 0, green: 0, blue: 0 }; + var TEXT_CAPTION_SIZE = 18; + var TEXT_CAPTION_MARGIN = 6; + var CHECKBOX_MARK_MARGIN = 3; + + var DEGREE_TO_RADIAN = 0.0174533; + var ENGAGED_AVATARS_DEBUG_COLOR = { red: 0, green: 255, blue: 255 }; + var ENGAGED_AVATARS_DEBUG_ALPHA = 0.3; + var FOCUS_AVATAR_DEBUG_COLOR = { red: 255, green: 0, blue: 0 }; + var FOCUS_AVATAR_DEBUG_ALPHA = 1.0; + var TALKER_AVATAR_DEBUG_COLOR = { red: 0, green: 0, blue: 0 }; + var TALKER_AVATAR_DEBUG_ALPHA = 0.8; + var DEFAULT_OUTLINE_COLOR = { red: 155, green: 155, blue: 255 }; + var DEFAULT_OUTLINE_WIDTH = 2; + + var LookAtDebugger = function() { + var self = this; + var IMAGE_DIMENSIONS = {x: 0.2, y: 0.2, z:0.2}; + var TARGET_ICON_PATH = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/LookAtApp/eyeFocus.png"; + var INFINITY_ICON_PATH = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/LookAtApp/noFocus.png"; + this.items = {}; + this.active = false; + + // UI elements + this.textBox; + this.activeCheckBox; + this.activeCheckBoxMark; + this.activeCaption; + this.textBoxHeight = 0.0; + this.logs = []; + + this.getStyleProps = function(color, alpha) { + return { + fillUnoccludedColor: color, + fillUnoccludedAlpha: alpha, + fillOccludedColor: color, + fillOccludedAlpha: alpha, + outlineUnoccludedColor: DEFAULT_OUTLINE_COLOR, + outlineUnoccludedAlpha: alpha, + outlineOccludedColor: DEFAULT_OUTLINE_COLOR, + outlineOccludedAlpha: alpha, + outlineWidth: DEFAULT_OUTLINE_WIDTH, + isOutlineSmooth: false + } + } + this.Styles = { + "engaged" : { + "style": self.getStyleProps(ENGAGED_AVATARS_DEBUG_COLOR, ENGAGED_AVATARS_DEBUG_ALPHA), + "name" : "engagedSel" + }, + "focus" : { + "style": self.getStyleProps(FOCUS_AVATAR_DEBUG_COLOR, FOCUS_AVATAR_DEBUG_ALPHA), + "name" : "focusSel" + }, + "talker" : { + "style": self.getStyleProps(TALKER_AVATAR_DEBUG_COLOR, TALKER_AVATAR_DEBUG_ALPHA), + "name" : "talkerSel" + } + }; + + this.eyeTarget; + this.eyesTargetProps = { + name: "Eyes-Target-Image", + position: { x: 0.0, y: 0.0, z: 0.0 }, + color: {red: 255, green: 0, blue: 255}, + url: TARGET_ICON_PATH, + dimensions: IMAGE_DIMENSIONS, + alpha: 1.0, + visible: false, + emissive: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + isFacingAvatar: true + }; + this.headTarget; + this.headTargetProps = { + name: "Head-Target-Image", + position: { x: 0.0, y: 0.0, z: 0.0 }, + color: {red: 0, green: 255, blue: 255}, + url: TARGET_ICON_PATH, + dimensions: IMAGE_DIMENSIONS, + alpha: 1.0, + visible: false, + emissive: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + isFacingAvatar: true + }; + + + this.textBoxProps = { + x: Window.innerWidth - TEXT_BOX_WIDTH, + y: TEXT_BOX_TOP_MARGIN, + width: TEXT_BOX_WIDTH, + height: TEXT_BOX_MIN_HEIGHT, + alpha: 0.7, + color: { red: 255, green: 255, blue: 255 } + }; + + this.activeCheckBoxProps = { + x: TEXT_CAPTION_MARGIN + self.textBoxProps.x, + y: 2 * TEXT_CAPTION_MARGIN + self.textBoxProps.y, + width: TEXT_CAPTION_SIZE, + height: TEXT_CAPTION_SIZE, + alpha: 0.0, + borderWidth: 2, + borderColor: TEXT_CAPTION_COLOR + }; + + this.activeCheckBoxMarkProps = { + x: self.activeCheckBoxProps.x + CHECKBOX_MARK_MARGIN, + y: self.activeCheckBoxProps.y + CHECKBOX_MARK_MARGIN, + width: TEXT_CAPTION_SIZE - 2.0 * CHECKBOX_MARK_MARGIN, + height: TEXT_CAPTION_SIZE - 2.0 * CHECKBOX_MARK_MARGIN, + visible: false, + alpha: 1.0, + color: TEXT_CAPTION_COLOR + } + + this.captionProps = { + x: 2 * TEXT_CAPTION_SIZE + self.textBoxProps.x, + y: TEXT_CAPTION_MARGIN + self.textBoxProps.y, + width: self.textBoxProps.width, + height: 30, + alpha: 1.0, + backgroundAlpha: 0.0, + visible: true, + text: "Debug Auto Look At", + font: { + size: TEXT_CAPTION_SIZE + }, + color: TEXT_CAPTION_COLOR, + topMargin: 0.5 * TEXT_CAPTION_MARGIN + }; + + this.log = function(txt) { + if (self.active) { + self.logs.push(Overlays.addOverlay("text", self.captionProps)); + var y = self.textBoxProps.y + self.captionProps.height * (self.logs.length); + Overlays.editOverlay(self.logs[self.logs.length - 1], {y: y, text: txt}); + var height = (TEXT_CAPTION_SIZE + 2.0 * TEXT_CAPTION_MARGIN) * (self.logs.length + 2); + if (this.textBoxHeight !== height) { + this.textBoxHeight = height + Overlays.editOverlay(self.textBox, {height: height}); + } + } + } + + this.clearLog = function() { + for (var n = 0; n < self.logs.length; n++) { + Overlays.deleteOverlay(self.logs[n]); + } + Overlays.editOverlay(self.textBox, self.textBoxProps); + self.logs = []; + self.log("____________________________"); + } + + this.setActive = function(isActive) { + self.active = isActive; + if (!self.active) { + self.turnOff(); + } else { + self.turnOn(); + } + }; + + + this.onClick = function(event) { + if (event.x > self.activeCheckBoxProps.x && event.x < (self.activeCheckBoxProps.x + self.activeCheckBoxProps.width) && + event.y > self.activeCheckBoxProps.y && event.y < (self.activeCheckBoxProps.y + self.activeCheckBoxProps.height)) { + self.setActive(!self.active); + Overlays.editOverlay(self.activeCheckBoxMark, {visible: self.active}); + } + } + + this.turnOn = function() { + for (var key in self.Styles) { + var selStyle = self.Styles[key]; + Selection.enableListHighlight(selStyle.name, selStyle.style); + } + if (!self.eyeTarget) { + self.eyeTarget = Overlays.addOverlay("image3d", self.eyesTargetProps); + } + if (!self.headTarget) { + self.headTarget = Overlays.addOverlay("image3d", self.headTargetProps); + } + } + + this.init = function() { + if (!self.textBox) { + self.textBox = Overlays.addOverlay("rectangle", self.textBoxProps); + } + if (!self.activeCheckBox) { + self.activeCheckBox = Overlays.addOverlay("rectangle", self.activeCheckBoxProps); + } + if (!self.activeCheckBoxMark) { + self.activeCheckBoxMark = Overlays.addOverlay("rectangle", self.activeCheckBoxMarkProps); + } + if (!self.activeCaption) { + self.activeCaption = Overlays.addOverlay("text", self.captionProps); + } + }; + + this.highLightAvatars = function(engagedIDs, focusID, talkerID) { + if (self.active) { + self.clearSelection(); + engagedIDs = !engagedIDs ? [] : engagedIDs; + var focusIDs = !focusID ? [] : [focusID]; + var talkerIDs = !talkerID ? [] : [talkerID]; + self.highLightIDs(self.Styles.engaged.name, engagedIDs); + self.highLightIDs(self.Styles.focus.name, focusIDs); + self.highLightIDs(self.Styles.talker.name, talkerIDs); + return true; + } else { + return false; + } + }; + + this.getTargetProps = function(target) { + var distance = Vec3.length(Vec3.subtract(target, Camera.getPosition())); + RETARGET_MAX_DISTANCE = 100.0; + DIMENSION_SCALE = 0.05; + var isInfinite = false; + if (distance > RETARGET_MAX_DISTANCE) { + isInfinite = true; + var eyesToTarget = Vec3.multiply(RETARGET_MAX_DISTANCE, Vec3.normalize(Vec3.subtract(target, MyAvatar.getDefaultEyePosition()))); + var newTarget = Vec3.sum(MyAvatar.getDefaultEyePosition(), eyesToTarget); + var cameraToTarget = Vec3.normalize(Vec3.subtract(newTarget, Camera.getPosition())); + target = Vec3.sum(Camera.getPosition(), cameraToTarget); + distance = Vec3.length(cameraToTarget); + } + // Scale the target to appear always with the same size on screen + var fov = DEGREE_TO_RADIAN * Camera.frustum.fieldOfView; + var scale = (Camera.frustum.aspectRatio < 1.0 ? Camera.frustum.aspectRatio : 1.0) * DIMENSION_SCALE; + var dimensionRatio = scale * (distance / (0.5 * Math.tan(0.5 * fov))); + var dimensions = Vec3.multiply(dimensionRatio, IMAGE_DIMENSIONS); + return {"dimensions": dimensions, "visible": true, "position": target, "url": isInfinite ? INFINITY_ICON_PATH : TARGET_ICON_PATH}; + } + + this.showTarget = function(headTarget, eyeTarget) { + if (!self.active) { + return; + } + var targetProps = self.getTargetProps(eyeTarget); + Overlays.editOverlay(self.eyeTarget, targetProps); + var headTargetProps = self.getTargetProps(headTarget); + Overlays.editOverlay(self.headTarget, headTargetProps); + } + + this.hideEyeTarget = function() { + Overlays.editOverlay(self.eyeTarget, {"visible": false}); + } + + this.highLightIDs = function(selectionName, ids) { + self.items[selectionName] = ids; + for (var idx in ids) { + Selection.addToSelectedItemsList(selectionName, "avatar", ids[idx]); + } + } + this.clearSelection = function() { + for (key in self.Styles) { + var selStyle = self.Styles[key]; + Selection.clearSelectedItemsList(selStyle.name); + } + } + + this.turnOff = function() { + self.clearLog(); + for (var key in self.Styles) { + var selStyle = self.Styles[key]; + Selection.disableListHighlight(selStyle.name); + } + Overlays.deleteOverlay(self.headTarget); + self.headTarget = undefined; + Overlays.deleteOverlay(self.eyeTarget); + self.eyeTarget = undefined; + } + + this.finish = function() { + if (self.active) { + self.setActive(false); + } + Overlays.deleteOverlay(self.textBox); + self.textBox = undefined; + Overlays.deleteOverlay(self.activeCheckBox); + self.activeCheckBox = undefined; + Overlays.deleteOverlay(self.activeCaption); + self.activeCaption = undefined; + Overlays.deleteOverlay(self.activeCheckBoxMark); + self.activeCheckBoxMark = undefined; + } + + this.init(); + } + + ////////////////////////////////////////////// + // randomHelper.js ////////// + ////////////////////////////////////////////// + + var RandomHelper = function() { + var self = this; + + this.createRandomIndexes = function(count) { + var indexes = []; + for (var n = 0; n < count; n++) { + indexes.push(n); + } + var randomIndexes = []; + for (var n = 0; n < count; n++) { + var indexesCount = indexes.length; + var randomIndex = 0; + if (indexesCount > 1) { + var randFactor = Math.random(); + randomIndex = randFactor !== 1.0 ? Math.floor(randFactor * indexes.length) : indexes.length - 1; + } + randomIndexes.push(indexes[randomIndex]); + indexes.splice(randomIndex, 1); + } + return randomIndexes; + } + + this.getRandomKey = function(keypool) { + // keypool can be an object. {key1: percentage1, key2: percentage2} or + // keypool can be an array. [key1, key2] Equal percentage each component + + var equalChance = Array.isArray(keypool); + var totalPercentage = 0.0; + var percentages = {}; + var normalizedPercentages = {}; + var keys = equalChance ? keypool : Object.keys(keypool); + for (var n = 0; n < keys.length; n++) { + var key = keys[n]; + percentages[key] = equalChance ? 1.0 / keys.length : keypool[key].chance; + totalPercentage += equalChance ? percentages[key] : keypool[key].chance; + } + var accumulatedVal = 0.0; + for (var n = 0; n < keys.length; n++) { + var key = keys[n]; + var val = accumulatedVal + (percentages[key] / totalPercentage); + normalizedPercentages[key] = val; + accumulatedVal = val; + } + var dice = Math.random(); + var floor = 0.0; + var hit = normalizedPercentages[keys[0]]; + for (var n = 0; n < keys.length; n++) { + var key = keys[n]; + if (dice > floor && dice < normalizedPercentages[key]) { + hit = key; + break; + } + floor = normalizedPercentages[key]; + } + return { randomKey: hit, chance: percentages[hit]}; + } + } + + + ////////////////////////////////////////////// + // automaticMachine.js ////////// + ////////////////////////////////////////////// + var MIN_LOOKAT_HEAD_MIX_ALPHA = 0.04; + var MAX_LOOKAT_HEAD_MIX_ALPHA = 0.08; + var CAMERA_HEAD_MIX_ALPHA = 0.06; + + var TargetType = { + "unknown" : 0, + "avatar" : 1, + "entity" : 2 + } + + var TargetOffsetMode = { + "noOffset" : 0, + "onlyHead" : 1, + "onlyEyes" : 2, + "headAndEyes" : 3, + "print" : function(sta) { + return ("OffsetMode: " + (Object.keys(TargetOffsetMode))[sta]); + } + } + + var TargetMode = { + "noTarget" : 0, + "leftEye" : 1, + "rightEye" : 2, + "mouth" : 3, + "leftHand" : 4, + "rightHand" : 5, + "random" : 6, + "print" : function(sta) { + return ("TargetMode: " + (Object.keys(TargetMode))[sta]); + } + } + + var ACTION_CONFIGURATION = { + "TargetMode.mouth": { + "joint": "Head", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.7}, + "TargetOffsetMode.onlyHead": {"chance": 0.3}, + "TargetOffsetMode.onlyEyes": {"chance": 0.0}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 5.0}, + "chanceWhileListening" : 0.45, + "chanceWhileTalking" : 0.25 + }, + "TargetMode.leftEye": { + "joint": "LeftEye", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.5}, + "TargetOffsetMode.onlyHead": {"chance": 0.3}, + "TargetOffsetMode.onlyEyes": {"chance": 0.1}, + "TargetOffsetMode.headAndEyes": {"chance": 0.1} + }, + "offsetAngleRange" : {"min": 1.0, "max": 5.0}, + "chanceWhileListening" : 0.20, + "chanceWhileTalking" : 0.30 + }, + "TargetMode.rightEye": { + "joint": "RightEye", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.5}, + "TargetOffsetMode.onlyHead": {"chance": 0.3}, + "TargetOffsetMode.onlyEyes": {"chance": 0.1}, + "TargetOffsetMode.headAndEyes": {"chance": 0.1} + }, + "offsetAngleRange" : {"min": 1.0, "max": 5.0}, + "chanceWhileListening" : 0.20, + "chanceWhileTalking" : 0.30 + }, + "TargetMode.leftHand": { + "joint": "LeftHand", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.9}, + "TargetOffsetMode.onlyHead": {"chance": 0.1}, + "TargetOffsetMode.onlyEyes": {"chance": 0.0}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 10.0}, + "chanceWhileListening" : 0.05, + "chanceWhileTalking" : 0.05 + }, + "TargetMode.rightHand": { + "joint": "RightHand", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.9}, + "TargetOffsetMode.onlyHead": {"chance": 0.1}, + "TargetOffsetMode.onlyEyes": {"chance": 0.0}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 10.0}, + "chanceWhileListening" : 0.05, + "chanceWhileTalking" : 0.05 + }, + "TargetMode.random": { + "joint": "Head", + "stareTimeRange" : {"min": 0.2, "max": 1.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.0}, + "TargetOffsetMode.onlyHead": {"chance": 0.0}, + "TargetOffsetMode.onlyEyes": {"chance": 0.4}, + "TargetOffsetMode.headAndEyes": {"chance": 0.6} + }, + "offsetAngleRange" : {"min": 5.0, "max": 12.0}, + "chanceWhileListening" : 0.05, + "chanceWhileTalking" : 0.05 + }, + "TargetMode.noTarget": { + "joint": undefined, + "stareTimeRange" : {"min": 0.1, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.5}, + "TargetOffsetMode.onlyHead": {"chance": 0.0}, + "TargetOffsetMode.onlyEyes": {"chance": 0.5}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 15.0}, + "chanceWhileListening" : 0.0, + "chanceWhileTalking" : 0.0 + } + } + + var FOCUS_MODE_CHANCES = { + "idle" : { + "TargetMode.mouth": {"chance": 0.2}, + "TargetMode.rightEye": {"chance": 0.4}, + "TargetMode.leftEye": {"chance": 0.4} + }, + "talking" : ["TargetMode.mouth", "TargetMode.rightEye", "TargetMode.leftEye"] // Equal chances (33.33% each) + } + + var LookAction = function() { + var self = this; + this.targetType = TargetType.unknown; + this.speed = MIN_LOOKAT_HEAD_MIX_ALPHA; + this.id = ""; + this.focusName = "None"; + this.focusChance = 0.0; + this.config = undefined; + this.targetMode = undefined; + this.lookAtJoint = undefined; + this.targetPoint = undefined; + this.elapseTime = 0.0; + this.totalTime = 1.0; + this.eyesHeadOffset = Vec3.ZERO; + this.eyesForward = false; + this.offsetEyes = false; + this.offsetHead = false; + this.offsetModeName = ""; + this.offsetChance = 0.0; + this.confortAngle = 0.0; + this.printChance = function(chance) { + return "" + Math.floor(100 * chance) + "%"; + } + this.print = function() { + var lines = []; + lines.push(TargetMode.print(eval(self.targetMode)) + " P: " + self.printChance(self.focusChance)); + lines.push(TargetOffsetMode.print(eval(self.offsetModeName)) + " P: " + self.printChance(self.offsetChance)); + lines.push("Action time: " + self.totalTime.toFixed(2) + " seconds"); + return lines; + } + } + + var AudienceAvatar = function(id) { + var self = this; + this.id = id; + this.name = ""; + this.engaged = true; + this.moved = true; + this.isTalking = false; + this.isListening = false; + this.position = Vec3.ZERO; + this.headPosition = Vec3.ZERO; + this.leftPalmPosition = Vec3.ZERO; + this.rightPalmPosition = Vec3.ZERO; + this.leftHandSpeed = 0.0; + this.rightHandSpeed = 0.0; + this.velocity = 0.0; + this.reactionTime = 0.0; + this.distance = 0.0; + this.loudness = 0.0; + this.talkingTime = 0.0; + } + + + var SmartLookMachine = function() { + var self = this; + this.myAvatarID = MyAvatar.sessionUUID; + + this.nearAvatarList = {}; + this.dice = new RandomHelper(); + + var LOOK_FOR_AVATARS_MAX_DISTANCE = 15.0; + var MIN_FOCUS_TO_LISTENER_TIME = 3.0; + var MAX_FOCUS_TO_LISTENER_TIME = 5.0; + var MIN_FOCUS_TO_TALKER_TIME = 0.5; + var MAX_FOCUS_TO_TALKER_RANGE = 1.5; + var TRIGGER_FOCUS_WHILE_IDLE_CHANCE = 0.1; + + + + this.currentAvatarFocusID = undefined; + this.currentTalker; + this.currentAction = new LookAction(); + + this.eyesTargetPoint = Vec3.ZERO; + this.headTargetPoint = Vec3.ZERO; + + + this.timeScale = 1.0; + this.lookAtDebugger = new LookAtDebugger(); + + this.active = true; + this.headTargetSpeed = 0.0; + + this.avatarFocusTotalTime = 0.0; + this.avatarFocusMax = 0.0; + this.lockedFocusID = undefined; + + this.visibilityCount = 0; + + this.shouldUpdateDebug = false; + + var TalkingState = { + "noTalking" : 0, + "meTalkingFirst" : 1, + "meTalkingAgain" : 2, + "otherTalkingFirst" : 3, + "otherTalkingAgain" : 4, + "othersTalking": 5, + "print" : function(sta) { + return ("TalkingState: " + (Object.keys(TalkingState))[sta]); + } + } + this.talkingState = TalkingState.noTalking; + var FocusState = { + "onNobody" : 0, + "onTalker" : 1, + "onRandomAudience" : 2, + "onLastTalker" : 3, + "onRandomLastTalker" : 4, + "onLastFocus" : 5, + "onSelected" : 6, + "onMovement" : 7, + "print" : function(sta) { + return ("FocusState: " + (Object.keys(FocusState))[sta]); + } + } + + this.focusState = FocusState.onNobody; + + var LockFocusType = { + "none" : 0, + "click" : 1, + "movement" : 2 + } + self.lockFocusType = LockFocusType.none; + + this.wasMeTalking = false; + this.nearAvatarIDs = []; + + this.updateAvatarVisibility = function() { + if (self.nearAvatarIDs.length > 0) { + if (self.nearAvatarList[self.myAvatarID] && self.nearAvatarList[self.myAvatarID].moved) { + for (id in self.nearAvatarList) { + self.nearAvatarList[id].moved = true; + } + self.nearAvatarList[self.myAvatarID].moved = false; + } + self.visibilityCount = ((self.visibilityCount + 1) >= self.nearAvatarIDs.length) ? 0 : self.visibilityCount + 1; + var id = self.nearAvatarIDs[self.visibilityCount]; + var avatar = self.nearAvatarList[id]; + if (id !== self.myAvatarID && avatar !== undefined && avatar.moved) { + self.nearAvatarList[id].moved = false; + var eyePos = MyAvatar.getDefaultEyePosition(); + var avatarSight = Vec3.subtract(avatar.headPosition, eyePos); + var intersection = Entities.findRayIntersection({origin: eyePos, direction: Vec3.normalize(avatarSight)}, true); + self.nearAvatarList[avatar.id].engaged = !intersection.intersects || intersection.distance > Vec3.length(avatarSight); + } + } + } + + this.getEngagedAvatars = function() { + var engagedAvatarIDs = []; + for (var id in self.nearAvatarList) { + if (self.nearAvatarList[id].engaged) { + engagedAvatarIDs.push(id); + } + } + return engagedAvatarIDs; + } + + this.updateAvatarList = function(deltaTime) { + var TALKING_LOUDNESS_THRESHOLD = 50.0; + var SILENCE_TALK_ATTENUATION = 0.5; + var ATTENTION_HANDS_SPEED = 5.0; + var ATTENTION_AVATAR_SPEED = 2.0; + var MAX_TALKING_TIME = 5.0; + var talkingAvatarID; + var maxLoudness = 0.0; + var count = 0; + var previousTalkers = []; + var fastHands = []; + var fastMovers = []; + var lookupCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(0.8 * LOOK_FOR_AVATARS_MAX_DISTANCE, Quat.getFront(MyAvatar.orientation))); + var nearbyAvatars = AvatarManager.getAvatarsInRange(lookupCenter, LOOK_FOR_AVATARS_MAX_DISTANCE); + for (var n = 0; n < nearbyAvatars.length; n++) { + var avatar = AvatarManager.getAvatar(nearbyAvatars[n]); + var distance = Vec3.distance(MyAvatar.position, avatar.position); + var loudness = avatar.audioLoudness; + loudness = avatar.audioLoudness > 30.0 ? 100.0 : 0.0; + var TALKING_TAU = 0.01; + if (self.nearAvatarList[avatar.sessionUUID] === undefined) { + self.nearAvatarList[avatar.sessionUUID] = new AudienceAvatar(avatar.sessionUUID); + self.nearAvatarList[avatar.sessionUUID].name = avatar.displayName; + } else { + if (Vec3.distance(self.nearAvatarList[avatar.sessionUUID].position, avatar.position) > 0.0) { + self.nearAvatarList[avatar.sessionUUID].moved = true; + } else { + self.nearAvatarList[avatar.sessionUUID].velocity = Vec3.length(avatar.velocity); + if (self.nearAvatarList[avatar.sessionUUID].velocity > 0.0) { + self.nearAvatarList[avatar.sessionUUID].moved = true; + } + } + self.nearAvatarList[avatar.sessionUUID].position = avatar.position; + self.nearAvatarList[avatar.sessionUUID].headPosition = avatar.getJointPosition("Head"); + if (self.nearAvatarList[avatar.sessionUUID].engaged) { + self.nearAvatarList[avatar.sessionUUID].loudness = self.nearAvatarList[avatar.sessionUUID].loudness + TALKING_TAU * (loudness - self.nearAvatarList[avatar.sessionUUID].loudness); + self.nearAvatarList[avatar.sessionUUID].orientation = avatar.orientation; + var leftPalmPos = avatar.getJointPosition("LeftHand"); + var rightPalmPos = avatar.getJointPosition("RightHand"); + + var distanceAttenuation = (distance > 1.0) ? (1.0 / distance) : 1.0; + var leftPalmSpeed = distanceAttenuation * Vec3.distance(self.nearAvatarList[avatar.sessionUUID].leftPalmPosition, leftPalmPos) / deltaTime; + var rightPalmSpeed = distanceAttenuation * Vec3.distance(self.nearAvatarList[avatar.sessionUUID].rightPalmPosition, rightPalmPos) / deltaTime; + self.nearAvatarList[avatar.sessionUUID].leftPalmSpeed = leftPalmSpeed; + self.nearAvatarList[avatar.sessionUUID].rightPalmSpeed = rightPalmSpeed; + + self.nearAvatarList[avatar.sessionUUID].leftPalmPosition = leftPalmPos; + self.nearAvatarList[avatar.sessionUUID].rightPalmPosition = rightPalmPos; + if (self.nearAvatarList[avatar.sessionUUID] && self.nearAvatarList[avatar.sessionUUID].loudness) { + self.nearAvatarList[avatar.sessionUUID].loudness += 0.1; + } + self.nearAvatarList[avatar.sessionUUID].isTalking = false; + self.nearAvatarList[avatar.sessionUUID].isTalker = false; + if (self.nearAvatarList[avatar.sessionUUID].loudness > TALKING_LOUDNESS_THRESHOLD) { + if (self.nearAvatarList[avatar.sessionUUID].talkingTime < MAX_TALKING_TIME) { + self.nearAvatarList[avatar.sessionUUID].talkingTime += deltaTime; + } + self.nearAvatarList[avatar.sessionUUID].isTalking = true; + count++; + if (maxLoudness < self.nearAvatarList[avatar.sessionUUID].loudness) { + maxLoudness = self.nearAvatarList[avatar.sessionUUID].loudness; + talkingAvatarID = avatar.sessionUUID; + } + } else if (self.nearAvatarList[avatar.sessionUUID].talkingTime > 0.0){ + self.nearAvatarList[avatar.sessionUUID].talkingTime -= SILENCE_TALK_ATTENUATION * deltaTime; + if (self.nearAvatarList[avatar.sessionUUID].talkingTime < 0.0) { + self.nearAvatarList[avatar.sessionUUID].talkingTime = 0.0; + } + } + if (!self.nearAvatarList[avatar.sessionUUID].isTalking && self.nearAvatarList[avatar.sessionUUID].talkingTime > 0.0){ + previousTalkers.push(avatar.sessionUUID); + } + if ((leftPalmSpeed > ATTENTION_AVATAR_SPEED || rightPalmSpeed > ATTENTION_AVATAR_SPEED) && avatar.sessionUUID !== self.myAvatarID) { + if (!self.nearAvatarList[avatar.sessionUUID].isTalking) { + fastHands.push(avatar.sessionUUID); + } else if (self.nearAvatarList[avatar.sessionUUID].isTalking) { + var headPos = avatar.getJointPosition("Neck"); + var raisedHand = Math.max(leftPalmPos.y, rightPalmPos.y) > headPos.y; + if (raisedHand) { + // If the talker raise the hands it will trigger attention + fastHands.push(avatar.sessionUUID); + } + } + } + } + } + } + + for (var id in self.nearAvatarList) { + if (nearbyAvatars.indexOf(id) == -1) { + delete self.nearAvatarList[id]; + } + } + self.nearAvatarIDs = Object.keys(self.nearAvatarList); + if (self.nearAvatarList[self.myAvatarID] === undefined) { + self.myAvatarID = MyAvatar.sessionUUID; + } + if (talkingAvatarID !== undefined) { + self.nearAvatarList[talkingAvatarID].isTalker = true; + } + self.updateAvatarVisibility(); + return { talker: talkingAvatarID, talkingCount: count, previousTalkers: previousTalkers, fastHands: fastHands, fastMovers: fastMovers }; + } + + this.getHeadConfortAngle = function(point) { + var eyesToPoint = Vec3.subtract(point, MyAvatar.getDefaultEyePosition()); + var angle = Vec3.getAngle(eyesToPoint, Quat.getFront(MyAvatar.orientation)) / DEGREE_TO_RADIAN; + angle = Math.min(angle, 90.0); + var MAX_OFFSET_DEGREES = 20.0; + var offsetMultiplier = MAX_OFFSET_DEGREES / 90.0; + var facingRight = Vec3.dot(eyesToPoint, Quat.getRight(MyAvatar.orientation)) > 0.0; + angle = (facingRight ? 1.0 : -1.0) * offsetMultiplier * angle; + return angle; + } + + this.updateCurrentAction = function(deltaTime) { + if (self.currentAction.lookAtJoint !== undefined) { + var avatar = AvatarList.getAvatar(self.currentAction.id); + self.currentAction.targetPoint = avatar.getJointPosition(self.currentAction.lookAtJoint); + } + self.currentAction.elapseTime += deltaTime; + } + + this.requestNewAction = function(targetType, id) { + + var HAND_ATTENTION_TRIGGER_SPEED = 0.2; + var action = new LookAction(); + action.targetType = targetType; + action.id = id; + var sortStare = false; + action.targetMode = "TargetMode.noTarget"; + if (targetType == TargetType.avatar && id !== undefined && self.nearAvatarList[id] !== undefined) { + var avatar = AvatarList.getAvatar(id); + action.focusName = self.nearAvatarList[id].name; + if (self.nearAvatarList[id].rightPalmSpeed > HAND_ATTENTION_TRIGGER_SPEED || self.nearAvatarList[id].leftPalmSpeed > HAND_ATTENTION_TRIGGER_SPEED) { + if (self.nearAvatarList[id].rightPalmSpeed < self.nearAvatarList[id].leftPalmSpeed) { + action.targetMode = "TargetMode.leftHand"; + } else { + action.targetMode = "TargetMode.rightHand"; + } + action.focusChance = 1.0; + } else { + var faceChances = self.nearAvatarList[id].isTalking ? FOCUS_MODE_CHANCES.talking : FOCUS_MODE_CHANCES.idle; + var randomFaceTargetMode = self.dice.getRandomKey(faceChances); + action.targetMode = randomFaceTargetMode.randomKey; + action.focusChance = randomFaceTargetMode.chance; + } + + } else if (targetType == TargetType.entity) { + // TODO + // Randomize around the entity size + } + var actionConfig = ACTION_CONFIGURATION[action.targetMode]; + action.config = actionConfig; + action.lookAtJoint = actionConfig.joint; + action.targetPoint = action.lookAtJoint !== undefined ? avatar.getJointPosition(action.lookAtJoint) : action.targetPoint = MyAvatar.getHeadLookAt(); + var randomKeyResult = self.dice.getRandomKey(actionConfig.offsetChances); + action.offsetModeName = randomKeyResult.randomKey; + action.offsetChance = randomKeyResult.chance; + var offsetMode = eval(action.offsetModeName); + if (offsetMode !== TargetOffsetMode.noOffset) { + var headPosition = MyAvatar.getJointPosition("Head"); + var headToTarget = Vec3.subtract(action.targetPoint, headPosition); + var randAngle = actionConfig.offsetAngleRange.min + Math.random() * (actionConfig.offsetAngleRange.max - actionConfig.offsetAngleRange.min); + var randAngle = Math.random() < 0.5 ? -randAngle : randAngle; + if (self.nearAvatarList[self.myAvatarID]) { + var randOffsetRotation = Quat.angleAxis(randAngle, Vec3.UNIT_Y); + action.eyesHeadOffset = Vec3.subtract(Vec3.sum(headPosition, Vec3.multiplyQbyV(randOffsetRotation, headToTarget)), action.targetPoint); + } + action.offsetEyes = offsetMode === TargetOffsetMode.onlyEyes || offsetMode === TargetOffsetMode.headAndEyes; + action.offsetHead = offsetMode === TargetOffsetMode.onlyHead || offsetMode === TargetOffsetMode.headAndEyes; + } + action.totalTime = actionConfig.stareTimeRange.min + Math.random() * (actionConfig.stareTimeRange.max - actionConfig.stareTimeRange.min); + action.confortAngle = self.getHeadConfortAngle(action.targetPoint); + action.speed = actionConfig.headSpeedRange.min + Math.random() * (actionConfig.headSpeedRange.max - actionConfig.headSpeedRange.min); + + return action; + } + + this.findAudienceAvatar = function(avatarIDs) { + // We look for avatars on the avatarIDs array if provided + // If not avatarIDs becomes the array with all the engaged avatars nearAvatarList + if (avatarIDs === undefined) { + avatarIDs = self.nearAvatarIDs; + } + var randAvatarID; + var MAX_AUDIENCE_DISTANCE = 8; + var firstAnyOther = undefined; + var firstNearOther = undefined; + if (avatarIDs.length > 1) { + var randomIndexes = self.dice.createRandomIndexes(avatarIDs.length); + for (var n = 0; n < randomIndexes.length; n++) { + var avatarID = avatarIDs[randomIndexes[n]]; + var avatar = self.nearAvatarList[avatarID]; + if (avatarID != self.myAvatarID && avatar.engaged) { + firstAnyOther = !firstAnyOther ? avatarID : firstAnyOther; + + if (avatar.distance < MAX_AUDIENCE_DISTANCE) { + firstNearOther = !firstNearOther ? avatarID : firstNearOther; + var otherToMe = Vec3.normalize(Vec3.subtract(self.nearAvatarList[self.myAvatarID].position, avatar.position)); + var myFront = Quat.getFront(self.nearAvatarList[self.myAvatarID].orientation); + var otherFront = Quat.getFront(avatar.orientation); + if (Vec3.dot(otherToMe, otherFront) > 0.0 && Vec3.dot(myFront, otherToMe) < 0.0) { + randAvatarID = avatarID; + break; + } + } + if (n === randomIndexes.length - 1) { + // We have not found a valid candidate facing us + // return the first id different from out avatar's id + randAvatarID = firstNearOther !== undefined ? firstNearOther : firstAnyOther; + } + } + } + } else if (avatarIDs.length > 0 && avatarIDs[0] != self.myAvatarID && self.nearAvatarList[avatarIDs[0]].engaged){ + // If the array provided only has one ID + randAvatarID = avatarIDs[0]; + } + return randAvatarID; + } + + this.applyHeadOffset = function(point, angle) { + var eyesToPoint = Vec3.subtract(point, MyAvatar.getDefaultEyePosition()); + var offsetRot = Quat.angleAxis(angle, Quat.getUp(MyAvatar.orientation)); + var offsetPoint = Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(offsetRot, eyesToPoint)); + return offsetPoint; + } + + this.computeTalkingState = function(sceneData, myAvatarID, currentTalker, currentFocus) { + var talkingState = TalkingState.noTalking; + if (sceneData.talker === myAvatarID) { + if (currentTalker != myAvatarID) { + talkingState = TalkingState.meTalkingFirst; + } else { + talkingState = TalkingState.meTalkingAgain; + } + } else if (sceneData.talkingCount > 1) { + talkingState = TalkingState.othersTalking; + } else if (sceneData.talkingCount > 0) { + if (sceneData.talker !== currentFocus) { + talkingState = TalkingState.otherTalkingFirst; + } else { + talkingState = TalkingState.otherTalkingAgain; + } + } + return talkingState; + } + + this.computeFocusState = function(sceneData, talkingState, currentFocus, lockedFocus, lockType) { + var focusState = FocusState.onNobody; + switch (talkingState) { + case TalkingState.noTalking : { + if (sceneData.previousTalkers.length > 0) { + focusState = FocusState.onLastTalker; + } else if (Math.random() < TRIGGER_FOCUS_WHILE_IDLE_CHANCE) { + // There is chance of triggering a random focus when nobody is talking + focusState = FocusState.onRandomAudience; + } else { + focusState = FocusState.onNobody; + } + break; + } + case TalkingState.meTalkingFirst : { + if (currentFocus !== undefined) { + // Look at the last focused avatar + focusState = FocusState.onLastFocus; + } else if (sceneData.previousTalkers.length > 0) { + // Look at one of the previous talkers + focusState = FocusState.onRandomLastTalker; + } else { + focusState = FocusState.onRandomAudience; + } + break; + } + case TalkingState.meTalkingAgain : { + // Look at any random avatar + focusState = FocusState.onRandomAudience; + break; + } + case TalkingState.otherTalkingAgain : { + // If we were focused already on the talker we have a 15% chance to look at somebody else + // randomly giving preference to the previous talkers + if (Math.random() < 0.15) { + focusState = FocusState.onRandomLastTalker; + } else { + focusState = FocusState.onTalker; + } + break; + } + case TalkingState.otherTalkingFirst : { + // Focus on the new talker + focusState = FocusState.onTalker; + break; + } + case TalkingState.othersTalking : { + // When multiple people talk at the same time we have a 50% chance of not changing focus + if (Math.random() < 0.5) { + focusState = FocusState.onLastFocus; + } else { + focusState = FocusState.onTalker; + } + break; + } + } + if (lockedFocus !== undefined) { + if (lockType === LockFocusType.click) { + focusState = FocusState.onSelected; + } else if (lockType === LockFocusType.movement) { + focusState = FocusState.onMovement; + } + } + return focusState; + } + + this.computeAvatarFocus = function(sceneData, focusState, currentFocus, lockedFocus) { + var avatarFocusID = undefined; + switch (focusState) { + case FocusState.onTalker: { + avatarFocusID = sceneData.talker; + break; + } + case FocusState.onRandomAudience: { + avatarFocusID = self.findAudienceAvatar(); + break; + } + case FocusState.onLastTalker: + case FocusState.onRandomLastTalker: { + if (sceneData.previousTalkers.length > 0) { + avatarFocusID = self.findAudienceAvatar(sceneData.previousTalkers); + } + if (avatarFocusID === undefined) { + // Guarantee a 20% chance of looking at somebody + if (focusState === FocusState.onRandomLastTalker || Math.random() < 0.2) { + avatarFocusID = self.findAudienceAvatar(); + } + } + break; + } + case FocusState.onLastFocus: { + avatarFocusID = currentFocus; + break; + } + case FocusState.onMovement: + case FocusState.onSelected: { + avatarFocusID = lockedFocus; + break; + } + } + return avatarFocusID; + } + + this.forceFocus = function(avatarID) { + if (self.nearAvatarList[avatarID] !== undefined) { + self.lockedFocusID = avatarID; + self.lockFocusType = LockFocusType.click; + } + } + + this.logAction = function(action) { + self.lookAtDebugger.clearLog(); + self.lookAtDebugger.log(TalkingState.print(self.talkingState)); + self.lookAtDebugger.log("________________________Focus"); + self.lookAtDebugger.log("On avatar: " + action.focusName); + self.lookAtDebugger.log(FocusState.print(self.focusState)); + self.lookAtDebugger.log("Focus time: " + self.avatarFocusMax.toFixed(2) + " seconds"); + self.lookAtDebugger.log("________________________Action"); + var extraLogs = action.print(); + for (var n = 0; n < extraLogs.length; n++) { + self.lookAtDebugger.log(extraLogs[n]); + } + } + + this.update = function(deltaTime) { + var FPS = 60.0; + var CLICKED_AVATAR_MAX_FOCUS_TIME = 10.0; + self.timeScale = deltaTime * FPS; + + var sceneData = self.updateAvatarList(deltaTime); + if (self.nearAvatarIDs.length === 0) { + return; + } + + var abortAction = self.lockFocusType === LockFocusType.click; + // Focus on any avatar moving their hands + if (sceneData.fastHands.length > 0 && self.lockFocusType === LockFocusType.none) { + var randomFastHands = self.findAudienceAvatar(sceneData.fastHands); + if (self.nearAvatarList[randomFastHands] && (!self.nearAvatarList[randomFastHands].isTalking || self.nearAvatarList[randomFastHands].isTalker)) { + abortAction = Math.random() < 0.3; + self.lockedFocusID = randomFastHands; + self.lockFocusType = LockFocusType.movement; + } + } else if (self.avatarFocusTotalTime >= self.avatarFocusMax) { + self.lockFocusType = LockFocusType.none; + } + + // Set the talking status + self.talkingState = self.computeTalkingState(sceneData, self.myAvatarID, self.currentTalker, self.currentAvatarFocusID); + + // If the talker change, we have a 50% chance to focus on them once the next action is completed. + var otherTalkerTriggerRefocus = self.talkingState === TalkingState.otherTalkingFirst && Math.random() < 0.5; + if (self.lockedFocusID !== undefined || otherTalkerTriggerRefocus) { + // Force a new focus + self.avatarFocusTotalTime = self.avatarFocusMax; + if (abortAction) { + // Force a new action + self.currentAction.elapseTime = self.currentAction.totalTime; + } + } + var needsNewFocus = self.avatarFocusTotalTime >= self.avatarFocusMax; + var needsNewAction = self.currentAction.elapseTime >= self.currentAction.totalTime; + var newAvatarFocusID = self.currentAvatarFocusID; + + if (needsNewAction && needsNewFocus) { + self.focusState = self.computeFocusState(sceneData, self.talkingState, self.currentAvatarFocusID, self.lockedFocusID, self.lockFocusType); + newAvatarFocusID = self.computeAvatarFocus(sceneData, self.focusState, self.currentAvatarFocusID, self.lockedFocusID); + self.lockedFocusID = undefined; + if (self.lockFocusType !== LockFocusType.click) { + if (self.talkingState === TalkingState.meTalkingAgain) { + self.avatarFocusMax = MIN_FOCUS_TO_TALKER_TIME + Math.random() * MAX_FOCUS_TO_TALKER_RANGE; + } else { + self.avatarFocusMax = MIN_FOCUS_TO_LISTENER_TIME + Math.random() * MAX_FOCUS_TO_LISTENER_TIME; + } + } else { + self.avatarFocusMax = CLICKED_AVATAR_MAX_FOCUS_TIME; + } + self.avatarFocusTotalTime = 0.0; + self.currentTalker = sceneData.talker; + self.shouldUpdateDebug = true; + } else { + self.avatarFocusTotalTime += deltaTime; + } + if (needsNewAction) { + var currentFocus = newAvatarFocusID; + if (otherTalkerTriggerRefocus) { + // Reset the last action on the previous focus to provide a random delay + currentFocus = self.currentAction.id; + self.currentAction.elapseTime = 0.0; + } else { + // Create a new action + self.currentAction = self.requestNewAction(TargetType.avatar, newAvatarFocusID); + } + + if (self.currentAvatarFocusID !== newAvatarFocusID) { + self.currentAvatarFocusID = newAvatarFocusID; + } + + if (self.currentAvatarFocusID === undefined || + self.talkingState === TalkingState.meTalkingAgain || + self.talkingState === TalkingState.meTalkingFirst) { + // Minimize the head speed when we are talking or looking nowhere + self.currentAction.speed = MIN_LOOKAT_HEAD_MIX_ALPHA; + } + + self.logAction(self.currentAction); + } else { + self.updateCurrentAction(deltaTime); + } + + self.headTargetPoint = self.currentAction.targetPoint; + self.eyesTargetPoint = self.currentAction.targetPoint; + if (self.currentAction.offsetHead) { + self.headTargetPoint = Vec3.sum(self.headTargetPoint, self.currentAction.eyesHeadOffset); + } + self.headTargetPoint = self.applyHeadOffset(self.headTargetPoint, self.currentAction.confortAngle); + if (self.currentAction.offsetEyes) { + self.eyesTargetPoint = Vec3.sum(self.eyesTargetPoint, self.currentAction.eyesHeadOffset); + } + self.headTargetSpeed = Math.min(1.0, self.currentAction.speed * self.timeScale); + } + + this.getResults = function() { + return { + "eyesTarget" : self.eyesTargetPoint, + "headTarget" : self.headTargetPoint, + "headSpeed" : self.headTargetSpeed + } + } + + } + + ////////////////////////////////////////////// + // autoLook.js /////// + ////////////////////////////////////////////// + + var LookAtController = function() { + var CAMERA_HEAD_MIX_ALPHA = 0.06; + var LookState = { + "CameraLookActivating": 0, + "CameraLookActive":1, + "ClickToLookDeactivating":2, + "ClickToLookActive":3, + "AutomaticLook": 4 + } + + var self = this; + this.smartLookAt = new SmartLookMachine(); + this.currentState = LookState.AutomaticLook; + this.lookAtTarget = undefined; + this.lookingAtAvatarID = undefined; + this.lookingAvatarJointIndex = undefined; + + this.interpolatedHeadLookAt = MyAvatar.getHeadLookAt(); + + var CLICK_TO_LOOK_TOTAL_TIME = 5.0; + this.clickToLookTimer = 0; + + var CAMERA_LOOK_TOTAL_TIME = 5.0; + this.cameraLookTimer = 0; + + this.timeScale = 1.0; + this.eyesTarget = Vec3.ZERO; + this.headTarget = Vec3.ZERO; + + this.mousePressEvent = function(event) { + if (event.isLeftButton) { + self.smartLookAt.lookAtDebugger.onClick(event); + if (self.currentState === LookState.AutomaticLook) { + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = AvatarManager.findRayIntersection({origin: pickRay.origin, direction: pickRay.direction}, [], [self.smartLookAt.myAvatarID], false); + self.lookingAtAvatarID = intersection.intersects ? intersection.avatarID : undefined; + if (self.lookingAtAvatarID) { + self.smartLookAt.forceFocus(self.lookingAtAvatarID); + } + } + } + } + + this.mouseMoveEvent = function(event) { + if (event.isRightButton && self.currentState === LookState.AutomaticLook) { + self.currentState = LookState.CameraLookActivating; + self.cameraLookTimer = 0.0; + } + } + + this.updateHeadLookAtTarget = function(target, interpolatedTarget, speed, noPitch) { + var eyesPosition = MyAvatar.getDefaultEyePosition(); + var targetRot = Quat.lookAt(eyesPosition, target, Quat.getUp(MyAvatar.orientation)); + var interpolatedRot = Quat.lookAt(eyesPosition, interpolatedTarget, Quat.getUp(MyAvatar.orientation)); + var newInterpolatedRot = Quat.mix(interpolatedRot, targetRot, speed); + var newInterpolatedTarget = Vec3.sum(eyesPosition, Vec3.multiply(Vec3.distance(eyesPosition, target), Quat.getFront(newInterpolatedRot))); + // avoid pitch + if (noPitch) { + newInterpolatedTarget.y = eyesPosition.y; + } + MyAvatar.setHeadLookAt(newInterpolatedTarget); + return newInterpolatedTarget; + } + + this.retargetHeadTo = function(target, speed, noPitch, tolerance) { + var eyePos = MyAvatar.getDefaultEyePosition(); + var localTarget = Vec3.normalize(Vec3.subtract(target, eyePos)); + self.interpolatedHeadLookAt = self.updateHeadLookAtTarget(target, self.interpolatedHeadLookAt, speed, noPitch); + return (Vec3.dot(Vec3.normalize(Vec3.subtract(self.interpolatedHeadLookAt, eyePos)), Vec3.normalize(localTarget)) > tolerance); + } + + var MAX_INTERPOLING_STEPS = 100; + this.interpolatingSteps = 0.0; + this.automaticResults = {}; + + this.update = function(deltaTime) { + // Update timeScale + var FPS = 60.0; + self.timeScale = deltaTime * FPS; + var stateTransitSpeed = Math.min(1.0, CAMERA_HEAD_MIX_ALPHA * self.timeScale); + + var CLICK_RETARGET_TOLERANCE = 0.98; + var CAMERA_RETARGET_TOLERANCE = 0.98; + + var headTarget = MyAvatar.getHeadLookAt(); + var eyesTarget = MyAvatar.getEyesLookAt(); + + var isTargetValid = (self.lookAtTarget || self.lookingAtAvatarID); + if (isTargetValid && self.currentState === LookState.ClickToLookActive) { + if (self.lookingAtAvatarID) { + self.lookAtTarget = AvatarList.getAvatar(self.lookingAtAvatarID).getJointPosition(self.lookingAvatarJointIndex); + } + self.interpolatedHeadLookAt = self.updateHeadLookAtTarget(self.lookAtTarget, self.interpolatedHeadLookAt, stateTransitSpeed); + MyAvatar.setEyesLookAt(self.lookAtTarget); + if (self.clickToLookTimer > CLICK_TO_LOOK_TOTAL_TIME) { + self.currentState = LookState.ClickToLookDeactivating; + } + self.clickToLookTimer += deltaTime; + } else if (self.currentState === LookState.ClickToLookDeactivating) { + var breakInterpolation = self.interpolatingSteps > MAX_INTERPOLING_STEPS; + if (breakInterpolation || self.retargetHeadTo(self.automaticResults.headTarget, stateTransitSpeed, false, CLICK_RETARGET_TOLERANCE)) { + self.currentState = LookState.AutomaticLook; + self.interpolatingSteps = 0.0; + } + if (breakInterpolation) { + console.log("Breaking look at interpolation"); + } else if (self.interpolatingSteps++ === 0.0){ + console.log("Interpolating click"); + } + } else if (self.currentState === LookState.CameraLookActivating) { + var cameraFront = Quat.getFront(Camera.getOrientation()); + if (Camera.mode === "selfie") { + cameraFront = Vec3.multiply(-1, cameraFront); + } + var breakInterpolation = self.interpolatingSteps > MAX_INTERPOLING_STEPS; + if (breakInterpolation || self.retargetHeadTo(Vec3.sum(MyAvatar.getDefaultEyePosition(), cameraFront), stateTransitSpeed, true, CAMERA_RETARGET_TOLERANCE)) { + self.currentState = LookState.CameraLookActive; + self.interpolatingSteps = 0.0; + MyAvatar.releaseHeadLookAtControl(); + MyAvatar.releaseEyesLookAtControl(); + } + if (breakInterpolation) { + console.log("Breaking camera interpolation"); + } else if (self.interpolatingSteps++ === 0.0){ + console.log("Interpolating camera"); + } + } else if (self.currentState === LookState.CameraLookActive) { + if (self.cameraLookTimer > CAMERA_LOOK_TOTAL_TIME) { + // Set as initial target the current head look at + self.interpolatedHeadLookAt = MyAvatar.getHeadLookAt(); + self.currentState = LookState.AutomaticLook; + } + self.cameraLookTimer += deltaTime; + } else if (self.currentState === LookState.AutomaticLook) { + var updateLookat = Vec3.length(MyAvatar.velocity) < 1.0; + self.smartLookAt.update(deltaTime); + if (updateLookat) { + self.automaticResults = self.smartLookAt.getResults(); + self.headTarget = self.automaticResults.headTarget; + self.eyesTarget = self.automaticResults.eyesTarget; + self.interpolatedHeadLookAt = self.updateHeadLookAtTarget(self.headTarget, self.interpolatedHeadLookAt, self.automaticResults.headSpeed, true); + MyAvatar.setEyesLookAt(self.eyesTarget); + headTarget = self.interpolatedHeadLookAt; + eyesTarget = self.eyesTarget; + } else { + // Too fast. Stand by automatic look + MyAvatar.setEyesLookAt(MyAvatar.getEyesLookAt()); + MyAvatar.setHeadLookAt(MyAvatar.getHeadLookAt()); + } + } + if (self.smartLookAt.shouldUpdateDebug) { + // Update engaged avatars debugging when a new action is created + var engagedAvatars = self.smartLookAt.getEngagedAvatars(); + self.smartLookAt.lookAtDebugger.highLightAvatars(engagedAvatars, + self.smartLookAt.currentAction.id, + self.smartLookAt.currentTalker !== self.smartLookAt.currentAction.id ? self.smartLookAt.currentTalker : undefined); + } + self.smartLookAt.lookAtDebugger.showTarget(headTarget, eyesTarget); + } + + this.finish = function() { + self.smartLookAt.lookAtDebugger.finish(); + } + } + var lookAtController = new LookAtController(); + Controller.mousePressEvent.connect(lookAtController.mousePressEvent); + Controller.mouseMoveEvent.connect(lookAtController.mouseMoveEvent); + Script.update.connect(lookAtController.update); + Script.scriptEnding.connect(lookAtController.finish); +})(); \ No newline at end of file diff --git a/scripts/developer/debugging/debugWorkloadWithMouseHover.js b/scripts/developer/debugging/debugWorkloadWithMouseHover.js new file mode 100644 index 0000000000..eaff607359 --- /dev/null +++ b/scripts/developer/debugging/debugWorkloadWithMouseHover.js @@ -0,0 +1,124 @@ +// +// debugWorkloadWithMouseHover.js - render workload proxy for entity under mouse hover +// +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +"use strict"; + +(function() { + + Script.scriptEnding.connect(function () { + }); + + // Create a Laser pointer used to pick and add entity to selection + var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 }; + var COLOR1 = {red: 255, green: 0, blue: 255}; // magenta + var COLOR2 = {red: 255, green: 255, blue: 0}; // yellow + var end1 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR1, + ignorePickIntersection: true + } + var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignorePickIntersection: true + } + var laser = Pointers.createPointer(PickType.Ray, { + joint: "Mouse", + filter: Picks.PICK_ENTITIES | Picks.PICK_BYPASS_IGNORE | Picks.PICK_INCLUDE_COLLIDABLE | Picks.PICK_INCLUDE_NONCOLLIDABLE, + renderStates: [{name: "one", end: end1}], + defaultRenderStates: [{name: "one", end: end2, distance: 2.0}], + enabled: true + }); + Pointers.setRenderState(laser, "one"); + var hoveredObject = undefined; + + var SelectionListName = "DebugWorkloadSelection"; // sekret undocumented selection list (hard coded in C++) + var selectionStyle = { + isOutlineSmooth: true, + outlineWidth: 5, + outlineUnoccludedColor: {red: 255, green: 128, blue: 128}, + outlineUnoccludedAlpha: 0.88, + outlineOccludedColor: {red: 255, green: 128, blue: 128}, + outlineOccludedAlpha:0.5, + fillUnoccludedColor: {red: 26, green: 0, blue: 0}, + fillUnoccludedAlpha: 0.0, + fillOccludedColor: {red: 26, green: 0, blue: 0}, + fillOccludedAlpha: 0.0 + } + Selection.enableListHighlight(SelectionListName, selectionStyle) + + var isSelectionEnabled = false + + function setSelectionEnabled(enabled) { + if (isSelectionEnabled != enabled) { + isSelectionEnabled = enabled; + //print("isSelectionEnabled set to " + isSelectionEnabled.toString()) + if (isSelectionEnabled) { + Pointers.enablePointer(laser) + } else { + Pointers.disablePointer(laser) + Selection.clearSelectedItemsList(SelectionListName) + } + } + } + setSelectionEnabled(true); + + function getIntersectionTypeString(type) { + if (type === Picks.INTERSECTED_ENTITY) { + return "entity"; + } else if (type === Picks.INTERSECTED_OVERLAY) { + return "overlay"; + } else if (type === Picks.INTERSECTED_AVATAR) { + return "avatar"; + } + } + + function update() { + var result = Pointers.getPrevPickResult(laser); + if (result.intersects) { + if (hoveredObject !== undefined && result.objectID !== hoveredObject.objectID) { + // Hovering on something different + if (isSelectionEnabled) { + Selection.removeFromSelectedItemsList(SelectionListName, getIntersectionTypeString(hoveredObject.type), hoveredObject.objectID) + //print("remove helloDebugHighlight " + hoveredObject.objectID.toString()); + } + } + + if (isSelectionEnabled) { + if (hoveredObject === undefined || result.objectID !== hoveredObject.objectID) { + // Hovering over something new + Selection.addToSelectedItemsList(SelectionListName, getIntersectionTypeString(result.type), result.objectID); + hoveredObject = result; + //print("add helloDebugHighlight " + hoveredObject.objectID.toString() + " type = '" + getIntersectionTypeString(result.type) + "'"); + } + } + } else if (hoveredObject !== undefined) { + // Stopped hovering + if (isSelectionEnabled) { + Selection.removeFromSelectedItemsList(SelectionListName, getIntersectionTypeString(hoveredObject.type), hoveredObject.objectID) + hoveredObject = undefined; + //print("clear helloDebugHighlight"); + } + } + } + Script.update.connect(update); + + function cleanup() { + Pointers.removePointer(laser); + Selection.disableListHighlight(SelectionListName) + Selection.removeListFromMap(SelectionListName) + + } + Script.scriptEnding.connect(cleanup); + +}()); + + diff --git a/scripts/simplifiedUI/clickToZoom/clickToZoom.js b/scripts/simplifiedUI/clickToZoom/clickToZoom.js new file mode 100644 index 0000000000..2ce828ec8b --- /dev/null +++ b/scripts/simplifiedUI/clickToZoom/clickToZoom.js @@ -0,0 +1,509 @@ +// +// Created by Luis Cuenca on 11/14/19 +// Copyright 2019 High Fidelity, Inc. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var ZoomStatus = { + "zoomingIn" : 0, // The camera is moving towards the selected entity + "zoomingOut" : 1, // The camera is moving away from the selected entity + "zoomedIn" : 2, // The camera is locked looking at the selected entity + "zoomedOut" : 4, // The camera is on its initial position and mode + "consumed" : 5 // The zooming loop has been completed + } + + var FocusType = { + "avatar" : 0, + "entity" : 1 + } + + var EasingFunctions = { + easeInOutQuad: function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t }, + // accelerating from zero velocity + easeInCubic: function (t) { return t*t*t }, + // decelerating to zero velocity + easeOutCubic: function (t) { return (--t)*t*t+1 }, + // acceleration until halfway, then deceleration + easeInOutCubic: function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }, + // accelerating from zero velocity + easeInQuart: function (t) { return t*t*t*t }, + // decelerating to zero velocity + easeOutQuart: function (t) { return 1-(--t)*t*t*t }, + // acceleration until halfway, then deceleration + easeInOutQuart: function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t }, + // accelerating from zero velocity + easeInQuint: function (t) { return t*t*t*t*t }, + // decelerating to zero velocity + easeOutQuint: function (t) { return 1+(--t)*t*t*t*t }, + // acceleration until halfway, then deceleration + easeInOutQuint: function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t } + }; + // Class that manages the zoom effect + var ZoomData = function(type, lookAt, focusNormal, focusDimensions, velocity, maxDuration) { + var self = this; + + this.focusType = type; + this.lookAt = lookAt; // Look at 3d point in world coordinates + this.focusDimensions = focusDimensions; // 2d dimensions of the bounding box projection approximation of the selected entity + this.focusNormal = focusNormal; // The normal vector provided by the ray intersection used to initialize the zoom + this.velocity = velocity; // Max velocity of the camera zoom effect + this.maxDuration = maxDuration; // Max duration of the camera zoom effect + + this.initialPos = Camera.getPosition(); + this.initialRot = Camera.getOrientation(); + this.interpolatedPos = this.initialPos; + this.interpolatedRot = this.initialRot; + this.initialMode = Camera.mode; + this.initialOffset = Vec3.distance(self.initialPos, MyAvatar.getDefaultEyePosition()); // Save the offset distance from the camera to the avatar eyes (boom) + + this.finalPos = Vec3.ZERO; + this.finalRot = Quat.IDENTITY; + this.direction = Vec3.ZERO; // Direction vector for the camera path + this.distance = Vec3.ZERO; // Total distance that the camera needs to cover to zoom in along the path + this.totalTime = 0.0; // Total time needed to cover the zoom path + this.elapsedTime = 0.0; + + var MAX_ZOOM_IN_AMOUNT = 0.6; // Maximum percentage of camera proximity to the selected entity from the look at point + var MAX_ZOOM_OUT_AMOUNT = 0.2; // Maximum percentage of camera remoteness to the selected entity from the look at point + + this.maxZoomInAmount = MAX_ZOOM_IN_AMOUNT; + this.maxZoomOutAmount = MAX_ZOOM_OUT_AMOUNT; + this.currentZoomAmount = 0.0; + + this.zoomPanOffset = {x: 0.5 * Window.innerWidth, y: 0.5 * Window.innerHeight}; + this.zoomPanDelta = {x: 0.0, y: 0.0}; + + this.status = ZoomStatus.zoomedOut; + + var OWN_CAMERA_CHANGE_MAX_FRAMES = 30; + this.ownCameraChangeElapseTime = 0.0; // Variables used to track if the camera mode change is triggered by this script + this.ownCameraChange = false; // or by user input, since the later needs to abort the zoom loop + + + // Function called when the user release the mouse while panning the camera when using the secondary zoom + this.applyZoomPan = function() { + self.zoomPanOffset.x += self.zoomPanDelta.x; + self.zoomPanOffset.y += self.zoomPanDelta.y; + } + + // Function called when the user moves the mouse while panning the camera when using the secondary zoom + this.setZoomPanDelta = function(x, y) { + self.zoomPanDelta.x = x; + self.zoomPanDelta.y = y; + var totalX = self.zoomPanOffset.x + self.zoomPanDelta.x; + var totalY = self.zoomPanOffset.y + self.zoomPanDelta.y; + totalX = Math.min(Math.max(totalX, 0.0), Window.innerWidth); + totalY = Math.min(Math.max(totalY, 0.0), Window.innerHeight); + self.zoomPanDelta.x = totalX - self.zoomPanOffset.x; + self.zoomPanDelta.y = totalY - self.zoomPanOffset.y; + self.updateSuperPan(totalX, totalY); + } + + // This function computes the correct distance to the selected entity so it ends up fitted on the screen + this.getFocusDistance = function(zoomDims) { + var objAspect = zoomDims.x / zoomDims.y; + var camAspect = Camera.frustum.aspectRatio; + var m = 0.0; + if (objAspect < camAspect) { + m = zoomDims.y; + } else { + m = zoomDims.x / camAspect; + } + var DEGREE_TO_RADIAN = 0.0174533; + var fov = DEGREE_TO_RADIAN * Camera.frustum.fieldOfView; + return (0.5 * m) / Math.tan(0.5 * fov); + } + + // This function computes the final camera position and rotation + this.computeFinalForm = function() { + self.finalPos = Vec3.sum(self.lookAt, Vec3.multiply(self.getFocusDistance(self.focusDimensions), self.focusNormal)); + self.finalRot = Quat.lookAtSimple(self.finalPos, self.lookAt); + } + + // Function that computes the time and path information for the camera zoom in + this.computeRouteIn = function() { + var railVector = Vec3.subtract(self.finalPos, self.initialPos); + self.direction = Vec3.normalize(railVector); + self.distance = Vec3.length(railVector); + self.totalTime = self.distance / self.velocity; + self.totalTime = self.totalTime > self.maxDuration ? self.maxDuration : self.totalTime; + } + + // Function that computes the time and path information for the camera zoom out + // Since the camera orientation and position gets reset when changing camera modes, + // the camera path while zooming out is different from the path zooming in. + this.computeRouteOut = function() { + self.finalPos = Camera.getPosition(); + var camOffset = Vec3.ZERO; + var myAvatarRotation = MyAvatar.orientation; + if (self.initialMode.indexOf("first") != -1) { + self.initialRot = myAvatarRotation; + } else { + var lookAtPoint = MyAvatar.getDefaultEyePosition(); + var lookFromSign = self.initialMode.indexOf("selfie") != -1 ? 1 : -1; + var lookFromPoint = Vec3.sum(lookAtPoint, Vec3.multiply(self.initialOffset * lookFromSign, Quat.getFront(myAvatarRotation))); + self.initialRot = Quat.lookAtSimple(lookFromPoint, lookAtPoint); + self.initialPos = lookFromPoint; + } + self.computeRouteIn(); + } + + // This function initiate the camera zoom in + this.initZoomIn = function() { + if (self.status === ZoomStatus.zoomedOut) { + self.computeRouteIn(); + self.status = ZoomStatus.zoomingIn; + self.changeCameraMode("independent"); + self.elapsedTime = 0.0; + } + } + + // This function initiate the camera zoom in + this.initZoomOut = function() { + if (self.status === ZoomStatus.zoomedIn) { + self.computeRouteOut(); + self.status = ZoomStatus.zoomingOut; + self.changeCameraMode("independent"); + self.elapsedTime = 0.0; + } + } + + // This function checks if the zoom loop needs update + this.needsUpdate = function() { + return self.status === ZoomStatus.zoomingIn || self.status === ZoomStatus.zoomingOut; + } + + // This function connects to the update signal only when zoom in or zoom out is needed + this.updateZoom = function(deltaTime) { + if (self.ownCameraChange) { + self.ownCameraChange = self.ownCameraChangeElapseTime < OWN_CAMERA_CHANGE_MAX_FRAMES * deltaTime; + self.ownCameraChangeElapseTime += deltaTime; + } + if (!self.needsUpdate) { + return; + } + if (self.elapsedTime < self.totalTime) { + var ratio = EasingFunctions.easeInOutQuart(self.elapsedTime / self.totalTime); + + if (self.status === ZoomStatus.zoomingIn) { + var curDist = self.distance * ratio; + var addition = Vec3.multiply(curDist, self.direction); + self.interpolatedPos = Vec3.sum(self.initialPos, addition); + self.interpolatedRot = Quat.mix(self.initialRot, self.finalRot, ratio); + } else if (self.status === ZoomStatus.zoomingOut) { + self.interpolatedPos = Vec3.sum(self.finalPos, Vec3.multiply(self.distance * ratio, Vec3.multiply(-1, self.direction))); + self.interpolatedRot = Quat.mix(self.finalRot, self.initialRot, ratio); + } + self.elapsedTime += deltaTime; + Camera.setPosition(self.interpolatedPos); + Camera.setOrientation(self.interpolatedRot); + } else { + Camera.setPosition(self.finalPos); + Camera.setOrientation(self.finalRot); + if (self.status === ZoomStatus.zoomingIn) { + self.status = ZoomStatus.zoomedIn; + } else if (self.status === ZoomStatus.zoomingOut) { + self.status = ZoomStatus.consumed; + self.changeCameraMode(self.initialMode); + } + } + } + + // This function recomputes the camera distance to the selected item in order to fit it on the screen + this.resetZoomAspect = function() { + self.computeFinalForm(); + self.computeRouteIn(); + Camera.setPosition(self.finalPos); + } + + // This function updates the secondary zoom distance to the selected entity according to the mouse wheel event + this.updateSuperZoom = function(delta) { + var ZOOM_STEP = 0.1; + self.currentZoomAmount = self.currentZoomAmount + (delta < 0.0 ? -1 : 1) * ZOOM_STEP; + self.currentZoomAmount = Math.min(Math.max(self.currentZoomAmount, -self.maxZoomOutAmount), self.maxZoomInAmount); + self.updateSuperPan(self.zoomPanOffset.x, self.zoomPanOffset.y); + } + + // This function updates the secondary zoom panning according to the mouse move event + this.updateSuperPan = function(x, y) { + var zoomOffset = Vec3.multiply(self.currentZoomAmount, Vec3.subtract(self.lookAt, self.finalPos)); + var xRatio = 0.5 - x / Window.innerWidth; + var yRatio = 0.5 - y / Window.innerHeight; + var cameraOrientation = Camera.getOrientation(); + var cameraY = Quat.getUp(cameraOrientation); + var cameraX = Vec3.multiply(-1, Quat.getRight(cameraOrientation)); + var xOffset = Vec3.multiply(xRatio * self.focusDimensions.x, cameraX); + var yOffset = Vec3.multiply(yRatio * self.focusDimensions.y, cameraY); + zoomOffset = Vec3.sum(zoomOffset, xOffset); + zoomOffset = Vec3.sum(zoomOffset, yOffset); + Camera.setPosition(Vec3.sum(self.finalPos, zoomOffset)); + } + + // This function aborts the zoom loop when the user changes the camera mode + this.abort = function() { + self.changeCameraMode(self.initialMode); + } + + // This function changes the camera mode + this.changeCameraMode = function(mode) { + self.ownCameraChange = true; + self.ownCameraChangeElapseTime = 0.0; + Camera.mode = mode; + } + + this.computeFinalForm(); + } + + // Class that manages the mouse events and computes the 3d structure data of the selected entity or avatar + var ZoomOnAnything = function() { + var self = this; + this.zoomEntityData; // The selected entity's data + this.zoomCameraPos; + var ZOOM_MAX_VELOCITY = 15.0; // meters per second + var ZOOM_MAX_DURATION = 1.0; // seconds + this.zoomDelta = {x: 0.0, y: 0.0}; // mouse move delta while panning + this.isPanning = false; + this.screenPointRef = {x: 0.0, y: 0.0}; // Screen pixels reference used to compute the zoomDelta + this.connected = false; + + // This function estimates the projection axis and the dimensions along that axis of the selected entity + this.getEntityDimsFromNormal = function(dims, rot, normal) { + var zoomXNormal = Vec3.multiplyQbyV(rot, Vec3.UNIT_X); + var zoomYNormal = Vec3.multiplyQbyV(rot, Vec3.UNIT_Y); + var zoomZNormal = Vec3.multiplyQbyV(rot, Vec3.UNIT_Z); + var affinities = [ + {axis: "x", normal: zoomXNormal, affin: Math.abs(Vec3.dot(zoomXNormal, normal)), dims: {x: dims.z, y: dims.y}}, + {axis: "y", normal: zoomYNormal, affin: Math.abs(Vec3.dot(zoomYNormal, normal)), dims: {x: dims.x, y: dims.z}}, + {axis: "z", normal: zoomZNormal, affin: Math.abs(Vec3.dot(zoomZNormal, normal)), dims: {x: dims.x, y: dims.y}} + ]; + affinities.sort(function(a, b) { + return b.affin - a.affin; + }); + return affinities[0]; + } + + // This function estimates the appropriate focus point when the selected entity is an avatar + this.getAvatarFocusPoint = function(avatar) { + var rEyeIndex = avatar.getJointIndex("RightEye"); + var lEyeIndex = avatar.getJointIndex("LeftEye"); + var headIndex = avatar.getJointIndex("Head"); + var focusPoint = Vec3.ZERO; + var validPoint = false; + var count = 0; + if (rEyeIndex != -1) { + focusPoint = Vec3.sum(focusPoint, avatar.getJointPosition(rEyeIndex)); + validPoint = true; + count++; + } + if (lEyeIndex != -1) { + var leftEyePos = avatar.getJointPosition(lEyeIndex); + var NORMAL_EYE_DISTANCE = 0.1; + focusPoint = Vec3.sum(focusPoint, leftEyePos); + validPoint = true; + count++; + } + if (headIndex != -1) { + focusPoint = Vec3.sum(focusPoint, avatar.getJointPosition(headIndex)); + validPoint = true; + count++; + } + if (!validPoint) { + focusPoint = avatar.getJointPosition("Hips"); + count++; + } + return Vec3.multiply(1.0/count, focusPoint); + } + + // This function sets the data needed to zoom in on an avatar + this.getZoomDataFromAvatar = function(avatarID, skinToBoneDist, zoomVelocity, maxDuration) { + var headDiam = 2.0 * skinToBoneDist; // Head diameter is estimated based on the distance to the bone + headDiam = headDiam < 0.5 ? 0.5 : headDiam; + var avatar = AvatarList.getAvatar(avatarID); + var focusPoint = self.getAvatarFocusPoint(avatar); + var focusDims = {x: headDiam, y: headDiam}; + var focusNormal = Quat.getFront(avatar.orientation); + var zoomData = new ZoomData(FocusType.avatar, focusPoint, focusNormal, focusDims, zoomVelocity, maxDuration); + return zoomData; + } + + // This function sets the data needed to zoom in on an entity + this.getZoomDataFromEntity = function(intersection, objectProps, zoomVelocity, maxDuration) { + var position = objectProps.position; + var dimensions = objectProps.dimensions; + var rotation = objectProps.rotation; + var focusNormal = intersection.surfaceNormal; + var dimsResult = self.getEntityDimsFromNormal(dimensions, rotation, focusNormal); + var focusDims = dimsResult.dims; + var focusDepth = Vec3.dot(Vec3.subtract(intersection.intersection, position), dimsResult.normal); + var newPosition = Vec3.sum(position, Vec3.multiply(focusDepth, dimsResult.normal)); + var zoomData = new ZoomData(FocusType.entity, newPosition, focusNormal, focusDims, zoomVelocity, maxDuration); + return zoomData; + } + + // This function initiate the zoom loop on the selected entity + this.zoomOnEntity = function(intersection, objectProps) { + self.zoomEntityData = self.getZoomDataFromEntity(intersection, objectProps, ZOOM_MAX_VELOCITY, ZOOM_MAX_DURATION); + self.zoomEntityData.initZoomIn(); + } + + // This function initiates the zoom loop on the selected avatar + this.zoomOnAvatar = function(avatarID, skinToBoneDist) { + self.zoomEntityData = self.getZoomDataFromAvatar(avatarID, skinToBoneDist, ZOOM_MAX_VELOCITY, ZOOM_MAX_DURATION); + self.zoomEntityData.initZoomIn(); + } + + // This function connects to the Script.update signal + this.connect = function() { + self.connected = true; + Script.update.connect(self.updateZoom); + } + + // This function disconnects to the Script.update signal + this.disconnect = function() { + self.connected = false; + Script.update.disconnect(self.updateZoom); + } + + // This function get connected to the Script.update signal + this.updateZoom = function(deltaTime) { + if (self.zoomEntityData) { + if (self.zoomEntityData.needsUpdate()) { + self.zoomEntityData.updateZoom(deltaTime); + if (self.zoomEntityData.status === ZoomStatus.consumed) { + self.disconnect(); + self.zoomEntityData = undefined; + } + } else { + self.disconnect(); + } + } else if (self.connected) { + self.disconnect(); + } + } + + // This function listen to the mouse double click that evaluates the selected entity and the intersection data that initiates the zoom loop + this.mouseDoublePressEvent = function(event) { + if (event.isLeftButton) { + self.connect(); + if (!self.zoomEntityData) { + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = AvatarManager.findRayIntersection({origin: pickRay.origin, direction: pickRay.direction}, [], [MyAvatar.sessionUUID], false); + if (!intersection.intersects) { + intersection = Entities.findRayIntersection({origin: pickRay.origin, direction: pickRay.direction}, true); + var entityProps = Entities.getEntityProperties(intersection.entityID); + if (entityProps.type === "Shape") { + var FIND_SHAPES_DISTANCE = 10.0; + var shapes = Entities.findEntitiesByType("Shape", intersection.intersection, FIND_SHAPES_DISTANCE); + intersection = Entities.findRayIntersection({origin: pickRay.origin, direction: pickRay.direction}, true, [], shapes); + entityProps = Entities.getEntityProperties(intersection.entityID); + } + if (!entityProps.dimensions) { + return; + } + self.zoomOnEntity(intersection, entityProps); + } else { + var avatar = AvatarList.getAvatar(intersection.avatarID); + var skinToBoneDist = Vec3.distance(intersection.intersection, avatar.getJointPosition(intersection.jointIndex)); + self.zoomOnAvatar(intersection.avatarID, skinToBoneDist); + } + } else if (!self.zoomEntityData.needsUpdate()){ + self.zoomEntityData.initZoomOut(); + } + } + } + + // This function listen to the mouse press event to initiate panning while on secondary zoom + this.mousePressEvent = function(event) { + if (event.isRightButton) { + self.isPanning = true; + self.screenPointRef = {x: event.x, y: event.y}; + } + } + + // This function listen to the mouse release event to apply the pan delta while on secondary zoom + this.mouseReleaseEvent = function(event) { + if (event.isRightButton) { + if (self.zoomEntityData) { + self.zoomEntityData.applyZoomPan(); + self.isPanning = false; + self.screenPointRef = {x: 0, y: 0}; + } + } + } + + // This function listen to the mouse move event to modify the pan delta coordenates while on secondary zoom + this.mouseMoveEvent = function(event) { + if (event.isRightButton) { + if (self.isPanning && self.zoomEntityData) { + self.zoomEntityData.setZoomPanDelta(event.x - self.screenPointRef.x, event.y - self.screenPointRef.y); + } + } + } + + // This function listen to the mouse wheel event to modify the ammount of secondary zoom + this.mouseWheel = function(event) { + if (self.zoomEntityData) { + self.zoomEntityData.updateSuperZoom(event.delta); + } + } + + // This function aborts the zoom loop + this.abort = function() { + self.zoomEntityData.abort(); + self.zoomEntityData = undefined; + if (self.connected) { + self.disconnect(); + } + } + + // This function refits the selected entity on the screen when the screen dimensions change + this.geometryChanged = function() { + if (self.zoomEntityData !== undefined){ + self.zoomEntityData.resetZoomAspect(); + } + } + + // This function aborts the zoom loop if the camera mode is set by the user + this.modeUpdated = function(mode) { + if (self.zoomEntityData && !self.zoomEntityData.ownCameraChange) { + self.abort(); + } + } + + // This function initiate the connections + this.init = function() { + // Connect signals + Window.geometryChanged.connect(self.geometryChanged); + Camera.modeUpdated.connect(self.modeUpdated); + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseDoublePressEvent.connect(self.mouseDoublePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Controller.wheelEvent.connect(self.mouseWheel); + } + + // This function finish the connections + this.scriptEnding = function() { + // Disconnect on exit + Window.geometryChanged.disconnect(self.geometryChanged); + Camera.modeUpdated.disconnect(self.modeUpdated); + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseDoublePressEvent.disconnect(self.mouseDoublePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + Controller.wheelEvent.disconnect(self.mouseWheel); + if (self.zoomEntityData) { + self.abort(); + } + } + } + + var zoomOE = new ZoomOnAnything(); + zoomOE.init(); + Script.scriptEnding.connect(zoomOE.scriptEnding); + +})(); diff --git a/scripts/simplifiedUIBootstrapper.js b/scripts/simplifiedUIBootstrapper.js index 88bba109ed..985485c4f9 100644 --- a/scripts/simplifiedUIBootstrapper.js +++ b/scripts/simplifiedUIBootstrapper.js @@ -17,7 +17,8 @@ var currentlyRunningScripts = ScriptDiscoveryService.getRunning(); var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", - "simplifiedUI/ui/simplifiedUI.js" + "simplifiedUI/ui/simplifiedUI.js", + "simplifiedUI/clickToZoom/clickToZoom.js" ]; function loadSeparateDefaults() { for (var i = 0; i < DEFAULT_SCRIPTS_SEPARATE.length; i++) { diff --git a/scripts/system/away.js b/scripts/system/away.js index da24e0c3f8..ef79791e44 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -50,7 +50,7 @@ var OVERLAY_DATA_HMD = { }; var AWAY_INTRO = { - url: "https://hifi-content.s3.amazonaws.com/doug/animation/fbx/afk_texting.fbx", + url: "qrc:///avatar/animations/afk_texting.fbx", playbackRate: 30.0, loopFlag: true, startFrame: 1.0, diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index e6ba440c29..98c3e70fdf 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -129,6 +129,7 @@ Script.include("/~/system/libraries/controllers.js"); this.init = false; this.hand = hand; this.buttonValue = 0; + this.buttonTeleporting = false; // True if buttonValue is controlling teleport. this.standardAxisLY = 0.0; this.standardAxisRY = 0.0; this.disabled = false; // used by the 'Hifi-Teleport-Disabler' message handler @@ -687,8 +688,10 @@ Script.include("/~/system/libraries/controllers.js"); }; this.buttonPress = function (value) { + // Button press on gamepad. if (value === 0 || !_this.teleportLocked()) { _this.buttonValue = value; + _this.buttonTeleporting = true; } }; @@ -711,23 +714,23 @@ Script.include("/~/system/libraries/controllers.js"); this.getDominantHand = function () { return (MyAvatar.getDominantHand() === "left") ? LEFT_HAND : RIGHT_HAND; - } + }; this.getOffHand = function () { return (MyAvatar.getDominantHand() === "left") ? RIGHT_HAND : LEFT_HAND; - } + }; this.showReticle = function () { return (_this.getDominantY() > TELEPORT_DEADZONE) ? true : false; }; this.shouldTeleport = function () { - return (_this.getDominantY() > TELEPORT_DEADZONE && _this.getOffhandY() > TELEPORT_DEADZONE) ? true : false; + return ((_this.getDominantY() > TELEPORT_DEADZONE && _this.getOffhandY() > TELEPORT_DEADZONE) + || (_this.buttonTeleporting && _this.buttonValue === 0)) ? true : false; }; this.shouldCancel = function () { - //return (_this.getDominantY() < -TELEPORT_DEADZONE || _this.getOffhandY() < -TELEPORT_DEADZONE) ? true : false; - return (_this.getDominantY() <= TELEPORT_DEADZONE) ? true : false; + return (_this.getDominantY() <= TELEPORT_DEADZONE && !_this.buttonTeleporting) ? true : false; }; this.parameters = makeDispatcherModuleParameters( @@ -746,7 +749,8 @@ Script.include("/~/system/libraries/controllers.js"); } var otherModule = this.getOtherModule(); - if (!this.disabled && this.showReticle() && !otherModule.active && this.hand === this.getDominantHand()) { + if (!this.disabled && !otherModule.active && (this.showReticle() && this.hand === this.getDominantHand() + || this.buttonValue !== 0)) { this.active = true; this.enterTeleport(); return makeRunningValues(true, [], []); @@ -843,6 +847,7 @@ Script.include("/~/system/libraries/controllers.js"); _this.disableLasers(); _this.active = false; + _this.buttonTeleporting = false; return makeRunningValues(false, [], []); }; @@ -923,6 +928,7 @@ Script.include("/~/system/libraries/controllers.js"); var mappingName, teleportMapping; var isViveMapped = false; + var isGamePadMapped = false; function parseJSON(json) { try { @@ -1009,11 +1015,25 @@ Script.include("/~/system/libraries/controllers.js"); } } + function registerGamePadMapping(teleporter) { + if (Controller.Hardware.GamePad) { + var mappingName = 'Hifi-Teleporter-Dev-GamePad-' + Math.random(); + var teleportMapping = Controller.newMapping(mappingName); + teleportMapping.from(Controller.Hardware.GamePad.Y).peek().to(rightTeleporter.buttonPress); + Controller.enableMapping(mappingName); + isGamePadMapped = true; + } + } + function onHardwareChanged() { // Controller.Hardware.Vive is not immediately available at Interface start-up. if (!isViveMapped && Controller.Hardware.Vive) { registerViveTeleportMapping(); } + + if (!isGamePadMapped && Controller.Hardware.GamePad) { + registerGamePadMapping(); + } } Controller.hardwareChanged.connect(onHardwareChanged); @@ -1025,9 +1045,10 @@ Script.include("/~/system/libraries/controllers.js"); // Vive teleport button lock-out. registerViveTeleportMapping(); + // Gamepad "Y" button teleport. + registerGamePadMapping(); + // Teleport actions. - teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftTeleporter.buttonPress); - teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightTeleporter.buttonPress); teleportMapping.from(Controller.Standard.LY).peek().to(leftTeleporter.getStandardLY); teleportMapping.from(Controller.Standard.RY).peek().to(leftTeleporter.getStandardRY); teleportMapping.from(Controller.Standard.LY).peek().to(rightTeleporter.getStandardLY); diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 24f443f901..da12d2d503 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -147,6 +147,9 @@ "avatarPriority": { "tooltip": "Alter Avatars' update priorities." }, + "screenshare": { + "tooltip": "Enable screen-sharing within this zone" + }, "modelURL": { "tooltip": "A mesh model from an FBX or OBJ file." }, @@ -558,9 +561,6 @@ "gravity": { "tooltip": "The acceleration due to gravity that the entity should move with, in world space." }, - "acceleration": { - "tooltip": "A acceleration that the entity should move with, in world space." - }, "renderLayer": { "tooltip": "The layer on which this entity is rendered." }, diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index ef07aa4d6e..16b11698ac 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -393,7 +393,8 @@ const DEFAULT_ENTITY_PROPERTIES = { }, shapeType: "box", bloomMode: "inherit", - avatarPriority: "inherit" + avatarPriority: "inherit", + screenshare: "inherit", }, Model: { collisionShape: "none", @@ -561,14 +562,67 @@ var toolBar = (function () { if (!properties.grab) { properties.grab = {}; if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && - !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { + !(properties.type === "Zone" || properties.type === "Light" + || properties.type === "ParticleEffect" || properties.type === "Web")) { properties.grab.grabbable = true; } else { properties.grab.grabbable = false; } } + if (type === "Model") { + properties.visible = false; + } + entityID = Entities.addEntity(properties); + + var dimensionsCheckCallback = function(){ + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(false, this); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + } + // Make sure the entity is loaded before we try to figure out + // its dimensions. + var MAX_LOADED_CHECKS = 10; + var LOADED_CHECK_INTERVAL = 100; + var isLoadedCheckCount = 0; + var entityIsLoadedCheck = function() { + isLoadedCheckCount++; + if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) { + var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions + Entities.editEntity(entityID, { + visible: true, + dimensions: naturalDimensions + }) + dimensionsCheckCallback(); + return; + } + Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); + } + Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); + SelectionManager.addEntity(entityID, false, this); SelectionManager.saveProperties(); pushCommandForSelections([{ @@ -576,31 +630,6 @@ var toolBar = (function () { properties: properties }], [], true); - var POST_ADJUST_ENTITY_TYPES = ["Model"]; - if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { - // Adjust position of entity per bounding box after it has been created and auto-resized. - var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; - var DIMENSIONS_CHECK_INTERVAL = 200; - var MAX_DIMENSIONS_CHECKS = 10; - var dimensionsCheckCount = 0; - var dimensionsCheckFunction = function () { - dimensionsCheckCount++; - var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); - if (!Vec3.equal(properties.dimensions, initialDimensions)) { - position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, - properties.dimensions, properties.rotation); - position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), - properties.dimensions); - Entities.editEntity(entityID, { - position: position - }); - selectionManager._update(false, this); - } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { - Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); - } - }; - Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); - } } else { Window.notifyEditError("Can't create " + properties.type + ": " + properties.type + " would be out of bounds."); diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index 06e100f457..b68dcf80ba 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -177,7 +177,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var cameraPosition = Camera.position; PROFILE("getMultipleProperties", function () { - var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', + var multipleProperties = Entities.getMultipleEntityProperties(ids, ['position', 'name', 'type', 'locked', 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID', 'skybox.url', 'ambientLight.url']); for (var i = 0; i < multipleProperties.length; i++) { @@ -275,23 +275,6 @@ EntityListTool = function(shouldUseEditTabletApp) { Window.saveFileChanged.connect(onFileSaveChanged); Window.saveAsync("Select Where to Save", "", "*.json"); } - } else if (data.type === "pal") { - var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates. - selectionManager.selections.forEach(function (id) { - var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; - if (lastEditedBy) { - sessionIds[lastEditedBy] = true; - } - }); - var dedupped = Object.keys(sessionIds); - if (!selectionManager.selections.length) { - Window.alert('No objects selected.'); - } else if (!dedupped.length) { - Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.'); - } else { - // No need to subscribe if we're just sending. - Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local'); - } } else if (data.type === "delete") { deleteSelectedEntities(); } else if (data.type === "toggleLocked") { diff --git a/scripts/system/create/entityList/html/entityList.html b/scripts/system/create/entityList/html/entityList.html index 3e17a66df5..b7ff7cd4e4 100644 --- a/scripts/system/create/entityList/html/entityList.html +++ b/scripts/system/create/entityList/html/entityList.html @@ -29,7 +29,6 @@ - diff --git a/scripts/system/create/entityList/html/js/entityList.js b/scripts/system/create/entityList/html/js/entityList.js index b15c4e6703..b70e53ce15 100644 --- a/scripts/system/create/entityList/html/js/entityList.js +++ b/scripts/system/create/entityList/html/js/entityList.js @@ -209,7 +209,6 @@ let elEntityTable, elFilterInView, elFilterRadius, elExport, - elPal, elSelectedEntitiesCount, elVisibleEntitiesCount, elNoEntitiesMessage, @@ -254,7 +253,6 @@ function loaded() { elFilterInView = document.getElementById("filter-in-view"); elFilterRadius = document.getElementById("filter-radius"); elExport = document.getElementById("export"); - elPal = document.getElementById("pal"); elSelectedEntitiesCount = document.getElementById("selected-entities-count"); elVisibleEntitiesCount = document.getElementById("visible-entities-count"); elNoEntitiesMessage = document.getElementById("no-entities"); @@ -272,9 +270,6 @@ function loaded() { elExport.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); }; - elPal.onclick = function() { - EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); - }; elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); }; @@ -541,8 +536,9 @@ function loaded() { function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; let selection = [entityID]; + let controlKey = window.navigator.platform.startsWith("Mac") ? clickEvent.metaKey : clickEvent.ctrlKey; - if (clickEvent.ctrlKey) { + if (controlKey) { let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = []; @@ -573,7 +569,7 @@ function loaded() { selection.reverse(); } } - } else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { + } else if (!controlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { // if reselecting the same entity then start renaming it if (selectedEntities[0] === entityID) { if (renameLastBlur && renameLastEntityID === entityID && (Date.now() - renameLastBlur) < RENAME_COOLDOWN) { diff --git a/scripts/system/create/entityProperties/html/entityProperties.html b/scripts/system/create/entityProperties/html/entityProperties.html index 876e75ec35..6eadf4d3c0 100644 --- a/scripts/system/create/entityProperties/html/entityProperties.html +++ b/scripts/system/create/entityProperties/html/entityProperties.html @@ -1,52 +1,63 @@ - - - Properties - - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-
-
-
+ + Properties + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+
+ +
+
- -
- - + diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index e581fbd194..8c041d2563 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -1,8 +1,8 @@ // entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 -// Modified by David Back on 19 Oct 2018 // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -10,6 +10,8 @@ /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, JSONEditor, openEventBridge, setTimeout, window, _, $ */ +var currentTab = "base"; + const DEGREES_TO_RADIANS = Math.PI / 180.0; const NO_SELECTION = ","; @@ -24,7 +26,7 @@ const PROPERTY_SELECTION_VISIBILITY = Object.freeze({ SINGLE_SELECTION: 1, MULTIPLE_SELECTIONS: 2, MULTI_DIFF_SELECTIONS: 4, - ANY_SELECTIONS: 7, /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */ + ANY_SELECTIONS: 7 /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */ }); // Multiple-selection behavior @@ -34,12 +36,13 @@ const PROPERTY_MULTI_DISPLAY_MODE = Object.freeze({ * Comma separated values * Limited for properties with type "string" or "textarea" and readOnly enabled */ - COMMA_SEPARATED_VALUES: 1, + COMMA_SEPARATED_VALUES: 1 }); const GROUPS = [ { id: "base", + label: "ENTITY", properties: [ { label: NO_SELECTION, @@ -112,12 +115,17 @@ const GROUPS = [ lines: "Wireframe", }, propertyID: "primitiveMode", - }, + }/*, + { + label: "Render With Zones", + type: "multipleZonesSelection", + propertyID: "renderWithZones", + }*/ ] }, { id: "shape", - addToGroup: "base", + label: "SHAPE", properties: [ { label: "Shape", @@ -133,11 +141,21 @@ const GROUPS = [ type: "color", propertyID: "color", }, + { + label: "Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "shapeAlpha", + propertyName: "alpha", + }, ] }, { id: "text", - addToGroup: "base", + label: "TEXT", properties: [ { label: "Text", @@ -181,6 +199,36 @@ const GROUPS = [ unit: "m", propertyID: "lineHeight", }, + { + label: "Font", + type: "string", + propertyID: "font", + }, + { + label: "Effect", + type: "dropdown", + options: { + none: "None", + outline: "Outline", + "outline fill": "Outline with fill", + shadow: "Shadow" + }, + propertyID: "textEffect", + }, + { + label: "Effect Color", + type: "color", + propertyID: "textEffectColor", + }, + { + label: "Effect Thickness", + type: "number-draggable", + min: 0.0, + max: 0.5, + step: 0.01, + decimals: 2, + propertyID: "textEffectThickness", + }, { label: "Billboard Mode", type: "dropdown", @@ -220,12 +268,12 @@ const GROUPS = [ label: "Unlit", type: "bool", propertyID: "unlit", - }, + } ] }, { id: "zone", - addToGroup: "base", + label: "ZONE", properties: [ { label: "Shape Type", @@ -255,7 +303,13 @@ const GROUPS = [ label: "Filter", type: "string", propertyID: "filterURL", - }, + } + ] + }, + { + id: "zone_key_light", + label: "ZONE KEY LIGHT", + properties: [ { label: "Key Light", type: "dropdown", @@ -272,7 +326,7 @@ const GROUPS = [ { label: "Light Intensity", type: "number-draggable", - min: 0, + min: -40, max: 40, step: 0.01, decimals: 2, @@ -324,7 +378,13 @@ const GROUPS = [ decimals: 2, propertyID: "keyLight.shadowMaxDistance", showPropertyRule: { "keyLightMode": "enabled" }, - }, + } + ] + }, + { + id: "zone_skybox", + label: "ZONE SKYBOX", + properties: [ { label: "Skybox", type: "dropdown", @@ -342,7 +402,13 @@ const GROUPS = [ type: "string", propertyID: "skybox.url", showPropertyRule: { "skyboxMode": "enabled" }, - }, + } + ] + }, + { + id: "zone_ambient_light", + label: "ZONE AMBIENT LIGHT", + properties: [ { label: "Ambient Light", type: "dropdown", @@ -352,7 +418,7 @@ const GROUPS = [ { label: "Ambient Intensity", type: "number-draggable", - min: 0, + min: -200, max: 200, step: 0.1, decimals: 2, @@ -371,7 +437,13 @@ const GROUPS = [ className: "black", onClick: copySkyboxURLToAmbientURL } ], propertyID: "copyURLToAmbient", showPropertyRule: { "ambientLightMode": "enabled" }, - }, + } + ] + }, + { + id: "zone_haze", + label: "ZONE HAZE", + properties: [ { label: "Haze", type: "dropdown", @@ -454,7 +526,13 @@ const GROUPS = [ decimals: 0, propertyID: "haze.hazeGlareAngle", showPropertyRule: { "hazeMode": "enabled" }, - }, + } + ] + }, + { + id: "zone_bloom", + label: "ZONE BLOOM", + properties: [ { label: "Bloom", type: "dropdown", @@ -490,19 +568,30 @@ const GROUPS = [ decimals: 3, propertyID: "bloom.bloomSize", showPropertyRule: { "bloomMode": "enabled" }, - }, + } + ] + }, + { + id: "zone_avatar_priority", + label: "ZONE AVATAR PRIORITY", + properties: [ { label: "Avatar Priority", type: "dropdown", options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, propertyID: "avatarPriority", }, - + { + label: "Screen-share", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "screenshare", + } ] }, { id: "model", - addToGroup: "base", + label: "MODEL", properties: [ { label: "Model", @@ -542,7 +631,7 @@ const GROUPS = [ propertyID: "animation.loop", }, { - label: "Allow Transition", + label: "Allow Translation", type: "bool", propertyID: "animation.allowTranslation", }, @@ -587,12 +676,12 @@ const GROUPS = [ label: "Group Culled", type: "bool", propertyID: "groupCulled", - }, + } ] }, { id: "image", - addToGroup: "base", + label: "IMAGE", properties: [ { label: "Image", @@ -606,6 +695,16 @@ const GROUPS = [ propertyID: "imageColor", propertyName: "color", // actual entity property name }, + { + label: "Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "imageAlpha", + propertyName: "alpha", + }, { label: "Emissive", type: "bool", @@ -630,12 +729,12 @@ const GROUPS = [ label: "Keep Aspect Ratio", type: "bool", propertyID: "keepAspectRatio", - }, + } ] }, { id: "web", - addToGroup: "base", + label: "WEB", properties: [ { label: "Source", @@ -670,17 +769,38 @@ const GROUPS = [ decimals: 0, propertyID: "maxFPS", }, + { + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "webBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Input Mode", + type: "dropdown", + options: { + touch: "Touch events", + mouse: "Mouse events" + }, + propertyID: "inputMode", + }, + { + label: "Focus Highlight", + type: "bool", + propertyID: "showKeyboardFocusHighlight", + }, { label: "Script URL", type: "string", propertyID: "scriptURL", placeholder: "URL", - }, + } ] }, { id: "light", - addToGroup: "base", + label: "LIGHT", properties: [ { label: "Light Color", @@ -691,7 +811,7 @@ const GROUPS = [ { label: "Intensity", type: "number-draggable", - min: 0, + min: -1000, max: 10000, step: 0.1, decimals: 2, @@ -726,12 +846,12 @@ const GROUPS = [ step: 0.01, decimals: 2, propertyID: "cutoff", - }, + } ] }, { id: "material", - addToGroup: "base", + label: "MATERIAL", properties: [ { label: "Material URL", @@ -800,12 +920,12 @@ const GROUPS = [ label: "Material Repeat", type: "bool", propertyID: "materialRepeat", - }, + } ] }, { id: "grid", - addToGroup: "base", + label: "GRID", properties: [ { label: "Color", @@ -833,12 +953,12 @@ const GROUPS = [ step: 0.01, decimals: 2, propertyID: "minorGridEvery", - }, + } ] }, { id: "particles", - addToGroup: "base", + label: "PARTICLES", properties: [ { label: "Emit", @@ -864,13 +984,12 @@ const GROUPS = [ type: "texture", propertyID: "particleTextures", propertyName: "textures", // actual entity property name - }, + } ] }, { id: "particles_emit", - label: "EMIT", - isMinor: true, + label: "PARTICLES EMIT", properties: [ { label: "Emit Rate", @@ -937,13 +1056,12 @@ const GROUPS = [ label: "Trails", type: "bool", propertyID: "emitterShouldTrail", - }, + } ] }, { id: "particles_size", - label: "SIZE", - isMinor: true, + label: "PARTICLES SIZE", properties: [ { type: "triple", @@ -972,7 +1090,7 @@ const GROUPS = [ decimals: 2, propertyID: "radiusFinish", fallbackProperty: "particleRadius", - }, + } ] }, { @@ -981,13 +1099,12 @@ const GROUPS = [ step: 0.01, decimals: 2, propertyID: "radiusSpread", - }, + } ] }, { id: "particles_color", - label: "COLOR", - isMinor: true, + label: "PARTICLES COLOR", properties: [ { type: "triple", @@ -1011,7 +1128,7 @@ const GROUPS = [ type: "color", propertyID: "colorFinish", fallbackProperty: "color", - }, + } ] }, { @@ -1019,13 +1136,6 @@ const GROUPS = [ type: "color", propertyID: "colorSpread", }, - ] - }, - { - id: "particles_alpha", - label: "ALPHA", - isMinor: true, - properties: [ { type: "triple", label: "Alpha", @@ -1053,7 +1163,7 @@ const GROUPS = [ decimals: 3, propertyID: "alphaFinish", fallbackProperty: "alpha", - }, + } ] }, { @@ -1062,13 +1172,12 @@ const GROUPS = [ step: 0.001, decimals: 3, propertyID: "alphaSpread", - }, + } ] }, { - id: "particles_acceleration", - label: "ACCELERATION", - isMinor: true, + id: "particles_behavior", + label: "PARTICLES BEHAVIOR", properties: [ { label: "Emit Acceleration", @@ -1088,13 +1197,6 @@ const GROUPS = [ subLabels: [ "x", "y", "z" ], propertyID: "accelerationSpread", }, - ] - }, - { - id: "particles_spin", - label: "SPIN", - isMinor: true, - properties: [ { type: "triple", label: "Spin", @@ -1128,7 +1230,7 @@ const GROUPS = [ unit: "deg", propertyID: "spinFinish", fallbackProperty: "particleSpin", - }, + } ] }, { @@ -1144,13 +1246,12 @@ const GROUPS = [ label: "Rotate with Entity", type: "bool", propertyID: "rotateWithEntity", - }, + } ] }, { id: "particles_constraints", - label: "CONSTRAINTS", - isMinor: true, + label: "PARTICLES CONSTRAINTS", properties: [ { type: "triple", @@ -1174,7 +1275,7 @@ const GROUPS = [ multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarFinish", - }, + } ], }, { @@ -1199,7 +1300,7 @@ const GROUPS = [ multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthFinish", - }, + } ] } ] @@ -1299,7 +1400,7 @@ const GROUPS = [ buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], propertyID: "alignToGrid", - }, + } ] }, { @@ -1369,6 +1470,18 @@ const GROUPS = [ type: "bool", propertyID: "ignorePickIntersection", }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyID: "lifetime", + } + ] + }, + { + id: "scripts", + label: "SCRIPTS", + properties: [ { label: "Script", type: "string", @@ -1391,12 +1504,6 @@ const GROUPS = [ propertyID: "serverScriptStatus", selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, }, - { - label: "Lifetime", - type: "number", - unit: "s", - propertyID: "lifetime", - }, { label: "User Data", type: "textarea", @@ -1404,7 +1511,7 @@ const GROUPS = [ { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], propertyID: "userData", - }, + } ] }, { @@ -1469,7 +1576,7 @@ const GROUPS = [ label: "Dynamic", type: "bool", propertyID: "dynamic", - }, + } ] }, { @@ -1554,27 +1661,28 @@ const GROUPS = [ decimals: 4, unit: "m/s2", propertyID: "acceleration", - }, + } ] }, ]; const GROUPS_PER_TYPE = { - None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], - Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], - Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], - Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ], - Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], - Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], - Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], - Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ], - Material: [ 'base', 'material', 'spatial', 'behavior' ], - ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', - 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], - PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], - PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], - Grid: [ 'base', 'grid', 'spatial', 'behavior', 'physics' ], - Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + None: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'zone_key_light', 'zone_skybox', 'zone_ambient_light', 'zone_haze', + 'zone_bloom', 'zone_avatar_priority', 'spatial', 'behavior', 'scripts', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Material: [ 'base', 'material', 'spatial', 'behavior', 'scripts', 'physics' ], + ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', + 'particles_behavior', 'particles_constraints', 'spatial', 'behavior', 'scripts', 'physics' ], + PolyLine: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + PolyVox: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], + Grid: [ 'base', 'grid', 'spatial', 'behavior', 'scripts', 'physics' ], + Multiple: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], }; const EDITOR_TIMEOUT_DURATION = 1500; @@ -1641,7 +1749,7 @@ let selectedEntityIDs = new Set(); let currentSelections = []; let createAppTooltip = new CreateAppTooltip(); let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL; - +let zonesList = []; function createElementFromHTML(htmlString) { let elTemplate = document.createElement('template'); @@ -1658,7 +1766,7 @@ function isFlagSet(value, flag) { */ function getPropertyInputElement(propertyID) { - let property = properties[propertyID]; + let property = properties[propertyID]; switch (property.data.type) { case 'string': case 'number': @@ -1667,6 +1775,8 @@ function getPropertyInputElement(propertyID) { case 'textarea': case 'texture': return property.elInput; + case 'multipleZonesSelection': + return property.elInput; case 'number-draggable': return property.elNumber.elInput; case 'rect': @@ -1707,12 +1817,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); enableChildren(document, ".colpick"); - - let elLocked = getPropertyInputElement("locked"); - if (elLocked.checked === false) { - removeStaticUserData(); - removeStaticMaterialData(); - } + enableAllMultipleZoneSelector(); } function disableProperties() { @@ -1721,16 +1826,7 @@ function disableProperties() { for (let pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - - let elLocked = getPropertyInputElement("locked"); - if (elLocked.checked === true) { - if ($('#property-userData-editor').css('display') === "block") { - showStaticUserData(); - } - if ($('#property-materialData-editor').css('display') === "block") { - showStaticMaterialData(); - } - } + disableAllMultipleZoneSelector(); } function showPropertyElement(propertyID, show) { @@ -1743,7 +1839,7 @@ function setPropertyVisibility(property, visible) { function resetProperties() { for (let propertyID in properties) { - let property = properties[propertyID]; + let property = properties[propertyID]; let propertyData = property.data; switch (propertyData.type) { @@ -1805,6 +1901,12 @@ function resetProperties() { setTextareaScrolling(property.elInput); break; } + case 'multipleZonesSelection': { + property.elInput.classList.remove('multi-diff'); + property.elInput.value = "[]"; + setZonesSelectionData(property.elInput, false); + break; + } case 'icon': { property.elSpan.style.display = "none"; break; @@ -1820,7 +1922,7 @@ function resetProperties() { break; } } - + let showPropertyRules = properties[propertyID].showPropertyRules; if (showPropertyRules !== undefined) { for (let propertyToHide in showPropertyRules) { @@ -1842,9 +1944,15 @@ function resetServerScriptStatus() { function showGroupsForType(type) { if (type === "Box" || type === "Sphere") { showGroupsForTypes(["Shape"]); + showOnTheSamePage(["Shape"]); return; } + if (type === "None") { + showGroupsForTypes(["None"]); + return; + } showGroupsForTypes([type]); + showOnTheSamePage([type]); } function getGroupsForTypes(types) { @@ -1858,9 +1966,15 @@ function getGroupsForTypes(types) { function showGroupsForTypes(types) { Object.entries(elGroups).forEach(([groupKey, elGroup]) => { if (types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { return hasGroup; })) { - elGroup.style.display = "block"; + elGroup.style.display = "none"; + if (types !== "None") { + document.getElementById("tab-" + groupKey).style.display = "block"; + } else { + document.getElementById("tab-" + groupKey).style.display = "none"; + } } else { elGroup.style.display = "none"; + document.getElementById("tab-" + groupKey).style.display = "none"; } }); } @@ -2257,7 +2371,7 @@ function updateCheckedSubProperty(propertyName, propertyMultiValue, subPropertyE * PROPERTY ELEMENT CREATION FUNCTIONS */ -function createStringProperty(property, elProperty) { +function createStringProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; @@ -2270,12 +2384,12 @@ function createStringProperty(property, elProperty) { ${propertyData.readOnly ? 'readonly' : ''}/> `); - + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); if (propertyData.onChange !== undefined) { elInput.addEventListener('change', propertyData.onChange); } - + let elMultiDiff = document.createElement('span'); elMultiDiff.className = "multi-diff"; @@ -2286,7 +2400,7 @@ function createStringProperty(property, elProperty) { if (propertyData.buttons !== undefined) { addButtons(elProperty, elementID, propertyData.buttons, false); } - + return elInput; } @@ -2294,9 +2408,9 @@ function createBoolProperty(property, elProperty) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - + elProperty.className = "checkbox"; - + if (propertyData.glyph !== undefined) { let elSpan = document.createElement('span'); elSpan.innerHTML = propertyData.glyph; @@ -2525,7 +2639,7 @@ function createVec3Property(property, elProperty) { let propertyData = property.data; elProperty.className = propertyData.vec3Type + " fstuple"; - + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); @@ -2540,7 +2654,7 @@ function createVec3Property(property, elProperty) { elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); elNumberZ.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'z')); - + let elResult = []; elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; @@ -2548,11 +2662,11 @@ function createVec3Property(property, elProperty) { return elResult; } -function createVec2Property(property, elProperty) { +function createVec2Property(property, elProperty) { let propertyData = property.data; elProperty.className = propertyData.vec2Type + " fstuple"; - + let elTuple = document.createElement('div'); elTuple.className = "tuple"; @@ -2589,19 +2703,19 @@ function createColorProperty(property, elProperty) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - + elProperty.className += " rgb fstuple"; - + let elColorPicker = document.createElement('div'); elColorPicker.className = "color-picker"; elColorPicker.setAttribute("id", elementID); - + let elTuple = document.createElement('div'); elTuple.className = "tuple"; - + elProperty.appendChild(elColorPicker); elProperty.appendChild(elTuple); - + if (propertyData.min === undefined) { propertyData.min = COLOR_MIN; } @@ -2611,19 +2725,19 @@ function createColorProperty(property, elProperty) { if (propertyData.step === undefined) { propertyData.step = COLOR_STEP; } - + let elNumberR = createTupleNumberInput(property, "red"); let elNumberG = createTupleNumberInput(property, "green"); let elNumberB = createTupleNumberInput(property, "blue"); elTuple.appendChild(elNumberR.elDiv); elTuple.appendChild(elNumberG.elDiv); elTuple.appendChild(elNumberB.elDiv); - + let valueChangeFunction = createEmitColorPropertyUpdateFunction(property); elNumberR.setValueChangeFunction(valueChangeFunction); elNumberG.setValueChangeFunction(valueChangeFunction); elNumberB.setValueChangeFunction(valueChangeFunction); - + let colorPickerID = "#" + elementID; colorPickers[colorPickerID] = $(colorPickerID).colpick({ colorScheme: 'dark', @@ -2652,7 +2766,7 @@ function createColorProperty(property, elProperty) { } } }); - + let elResult = []; elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR; @@ -2677,26 +2791,26 @@ function createDropdownProperty(property, propertyID, elProperty) { option.text = propertyData.options[optionKey]; elInput.add(option); } - + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); elProperty.appendChild(elInput); - + return elInput; } -function createTextareaProperty(property, elProperty) { +function createTextareaProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; - + elProperty.className = "textarea"; - + let elInput = document.createElement('textarea'); elInput.setAttribute("id", elementID); if (propertyData.readOnly) { elInput.readOnly = true; - } - + } + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); let elMultiDiff = document.createElement('span'); @@ -2704,42 +2818,42 @@ function createTextareaProperty(property, elProperty) { elProperty.appendChild(elInput); elProperty.appendChild(elMultiDiff); - + if (propertyData.buttons !== undefined) { addButtons(elProperty, elementID, propertyData.buttons, true); } - + return elInput; } -function createIconProperty(property, elProperty) { +function createIconProperty(property, elProperty) { let elementID = property.elementID; elProperty.className = "value"; - + let elSpan = document.createElement('span'); elSpan.setAttribute("id", elementID + "-icon"); elSpan.className = 'icon'; elProperty.appendChild(elSpan); - + return elSpan; } -function createTextureProperty(property, elProperty) { +function createTextureProperty(property, elProperty) { let elementID = property.elementID; - + elProperty.className = "texture"; - + let elDiv = document.createElement("div"); let elImage = document.createElement("img"); elDiv.className = "texture-image no-texture"; elDiv.appendChild(elImage); - + let elInput = document.createElement('input'); elInput.setAttribute("id", elementID); elInput.setAttribute("type", "text"); - + let imageLoad = function(url) { elDiv.style.display = null; if (url.slice(0, 5).toLowerCase() === "atp:/") { @@ -2776,7 +2890,7 @@ function createTextureProperty(property, elProperty) { elMultiDiff.className = "multi-diff"; elProperty.appendChild(elMultiDiff); elProperty.appendChild(elDiv); - + let elResult = []; elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; @@ -2792,23 +2906,23 @@ function createButtonsProperty(property, elProperty) { if (propertyData.buttons !== undefined) { addButtons(elProperty, elementID, propertyData.buttons, false); } - + return elProperty; } function createDynamicMultiselectProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; - + elProperty.className = "dynamic-multiselect"; - + let elDivOptions = document.createElement('div'); elDivOptions.setAttribute("id", elementID + "-options"); elDivOptions.style = "overflow-y:scroll;max-height:160px;"; - + let elDivButtons = document.createElement('div'); elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons"); - + let elLabel = document.createElement('label'); elLabel.innerText = "No Options"; elDivOptions.appendChild(elLabel); @@ -2816,10 +2930,10 @@ function createDynamicMultiselectProperty(property, elProperty) { let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget }, { id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ]; addButtons(elDivButtons, elementID, buttons, false); - + elProperty.appendChild(elDivOptions); elProperty.appendChild(elDivButtons); - + return elDivOptions; } @@ -2837,13 +2951,13 @@ function createTupleNumberInput(property, subLabel) { let propertyElementID = property.elementID; let propertyData = property.data; let elementID = propertyElementID + "-" + subLabel.toLowerCase(); - + let elLabel = document.createElement('label'); elLabel.className = "sublabel " + subLabel; elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1); elLabel.setAttribute("for", elementID); elLabel.style.visibility = "visible"; - + let dragStartFunction = createDragStartFunction(property); let dragEndFunction = createDragEndFunction(property); let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, @@ -2851,14 +2965,14 @@ function createTupleNumberInput(property, subLabel) { elDraggableNumber.elInput.setAttribute("id", elementID); elDraggableNumber.elDiv.className += " fstuple"; elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow); - + return elDraggableNumber; } function addButtons(elProperty, propertyID, buttons, newRow) { let elDiv = document.createElement('div'); elDiv.className = "row"; - + buttons.forEach(function(button) { let elButton = document.createElement('input'); elButton.className = button.className; @@ -2880,7 +2994,7 @@ function addButtons(elProperty, propertyID, buttons, newRow) { } function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) { - let property = { + let property = { data: propertyData, elementID: propertyElementID, name: propertyName, @@ -2942,6 +3056,10 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI property.elInput = createTextareaProperty(property, elProperty); break; } + case 'multipleZonesSelection': { + property.elInput = createZonesSelection(property, elProperty); + break; + } case 'icon': { property.elSpan = createIconProperty(property, elProperty); break; @@ -3171,19 +3289,6 @@ function hideUserDataSaved() { $('#property-userData-saved').hide(); } -function showStaticUserData() { - if (editor !== null) { - let $propertyUserDataStatic = $('#property-userData-static'); - $propertyUserDataStatic.show(); - $propertyUserDataStatic.css('height', $('#property-userData-editor').height()); - $propertyUserDataStatic.text(editor.getText()); - } -} - -function removeStaticUserData() { - $('#property-userData-static').hide(); -} - function setEditorJSON(json) { editor.set(json); if (editor.hasOwnProperty('expandAll')) { @@ -3336,19 +3441,6 @@ function hideMaterialDataSaved() { $('#property-materialData-saved').hide(); } -function showStaticMaterialData() { - if (materialEditor !== null) { - let $propertyMaterialDataStatic = $('#property-materialData-static'); - $propertyMaterialDataStatic.show(); - $propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height()); - $propertyMaterialDataStatic.text(materialEditor.getText()); - } -} - -function removeStaticMaterialData() { - $('#property-materialData-static').hide(); -} - function setMaterialEditorJSON(json) { materialEditor.set(json); if (materialEditor.hasOwnProperty('expandAll')) { @@ -3460,6 +3552,175 @@ function setTextareaScrolling(element) { element.setAttribute("scrolling", isScrolling ? "true" : "false"); } +/** + * ZONE SELECTOR FUNCTIONS + */ + +function enableAllMultipleZoneSelector() { + let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection"); + let i, propId; + for (i = 0; i < allMultiZoneSelectors.length; i++) { + propId = allMultiZoneSelectors[i].id; + displaySelectedZones(propId, true); + } +} + +function disableAllMultipleZoneSelector() { + let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection"); + let i, propId; + for (i = 0; i < allMultiZoneSelectors.length; i++) { + propId = allMultiZoneSelectors[i].id; + displaySelectedZones(propId, false); + } +} + +function requestZoneList() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "zoneListRequest" + })); +} + +function addZoneToZonesSelection(propertyId) { + let hiddenField = document.getElementById(propertyId); + if (JSON.stringify(hiddenField.value) === '"undefined"') { + hiddenField.value = "[]"; + } + let selectedZones = JSON.parse(hiddenField.value); + let zoneToAdd = document.getElementById("zones-select-" + propertyId).value; + if (!selectedZones.includes(zoneToAdd)) { + selectedZones.push(zoneToAdd); + } + hiddenField.value = JSON.stringify(selectedZones); + displaySelectedZones(propertyId, true); + let propertyName = propertyId.replace("property-", ""); + updateProperty(propertyName, selectedZones, false); +} + +function removeZoneFromZonesSelection(propertyId, zoneId) { + let hiddenField = document.getElementById(propertyId); + if (JSON.stringify(hiddenField.value) === '"undefined"') { + hiddenField.value = "[]"; + } + let selectedZones = JSON.parse(hiddenField.value); + let index = selectedZones.indexOf(zoneId); + if (index > -1) { + selectedZones.splice(index, 1); + } + hiddenField.value = JSON.stringify(selectedZones); + displaySelectedZones(propertyId, true); + let propertyName = propertyId.replace("property-", ""); + updateProperty(propertyName, selectedZones, false); +} + +function displaySelectedZones(propertyId, isEditable) { + let i,j, name, listedZoneInner, hiddenData, isMultiple; + hiddenData = document.getElementById(propertyId).value; + if (JSON.stringify(hiddenData) === '"undefined"') { + isMultiple = true; + hiddenData = "[]"; + } else { + isMultiple = false; + } + let selectedZones = JSON.parse(hiddenData); + listedZoneInner = ""; + if (selectedZones.length === 0) { + if (!isMultiple) { + listedZoneInner += ""; + } else { + listedZoneInner += ""; + } + } else { + for (i = 0; i < selectedZones.length; i++) { + name = "{ERROR: NOT FOUND}"; + for (j = 0; j < zonesList.length; j++) { + if (selectedZones[i] === zonesList[j].id) { + if (zonesList[j].name !== "") { + name = zonesList[j].name; + } else { + name = zonesList[j].id; + } + break; + } + } + if (isEditable) { + listedZoneInner += ""; + } else { + listedZoneInner += ""; + } + } + } + listedZoneInner += "
  
[ WARNING: Any changes will apply to all. ] 
" + name + ""; + listedZoneInner += "
" + name + " 
"; + document.getElementById("selected-zones-" + propertyId).innerHTML = listedZoneInner; + if (isEditable) { + document.getElementById("multiZoneSelTools-" + propertyId).style.display = "block"; + } else { + document.getElementById("multiZoneSelTools-" + propertyId).style.display = "none"; + } +} + +function createZonesSelection(property, elProperty) { + let elementID = property.elementID; + requestZoneList(); + elProperty.className = "multipleZonesSelection"; + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "hidden"); + elInput.className = "hiddenMultiZonesSelection"; + + let elZonesSelector = document.createElement('div'); + elZonesSelector.setAttribute("id", "zones-selector-" + elementID); + + let elMultiDiff = document.createElement('span'); + elMultiDiff.className = "multi-diff"; + + elProperty.appendChild(elInput); + elProperty.appendChild(elZonesSelector); + elProperty.appendChild(elMultiDiff); + + return elInput; +} + +function setZonesSelectionData(element, isEditable) { + let zoneSelectorContainer = document.getElementById("zones-selector-" + element.id); + let zoneSelector = "
 "; + zoneSelector += "
"; + zoneSelector += "
"; + zoneSelectorContainer.innerHTML = zoneSelector; + displaySelectedZones(element.id, isEditable); +} + +function updateAllZoneSelect() { + let allZoneSelects = document.querySelectorAll(".zoneSelect"); + let i, j, name, propId; + for (i = 0; i < allZoneSelects.length; i++) { + allZoneSelects[i].options.length = 0; + for (j = 0; j < zonesList.length; j++) { + if (zonesList[j].name === "") { + name = zonesList[j].id; + } else { + name = zonesList[j].name; + } + allZoneSelects[i].options[j] = new Option(name, zonesList[j].id, false , false); + } + propId = allZoneSelects[i].id.replace("zones-select-", ""); + if (document.getElementById("multiZoneSelTools-" + propId).style.display === "block") { + displaySelectedZones(propId, true); + } else { + displaySelectedZones(propId, false); + } + } +} /** * MATERIAL TARGET FUNCTIONS @@ -3471,15 +3732,15 @@ function requestMaterialTarget() { entityID: getFirstSelectedID(), })); } - + function setMaterialTargetData(materialTargetData) { let elDivOptions = getPropertyInputElement("parentMaterialName"); resetDynamicMultiselectProperty(elDivOptions); - + if (materialTargetData === undefined) { return; } - + elDivOptions.firstChild.style.display = "none"; // hide "No Options" text elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons @@ -3487,7 +3748,7 @@ function setMaterialTargetData(materialTargetData) { for (let i = 0; i < numMeshes; ++i) { addMaterialTarget(elDivOptions, i, false); } - + let materialNames = materialTargetData.materialNames; let materialNamesAdded = []; for (let i = 0; i < materialNames.length; ++i) { @@ -3497,7 +3758,7 @@ function setMaterialTargetData(materialTargetData) { materialNamesAdded.push(materialName); } } - + materialTargetPropertyUpdate(elDivOptions.propertyValue); } @@ -3505,12 +3766,12 @@ function addMaterialTarget(elDivOptions, targetID, isMaterialName) { let elementID = elDivOptions.getAttribute("id"); elementID += isMaterialName ? "-material-" : "-mesh-"; elementID += targetID; - + let elDiv = document.createElement('div'); elDiv.className = "materialTargetDiv"; elDiv.onclick = onToggleMaterialTarget; elDivOptions.appendChild(elDiv); - + let elInput = document.createElement('input'); elInput.className = "materialTargetInput"; elInput.setAttribute("type", "checkbox"); @@ -3518,12 +3779,12 @@ function addMaterialTarget(elDivOptions, targetID, isMaterialName) { elInput.setAttribute("targetID", targetID); elInput.setAttribute("isMaterialName", isMaterialName); elDiv.appendChild(elInput); - + let elLabel = document.createElement('label'); elLabel.setAttribute("for", elementID); elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID; elDiv.appendChild(elLabel); - + return elDiv; } @@ -3556,7 +3817,7 @@ function clearAllMaterialTarget() { function sendMaterialTargetProperty() { let elDivOptions = getPropertyInputElement("parentMaterialName"); let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); - + let materialTargetList = []; for (let i = 0; i < elInputs.length; ++i) { let elInput = elInputs[i]; @@ -3569,26 +3830,26 @@ function sendMaterialTargetProperty() { } } } - + let propertyValue = materialTargetList.join(","); if (propertyValue.length > 1) { propertyValue = "[" + propertyValue + "]"; } - + updateProperty("parentMaterialName", propertyValue, false); } function materialTargetPropertyUpdate(propertyValue) { let elDivOptions = getPropertyInputElement("parentMaterialName"); let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); - + if (propertyValue.startsWith('[')) { propertyValue = propertyValue.substring(1, propertyValue.length); } if (propertyValue.endsWith(']')) { propertyValue = propertyValue.substring(0, propertyValue.length - 1); } - + let materialTargets = propertyValue.split(","); for (let i = 0; i < elInputs.length; ++i) { let elInput = elInputs[i]; @@ -3599,7 +3860,7 @@ function materialTargetPropertyUpdate(propertyValue) { } elInput.checked = materialTargets.indexOf(materialTargetName) >= 0; } - + elDivOptions.propertyValue = propertyValue; } @@ -3633,7 +3894,9 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { selectedEntityIDs = new Set(selections.map(selection => selection.id)); const multipleSelections = currentSelections.length > 1; const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs); - + + requestZoneList(); + if (selections.length === 0) { deleteJSONEditor(); deleteJSONMaterialEditor(); @@ -3678,6 +3941,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { const shownGroups = getGroupsForTypes(entityTypes); showGroupsForTypes(entityTypes); + showOnTheSamePage(entityTypes); const lockedMultiValue = getMultiplePropertyValue('locked'); @@ -3687,7 +3951,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { } else { enableProperties(); disableSaveUserDataButton(); - disableSaveMaterialDataButton() + disableSaveMaterialDataButton(); } const certificateIDMultiValue = getMultiplePropertyValue('certificateID'); @@ -3843,6 +4107,15 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { setTextareaScrolling(property.elInput); break; } + case 'multipleZonesSelection': { + property.elInput.value = JSON.stringify(propertyValue); + if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) { + setZonesSelectionData(property.elInput, false); + } else { + setZonesSelectionData(property.elInput, true); + } + break; + } case 'icon': { property.elSpan.innerHTML = propertyData.icons[propertyValue]; property.elSpan.style.display = "inline-block"; @@ -3888,7 +4161,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { } } - if (json !== null) { + if (json !== null && !lockedMultiValue.isMultiDiffValue && !lockedMultiValue.value) { if (editor === null) { createJSONEditor(); } @@ -3920,7 +4193,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { } } - if (materialJson !== null) { + if (materialJson !== null && !lockedMultiValue.isMultiDiffValue && !lockedMultiValue.value) { if (materialEditor === null) { createJSONMaterialEditor(); } @@ -3954,44 +4227,35 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { function loaded() { openEventBridge(function() { - let elPropertiesList = document.getElementById("properties-list"); - + let elPropertiesList = document.getElementById("properties-pages"); + let elTabs = document.getElementById("tabs"); + GROUPS.forEach(function(group) { let elGroup; - if (group.addToGroup !== undefined) { - let fieldset = document.getElementById("properties-" + group.addToGroup); - elGroup = document.createElement('div'); - fieldset.appendChild(elGroup); - } else { - elGroup = document.createElement('div'); - elGroup.className = 'section ' + (group.isMinor ? "minor" : "major"); - elGroup.setAttribute("id", "properties-" + group.id); - elPropertiesList.appendChild(elGroup); - } + + elGroup = document.createElement('div'); + elGroup.className = 'section ' + "major"; + elGroup.setAttribute("id", "properties-" + group.id); + elPropertiesList.appendChild(elGroup); if (group.label !== undefined) { let elLegend = document.createElement('div'); - elLegend.className = "section-header"; - - elLegend.appendChild(createElementFromHTML(`
${group.label}
`)); - - let elSpan = document.createElement('span'); - elSpan.className = "collapse-icon"; - elSpan.innerText = "M"; - elLegend.appendChild(elSpan); + elLegend.className = "tab-section-header"; + elLegend.appendChild(createElementFromHTML(`
${group.label}
`)); elGroup.appendChild(elLegend); + elTabs.appendChild(createElementFromHTML('')); } - + group.properties.forEach(function(propertyData) { let propertyType = propertyData.type; - let propertyID = propertyData.propertyID; + let propertyID = propertyData.propertyID; let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL; let propertyElementID = "property-" + propertyID; propertyElementID = propertyElementID.replace('.', '-'); - + let elContainer, elLabel; - + if (propertyData.replaceID === undefined) { // Create subheader, or create new property and append it. if (propertyType === "sub-header") { @@ -4081,15 +4345,15 @@ function loaded() { property.elContainer = elContainer; property.spaceMode = propertySpaceMode; property.group_id = group.id; - + if (property.type !== 'placeholder') { properties[propertyID] = property; - } + } if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') { propertyRangeRequests.push(propertyID); } - + let showPropertyRule = propertyData.showPropertyRule; if (showPropertyRule !== undefined) { let dependentProperty = Object.keys(showPropertyRule)[0]; @@ -4104,15 +4368,12 @@ function loaded() { } } }); - + elGroups[group.id] = elGroup; }); - let minorSections = document.querySelectorAll(".section.minor"); - minorSections[minorSections.length - 1].className += " last"; - updateVisibleSpaceModeProperties(); - + if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); @@ -4184,6 +4445,9 @@ function loaded() { if (data.entityID === getFirstSelectedID()) { setMaterialTargetData(data.materialTargetData); } + } else if (data.type === 'zoneListRequest') { + zonesList = data.zones; + updateAllZoneSelect(); } }); @@ -4191,7 +4455,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests })); } - + // Server Script Status let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus'); let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1]; @@ -4200,7 +4464,7 @@ function loaded() { let elServerScriptStatus = document.createElement('div'); elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); elServerScriptStatusContainer.appendChild(elServerScriptStatus); - + // Server Script Error let elServerScripts = getPropertyInputElement("serverScripts"); let elDiv = document.createElement('div'); @@ -4210,18 +4474,16 @@ function loaded() { elServerScriptError.setAttribute("id", serverScriptErrorElementID); elDiv.appendChild(elServerScriptError); elServerScriptStatusContainer.appendChild(elDiv); - + let elScript = getPropertyInputElement("script"); elScript.parentNode.className = "url refresh"; elServerScripts.parentNode.className = "url refresh"; - + // User Data let userDataProperty = properties["userData"]; let elUserData = userDataProperty.elInput; let userDataElementID = userDataProperty.elementID; elDiv = elUserData.parentNode; - let elStaticUserData = document.createElement('div'); - elStaticUserData.setAttribute("id", userDataElementID + "-static"); let elUserDataEditor = document.createElement('div'); elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); let elUserDataEditorStatus = document.createElement('div'); @@ -4230,17 +4492,14 @@ function loaded() { elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.innerText = "Saved!"; elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); - elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData); elDiv.insertBefore(elUserDataEditorStatus, elUserData); - + // Material Data let materialDataProperty = properties["materialData"]; let elMaterialData = materialDataProperty.elInput; let materialDataElementID = materialDataProperty.elementID; elDiv = elMaterialData.parentNode; - let elStaticMaterialData = document.createElement('div'); - elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); let elMaterialDataEditor = document.createElement('div'); elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); let elMaterialDataEditorStatus = document.createElement('div'); @@ -4249,26 +4508,9 @@ function loaded() { elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.innerText = "Saved!"; elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); - elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData); - - // Collapsible sections - let elCollapsible = document.getElementsByClassName("collapse-icon"); - let toggleCollapsedEvent = function(event) { - let element = this.parentNode.parentNode; - let isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false; - element.setAttribute("collapsed", isCollapsed ? "true" : "false"); - this.textContent = isCollapsed ? "L" : "M"; - }; - - for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { - let curCollapsibleElement = elCollapsible[collapseIndex]; - curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true); - } - // Textarea scrollbars let elTextareas = document.getElementsByTagName("TEXTAREA"); @@ -4285,7 +4527,7 @@ function loaded() { event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); } - + // Dropdowns // For each dropdown the following replacement is created in place of the original dropdown... // Structure created: @@ -4297,7 +4539,7 @@ function loaded() { //
  • ...
  • // // - // + // let elDropdowns = document.getElementsByTagName("select"); for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { let elDropdown = elDropdowns[dropDownIndex]; @@ -4346,7 +4588,7 @@ function loaded() { li.addEventListener("click", setDropdownValue); ul.appendChild(li); } - + let propertyID = elDropdown.getAttribute("propertyID"); let property = properties[propertyID]; property.elInput = dt; @@ -4407,14 +4649,14 @@ function loaded() { } })); }, false); - + window.onblur = function() { // Fake a change event let ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); }; - + // For input and textarea elements, select all of the text on focus let els = document.querySelectorAll("input, textarea"); for (let i = 0; i < els.length; ++i) { @@ -4422,12 +4664,14 @@ function loaded() { e.target.select(); }; } - - bindAllNonJSONEditorElements(); + + bindAllNonJSONEditorElements(); showGroupsForType("None"); + showPage("base"); resetProperties(); - disableProperties(); + disableProperties(); + }); augmentSpinButtons(); @@ -4442,3 +4686,30 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' })); }, 1000); } + +function showOnTheSamePage(entityType) { + let numberOfTypes = entityType.length; + let matchingType = 0; + for (let i = 0; i < numberOfTypes; i++) { + if (GROUPS_PER_TYPE[entityType[i]].includes(currentTab)) { + matchingType = matchingType + 1; + } + } + if (matchingType !== numberOfTypes) { + currentTab = "base"; + } + showPage(currentTab); +} + +function showPage(id) { + currentTab = id; + Object.entries(elGroups).forEach(([groupKey, elGroup]) => { + if (groupKey === id) { + elGroup.style.display = "block"; + document.getElementById("tab-" + groupKey).style.backgroundColor = "#2E2E2E"; + } else { + elGroup.style.display = "none"; + document.getElementById("tab-" + groupKey).style.backgroundColor = "#404040"; + } + }); +} diff --git a/scripts/system/create/entityProperties/html/tabs/base.png b/scripts/system/create/entityProperties/html/tabs/base.png new file mode 100644 index 0000000000..9f656abe27 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/base.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/behavior.png b/scripts/system/create/entityProperties/html/tabs/behavior.png new file mode 100644 index 0000000000..12662b8a1d Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/behavior.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/collision.png b/scripts/system/create/entityProperties/html/tabs/collision.png new file mode 100644 index 0000000000..c9bed39385 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/collision.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/grid.png b/scripts/system/create/entityProperties/html/tabs/grid.png new file mode 100644 index 0000000000..d9b8afb1ae Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/grid.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/image.png b/scripts/system/create/entityProperties/html/tabs/image.png new file mode 100644 index 0000000000..ebd03648f6 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/image.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/light.png b/scripts/system/create/entityProperties/html/tabs/light.png new file mode 100644 index 0000000000..bed097d54e Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/light.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/material.png b/scripts/system/create/entityProperties/html/tabs/material.png new file mode 100644 index 0000000000..458c6bad48 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/material.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/model.png b/scripts/system/create/entityProperties/html/tabs/model.png new file mode 100644 index 0000000000..79aa6b3830 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/model.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles.png b/scripts/system/create/entityProperties/html/tabs/particles.png new file mode 100644 index 0000000000..6a0d47cacb Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles_alpha.png b/scripts/system/create/entityProperties/html/tabs/particles_alpha.png new file mode 100644 index 0000000000..8df2981f14 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_alpha.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles_behavior.png b/scripts/system/create/entityProperties/html/tabs/particles_behavior.png new file mode 100644 index 0000000000..6be9f90638 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_behavior.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles_color.png b/scripts/system/create/entityProperties/html/tabs/particles_color.png new file mode 100644 index 0000000000..ac66a902cf Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_color.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles_constraints.png b/scripts/system/create/entityProperties/html/tabs/particles_constraints.png new file mode 100644 index 0000000000..9c783acc9b Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_constraints.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles_emit.png b/scripts/system/create/entityProperties/html/tabs/particles_emit.png new file mode 100644 index 0000000000..223baa5d56 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_emit.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/particles_size.png b/scripts/system/create/entityProperties/html/tabs/particles_size.png new file mode 100644 index 0000000000..b51fe65cdf Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_size.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/physics.png b/scripts/system/create/entityProperties/html/tabs/physics.png new file mode 100644 index 0000000000..f0fc451d37 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/physics.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/scripts.png b/scripts/system/create/entityProperties/html/tabs/scripts.png new file mode 100644 index 0000000000..2249af165b Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/scripts.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/shape.png b/scripts/system/create/entityProperties/html/tabs/shape.png new file mode 100644 index 0000000000..5f3722caf7 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/shape.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/spatial.png b/scripts/system/create/entityProperties/html/tabs/spatial.png new file mode 100644 index 0000000000..a280d0e822 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/spatial.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/text.png b/scripts/system/create/entityProperties/html/tabs/text.png new file mode 100644 index 0000000000..405d8e4104 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/text.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/web.png b/scripts/system/create/entityProperties/html/tabs/web.png new file mode 100644 index 0000000000..c1fc573619 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/web.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone.png b/scripts/system/create/entityProperties/html/tabs/zone.png new file mode 100644 index 0000000000..276ba26799 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone_ambient_light.png b/scripts/system/create/entityProperties/html/tabs/zone_ambient_light.png new file mode 100644 index 0000000000..ff01b16aaf Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_ambient_light.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone_avatar_priority.png b/scripts/system/create/entityProperties/html/tabs/zone_avatar_priority.png new file mode 100644 index 0000000000..e91111fb9b Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_avatar_priority.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone_bloom.png b/scripts/system/create/entityProperties/html/tabs/zone_bloom.png new file mode 100644 index 0000000000..925654df81 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_bloom.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone_haze.png b/scripts/system/create/entityProperties/html/tabs/zone_haze.png new file mode 100644 index 0000000000..0cf96692f8 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_haze.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone_key_light.png b/scripts/system/create/entityProperties/html/tabs/zone_key_light.png new file mode 100644 index 0000000000..6527c65320 Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_key_light.png differ diff --git a/scripts/system/create/entityProperties/html/tabs/zone_skybox.png b/scripts/system/create/entityProperties/html/tabs/zone_skybox.png new file mode 100644 index 0000000000..17697a817b Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_skybox.png differ diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index 6774c72627..9bab739060 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -1118,8 +1118,9 @@ SelectionDisplay = (function() { return false; } - // No action if the Alt key is pressed. - if (event.isAlt) { + // No action if the Alt key is pressed unless on Mac. + var isMac = Controller.getValue(Controller.Hardware.Application.PlatformMac); + if (event.isAlt && !isMac) { return; } @@ -2044,10 +2045,11 @@ SelectionDisplay = (function() { Vec3.print(" pickResult.intersection", pickResult.intersection); } - // Duplicate entities if Ctrl is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isControl || doDuplicate) { + // Duplicate entities if Ctrl is pressed on Windows or Alt is press on Mac. + // This will make a copy of the selected entities and move the _original_ entities, not the new ones. + var isMac = Controller.getValue(Controller.Hardware.Application.PlatformMac); + var isDuplicate = isMac ? event.isAlt : event.isControl; + if (isDuplicate || doDuplicate) { duplicatedEntityIDs = SelectionManager.duplicateSelection(); var ids = []; for (var i = 0; i < duplicatedEntityIDs.length; ++i) { @@ -2270,10 +2272,11 @@ SelectionDisplay = (function() { addHandleTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { - // Duplicate entities if Ctrl is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isControl) { + // Duplicate entities if Ctrl is pressed on Windows or Alt is pressed on Mac. + // This will make a copy of the selected entities and move the _original_ entities, not the new ones. + var isMac = Controller.getValue(Controller.Hardware.Application.PlatformMac); + var isDuplicate = isMac ? event.isAlt : event.isControl; + if (isDuplicate) { duplicatedEntityIDs = SelectionManager.duplicateSelection(); var ids = []; for (var i = 0; i < duplicatedEntityIDs.length; ++i) { diff --git a/scripts/system/html/css/tabs.css b/scripts/system/html/css/tabs.css new file mode 100644 index 0000000000..87e1c11ee8 --- /dev/null +++ b/scripts/system/html/css/tabs.css @@ -0,0 +1,77 @@ +/* +// tabs.css +// +// Created by Alezia Kurdis on 27 Feb 2020 +// Copyright 2020 Project Athena contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +div.tabsContainer { + float: left; + width: 32px; + padding: 0px; +} + +.tabsContainer button { + padding: 4px; + text-align: center; + cursor: pointer; + transition: 0.4s; + font-size: 14px; + background-color: #404040; + border-color: #404040; + border-width: 1px 0px 1px 1px; + border-radius: 5px 0px 0px 5px; + outline: none; +} + +.tabsContainer button:hover { + background-color: #575757; +} + +.tabsContainer button.active { + background-color: #2E2E2E; +} + +div.labelTabHeader { + font-size: 20px; + font-weight: 700; + height: 40px; + color: #ffffff; +} + +div.tab-section-header { + width: 100%; + padding: 5px; +} + +table.tabsTableFrame { + width: 100%; + min-height: 352px; + display: block; +} + +tr.tabsTrFrame { + width: 100%; +} + +td.tabsFrame { + width: 32px; + vertical-align: top; + background-color: #575757; + padding: 0px; + border: 0px; +} + +td.tabsPropertiesFrame { + width: 100%; + vertical-align: top; + border:0px; +} + +div.tabsPropertiesPage { + min-height: 352px; + display: block; +} diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 2d225fd2a6..23cff7699a 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -48,7 +48,6 @@ "Tip: Don't want others invading your personal space? Turn on the Bubble!", "Tip: Want to make a friend? Shake hands with them in VR!", "Tip: Enjoy live music? Visit Rust to dance your heart out!", - "Tip: Have you visited BodyMart to check out the new avatars recently?", "Tip: Use the Create app to import models and create custom entities.", "Tip: We're open source! Feel free to contribute to our code on GitHub!", "Tip: What emotes have you used in the Emote app?", diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index a92fbf1065..b7593656a3 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -82,9 +82,7 @@ function calcSpawnInfo(hand, landscape) { cleanUpOldMaterialEntities = function() { var avatarEntityData = MyAvatar.getAvatarEntityData(); for (var entityID in avatarEntityData) { - var entityName = Entities.getEntityProperties(entityID, ["name"]).name; - - if (entityName === TABLET_MATERIAL_ENTITY_NAME) { + if (avatarEntityData[entityID].name === TABLET_MATERIAL_ENTITY_NAME) { Entities.deleteEntity(entityID); } } diff --git a/scripts/system/more/app-more.js b/scripts/system/more/app-more.js index 99b359d9e8..1902ddf855 100644 --- a/scripts/system/more/app-more.js +++ b/scripts/system/more/app-more.js @@ -11,7 +11,8 @@ // // 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 ROOT = Script.resolvePath('').split("app-more.js")[0]; var APP_NAME = "MORE..."; @@ -68,8 +69,7 @@ if (instruction.action === "installScript") { if (lastProcessing.action !== instruction.action || lastProcessing.script !== instruction.script) { - ScriptDiscoveryService.loadOneScript(instruction.script); - lastProcessing.action = instruction.action; + ScriptDiscoveryService.loadScript(instruction.script, true, false, false, true, false); // Force reload the script, do not use cache. lastProcessing.action = instruction.action; lastProcessing.script = instruction.script; Script.setTimeout(function() { sendRunningScriptList(); diff --git a/server-console/package.json b/server-console/package.json index 6824d1d9cf..ed01a9c89e 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -1,6 +1,6 @@ { - "name": "HighFidelityConsole", - "description": "High Fidelity Console", + "name": "VircadiaConsole", + "description": "Vircadia Console", "author": "High Fidelity", "license": "Apache-2.0", "version": "1.0.0", @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/highfidelity/hifi.git" + "url": "https://github.com/kasenvr/project-athena.git" }, "main": "src/main.js", "scripts": { diff --git a/server-console/packager.js b/server-console/packager.js index 60aff6c540..dadc306bbf 100644 --- a/server-console/packager.js +++ b/server-console/packager.js @@ -28,15 +28,15 @@ var options = { const EXEC_NAME = "server-console"; var SHORT_NAME = argv.client_only ? "Console" : "Sandbox"; -var FULL_NAME = argv.client_only ? "High Fidelity Console" : "High Fidelity Sandbox"; +var FULL_NAME = argv.client_only ? "Vircadia Console" : "Vircadia Sandbox"; // setup per OS options if (osType == "Darwin") { - options["app-bundle-id"] = "com.highfidelity.server-console" + (argv.production ? "" : "-dev") + options["app-bundle-id"] = "com.vircadia.server-console" + (argv.production ? "" : "-dev") options["name"] = SHORT_NAME } else if (osType == "Windows_NT") { options["version-string"] = { - CompanyName: "High Fidelity, Inc.", + CompanyName: "Vircadia", FileDescription: FULL_NAME, ProductName: FULL_NAME, OriginalFilename: EXEC_NAME + ".exe" diff --git a/server-console/resources/console-beta.icns b/server-console/resources/console-beta.icns index 0023636e39..1fe162f88e 100644 Binary files a/server-console/resources/console-beta.icns and b/server-console/resources/console-beta.icns differ diff --git a/server-console/resources/console-beta.ico b/server-console/resources/console-beta.ico index 9263623fb4..63a3eff482 100644 Binary files a/server-console/resources/console-beta.ico and b/server-console/resources/console-beta.ico differ diff --git a/server-console/resources/console-beta.png b/server-console/resources/console-beta.png index 3494a6ef8f..59fe03c8ee 100644 Binary files a/server-console/resources/console-beta.png and b/server-console/resources/console-beta.png differ diff --git a/server-console/resources/console-notification.png b/server-console/resources/console-notification.png index b7df5312e3..e8991cd773 100644 Binary files a/server-console/resources/console-notification.png and b/server-console/resources/console-notification.png differ diff --git a/server-console/resources/console-tray-osx-stopped.png b/server-console/resources/console-tray-osx-stopped.png index 53589b60b0..82fcf76d5c 100644 Binary files a/server-console/resources/console-tray-osx-stopped.png and b/server-console/resources/console-tray-osx-stopped.png differ diff --git a/server-console/resources/console-tray-osx-stopped@2x.png b/server-console/resources/console-tray-osx-stopped@2x.png index 5356afb93e..535e6b8135 100644 Binary files a/server-console/resources/console-tray-osx-stopped@2x.png and b/server-console/resources/console-tray-osx-stopped@2x.png differ diff --git a/server-console/resources/console-tray-osx-stopping.png b/server-console/resources/console-tray-osx-stopping.png index 3255d43f60..c99313d00b 100644 Binary files a/server-console/resources/console-tray-osx-stopping.png and b/server-console/resources/console-tray-osx-stopping.png differ diff --git a/server-console/resources/console-tray-osx-stopping@2x.png b/server-console/resources/console-tray-osx-stopping@2x.png index f8fb805350..de09b722d3 100644 Binary files a/server-console/resources/console-tray-osx-stopping@2x.png and b/server-console/resources/console-tray-osx-stopping@2x.png differ diff --git a/server-console/resources/console-tray-osx.png b/server-console/resources/console-tray-osx.png index 11dbbbc38f..36756dd16f 100644 Binary files a/server-console/resources/console-tray-osx.png and b/server-console/resources/console-tray-osx.png differ diff --git a/server-console/resources/console-tray-osx@2x.png b/server-console/resources/console-tray-osx@2x.png index 8acd094606..86d9eacea2 100644 Binary files a/server-console/resources/console-tray-osx@2x.png and b/server-console/resources/console-tray-osx@2x.png differ diff --git a/server-console/resources/console-tray-stopped.ico b/server-console/resources/console-tray-stopped.ico index 8921a80297..2f0251a185 100644 Binary files a/server-console/resources/console-tray-stopped.ico and b/server-console/resources/console-tray-stopped.ico differ diff --git a/server-console/resources/console-tray-win-stopped.png b/server-console/resources/console-tray-win-stopped.png index 033ca3dfa4..cedc6e02e1 100644 Binary files a/server-console/resources/console-tray-win-stopped.png and b/server-console/resources/console-tray-win-stopped.png differ diff --git a/server-console/resources/console-tray-win-stopped@2x.png b/server-console/resources/console-tray-win-stopped@2x.png index e3d8de7aaa..b176209758 100644 Binary files a/server-console/resources/console-tray-win-stopped@2x.png and b/server-console/resources/console-tray-win-stopped@2x.png differ diff --git a/server-console/resources/console-tray-win-stopping.png b/server-console/resources/console-tray-win-stopping.png index 860bddbd3e..ddf60efc8e 100644 Binary files a/server-console/resources/console-tray-win-stopping.png and b/server-console/resources/console-tray-win-stopping.png differ diff --git a/server-console/resources/console-tray-win-stopping@2x.png b/server-console/resources/console-tray-win-stopping@2x.png index 46e5e32f59..50f9df457f 100644 Binary files a/server-console/resources/console-tray-win-stopping@2x.png and b/server-console/resources/console-tray-win-stopping@2x.png differ diff --git a/server-console/resources/console-tray-win.png b/server-console/resources/console-tray-win.png index 6ff889828c..cd8fa1d691 100644 Binary files a/server-console/resources/console-tray-win.png and b/server-console/resources/console-tray-win.png differ diff --git a/server-console/resources/console-tray-win@2x.png b/server-console/resources/console-tray-win@2x.png index 7295647c20..142498f6a1 100644 Binary files a/server-console/resources/console-tray-win@2x.png and b/server-console/resources/console-tray-win@2x.png differ diff --git a/server-console/resources/console-tray.ico b/server-console/resources/console-tray.ico index becc1b8e8b..6f06b43f73 100644 Binary files a/server-console/resources/console-tray.ico and b/server-console/resources/console-tray.ico differ diff --git a/server-console/resources/console.icns b/server-console/resources/console.icns index 38a7cc5b64..1fe162f88e 100644 Binary files a/server-console/resources/console.icns and b/server-console/resources/console.icns differ diff --git a/server-console/resources/console.ico b/server-console/resources/console.ico index a3957ea0eb..63a3eff482 100644 Binary files a/server-console/resources/console.ico and b/server-console/resources/console.ico differ diff --git a/server-console/resources/console.png b/server-console/resources/console.png index 0cd3b0a3c0..e6e13081fd 100644 Binary files a/server-console/resources/console.png and b/server-console/resources/console.png differ diff --git a/server-console/src/content-update.html b/server-console/src/content-update.html index 9298cd53e6..7c6b4c9971 100644 --- a/server-console/src/content-update.html +++ b/server-console/src/content-update.html @@ -1,7 +1,7 @@ - High Fidelity Sandbox + Vircadia Sandbox @@ -45,7 +45,7 @@
    diff --git a/server-console/src/downloader.html b/server-console/src/downloader.html index 07d3392246..f1f7a2024c 100644 --- a/server-console/src/downloader.html +++ b/server-console/src/downloader.html @@ -1,7 +1,7 @@ - High Fidelity + Vircadia diff --git a/server-console/src/images/console-hf-logo-2x.png b/server-console/src/images/console-hf-logo-2x.png index 420fb18fde..e8b048fae0 100644 Binary files a/server-console/src/images/console-hf-logo-2x.png and b/server-console/src/images/console-hf-logo-2x.png differ diff --git a/server-console/src/main.js b/server-console/src/main.js index 5adb4be4cb..d8d6fea4bf 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -113,7 +113,7 @@ function isInterfaceInstalled () { // the executable to a random location before starting it // which makes finding the interface near impossible using // relative paths. For now, as there are no server-only - // installs, we just assume the interface is installed here + // installs, we just assume the interface is installed here return true; } else { return interfacePath; @@ -134,7 +134,7 @@ function shutdown() { dialog.showMessageBox({ type: 'question', buttons: ['Yes', 'No'], - title: 'Stopping High Fidelity Sandbox', + title: 'Stopping Vircadia Sandbox', message: 'Quitting will stop your Sandbox and your Home domain will no longer be running.\nDo you wish to continue?' }, shutdownCallback); } else { @@ -280,7 +280,7 @@ function binaryMissingMessage(displayName, executableName, required) { var message = "The " + displayName + " executable was not found.\n"; if (required) { - message += "It is required for the High Fidelity Sandbox to run.\n\n"; + message += "It is required for the Vircadia Sandbox to run.\n\n"; } else { message += "\n"; } @@ -293,7 +293,7 @@ function binaryMissingMessage(displayName, executableName, required) { message += paths.join("\n"); } else { message += "It is expected to be found beside this executable.\n"; - message += "You may need to re-install the High Fidelity Sandbox."; + message += "You may need to re-install the Vircadia Sandbox."; } return message; @@ -337,10 +337,10 @@ var notificationState = NotificationState.UNNOTIFIED; function setNotificationState (notificationType, pending = undefined) { if (pending !== undefined) { - if ((notificationType === HifiNotificationType.TRANSACTIONS || + if ((notificationType === HifiNotificationType.TRANSACTIONS || notificationType === HifiNotificationType.ITEMS)) { // special case, because we want to clear the indicator light - // on INVENTORY when either Transactions or Items are + // on INVENTORY when either Transactions or Items are // clicked on in the notification popup, we detect that case // here and force both to be unnotified. pendingNotifications[HifiNotificationType.TRANSACTIONS] = pending; @@ -399,7 +399,7 @@ function visitSandboxClicked() { StartInterface('hifi://localhost'); } else { // show an error to say that we can't go home without an interface instance - dialog.showErrorBox("Client Not Found", binaryMissingMessage("High Fidelity client", "Interface", false)); + dialog.showErrorBox("Client Not Found", binaryMissingMessage("Vircadia client", "Interface", false)); } } @@ -613,7 +613,7 @@ function updateTrayMenu(serverState) { tray.setImage(trayIcons[notificationState]); tray.setContextMenu(Menu.buildFromTemplate(menuArray)); if (isShuttingDown) { - tray.setToolTip('High Fidelity - Shutting Down'); + tray.setToolTip('Vircadia - Shutting Down'); } } } @@ -868,7 +868,7 @@ function onContentLoaded() { notifier.notify({ icon: notificationIcon, title: 'An update is available!', - message: 'High Fidelity version ' + latestVersion + ' is available', + message: 'Vircadia version ' + latestVersion + ' is available', wait: true, appID: buildInfo.appUserModelId, url: url @@ -941,7 +941,7 @@ app.on('ready', function() { // Create tray icon tray = new Tray(trayIcons[NotificationState.UNNOTIFIED]); - tray.setToolTip('High Fidelity'); + tray.setToolTip('Vircadia'); tray.on('click', function() { tray.popUpContextMenu(tray.menu); @@ -951,7 +951,7 @@ app.on('ready', function() { trayNotifications.startPolling(); } updateTrayMenu(ProcessGroupStates.STOPPED); - + if (isServerInstalled()) { maybeInstallDefaultContentSet(onContentLoaded); } diff --git a/server-console/src/modules/hf-app.js b/server-console/src/modules/hf-app.js index af6c38cdb3..cdb744e5e6 100644 --- a/server-console/src/modules/hf-app.js +++ b/server-console/src/modules/hf-app.js @@ -34,8 +34,8 @@ exports.getBuildInfo = function() { buildIdentifier: "dev", buildNumber: "0", stableBuild: "0", - organization: "High Fidelity - dev", - appUserModelId: "com.highfidelity.console" + organization: "Vircadia - dev", + appUserModelId: "com.vircadia.console" }; var buildInfo = DEFAULT_BUILD_INFO; @@ -63,11 +63,11 @@ exports.startInterface = function(url) { // do this as a workaround for app translocation on osx, which makes // it nearly impossible to find the interface executable - var bundle_id = 'com.highfidelity.interface-dev'; + var bundle_id = 'com.vircadia.interface-dev'; if (buildInfo.releaseType == 'PR') { - bundle_id = 'com.highfidelity.interface-pr'; + bundle_id = 'com.vircadia.interface-pr'; } else if (buildInfo.releaseType == 'PRODUCTION') { - bundle_id = 'com.highfidelity.interface'; + bundle_id = 'com.vircadia.interface'; } childProcess.exec('open -b ' + bundle_id + ' --args --url ' + url); } else { diff --git a/server-console/src/splash.html b/server-console/src/splash.html index d93b742181..f273ef16b7 100644 --- a/server-console/src/splash.html +++ b/server-console/src/splash.html @@ -1,7 +1,7 @@ - High Fidelity + Vircadia @@ -19,7 +19,7 @@

    What now?

    -High Fidelity is now installed and your Home domain is ready for you to explore. To start you off, we've put a few things in your home to play around with and learn the ropes. +Vircadia is now installed and your Home domain is ready for you to explore. To start you off, we've put a few things in your home to play around with and learn the ropes.

    @@ -28,13 +28,13 @@ You can make your home yours by uploading your own models and scripts.

    - To get started exploring and creating, check out our Quick-start Guide + To get started exploring and creating, check out our Quick-start Guide

    How do I use it?

    - You can manage your server by clicking on the High Fidelity icon in your + You can manage your server by clicking on the Vircadia icon in your