diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml deleted file mode 100644 index 36cab7a929..0000000000 --- a/.github/workflows/cmake.yml +++ /dev/null @@ -1,197 +0,0 @@ -name: CMake CI - -on: - pull_request: - types: [opened, synchronize, reopened, labeled] - -# Eventually we will want to enable CI builds on push to specific branches, but we probably want to configure it not to happen in forks -# push: -# branches: -# - master -# - stable - -env: - CI_BUILD: Github - BUILD_TYPE: Release - RELEASE_TYPE: PR - RELEASE_NUMBER: ${{ github.event.number }} - VERSION_CODE: ${{ github.event.number }} - GIT_PR_COMMIT: ${{ github.sha }} - # FIXME should be a shortened version of the SHA - GIT_PR_COMMIT_SHORT: ${{ github.sha }} - HIFI_VCPKG_BOOTSTRAP: true - - AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }} - - # Settings for uploading - APP_NAME: interface - #APP_NAME: gpu-frame-player - BUCKET_NAME: hifi-public - UPLOAD_PREFIX: austin/builds - - # OSX specific variables - DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer - MACOSX_DEPLOYMENT_TARGET: '10.11' - # WIN32 specific variables - PreferredToolArchitecture: X64 - - BS_USERNAME: gustavo@highfidelity.io - BS_PASSWORD: ${MASKED_BUGSPLAT_PASSWORD} - CMAKE_BACKTRACE_URL: https://highfidelity.sp.backtrace.io:6098 - GA_TRACKING_ID: UA-39558647-8 - OCULUS_APP_ID: '1255907384473836' - # CMAKE_BACKTRACE_TOKEN: ${MASKED_CMAKE_BACKTRACE_TOKEN} - # CMAKE_BACKTRACE_SYMBOLS_TOKEN: ${MASKED_BACKTRACE_UPLOAD_TOKEN} - -# 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 -#OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_NIGHTLY} -#OAUTH_CLIENT_ID=6c7d2349c0614640150db37457a1f75dce98a28ffe8f14d47f6cfae4de5b262a -#OAUTH_REDIRECT_URI=https://dev-android-interface.highfidelity.com/auth -#ARTIFACT_EXPRESSION=android/*.apk -#SHA7=${GIT_PR_COMMIT_SHORT} -#ANDROID_APK_NAME=HighFidelity-Beta-PR${RELEASE_NUMBER}-${GIT_PR_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: - build_client_mac: - runs-on: macOS-latest - if: github.event.action != 'labeled' || github.event.label.name == 'rebuild-mac' || github.event.label.name == 'rebuild' - steps: - - uses: actions/checkout@v1 - with: - submodules: true - fetch-depth: 1 - - name: Install python modules - run: pip3 install awscli boto3 PyGithub - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DCLIENT_ONLY:BOOLEAN=TRUE -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode -DVCPKG_APPLOCAL_DEPS=OFF - - name: Build Console - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target packaged-server-console - - name: Build Application - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target $APP_NAME - - name: Build Installer - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target package - - build_full_mac: - runs-on: macOS-latest - if: github.event.action != 'labeled' || github.event.label.name == 'rebuild-mac' || github.event.label.name == 'rebuild' - steps: - - uses: actions/checkout@v1 - with: - submodules: true - fetch-depth: 1 - - name: Install python modules - run: pip3 install awscli boto3 PyGithub - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode -DVCPKG_APPLOCAL_DEPS=OFF - - name: Build Console - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target packaged-server-console - - name: Build Application - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target $APP_NAME - - name: Build Domain Server - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target domain-server - - name: Build Assignment Client - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target assignment-client - - name: Build Installer - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target package - - build_client_win32: - runs-on: windows-latest - if: github.event.action != 'labeled' || github.event.label.name == 'rebuild-win' || github.event.label.name == 'rebuild' - steps: - - uses: actions/checkout@v1 - with: - submodules: true - fetch-depth: 1 - - name: Install python modules - run: pip install awscli boto3 PyGithub - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCLIENT_ONLY:BOOLEAN=TRUE -A x64 - - name: Build Console - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target packaged-server-console - - name: Build Application - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target $APP_NAME - - name: Build Installer - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target package - - build_full_win32: - runs-on: windows-latest - if: github.event.action != 'labeled' || github.event.label.name == 'rebuild-win' || github.event.label.name == 'rebuild' - steps: - - uses: actions/checkout@v1 - with: - submodules: true - fetch-depth: 1 - - name: Install python modules - run: pip install awscli boto3 PyGithub - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -A x64 - - name: Build Console - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target packaged-server-console - - name: Build Application - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target $APP_NAME - - name: Build Domain Server - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target domain-server - - name: Build Assignment Client - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target assignment-client - - name: Build Installer - shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config $BUILD_TYPE --target package diff --git a/.github/workflows/dump.yml b/.github/workflows/dump.yml new file mode 100644 index 0000000000..279d8ff2f8 --- /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" \ No newline at end of file diff --git a/.github/workflows/master_build.yml b/.github/workflows/master_build.yml new file mode 100644 index 0000000000..6f87f59d1d --- /dev/null +++ b/.github/workflows/master_build.yml @@ -0,0 +1,251 @@ +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 + # Configureation 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 + - 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 "${{runner.workspace}}/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..71ff70a3c4 --- /dev/null +++ b/.github/workflows/pr_build.yml @@ -0,0 +1,121 @@ +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 + # Configureation 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 + - 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 + + 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: 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 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/CMakeLists.txt b/CMakeLists.txt index a651146dfc..059bcf98d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,6 +257,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) @@ -283,6 +288,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/android/containerized_build.sh b/android/containerized_build.sh index 37be6f8be3..0c21d1df91 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -29,7 +29,7 @@ docker run \ -e CMAKE_BACKTRACE_TOKEN \ -e CMAKE_BACKTRACE_SYMBOLS_TOKEN \ -e GA_TRACKING_ID \ - -e GIT_PR_COMMIT \ + -e GIT_COMMIT \ -e OAUTH_CLIENT_SECRET \ -e OAUTH_CLIENT_ID \ -e OAUTH_REDIRECT_URI \ diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index b4042cf294..31d8c9e5a8 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -18,21 +18,23 @@ link_hifi_libraries( ) include_hifi_library_headers(procedural) -add_dependencies(${TARGET_NAME} oven) +if (BUILD_TOOLS) + add_dependencies(${TARGET_NAME} oven) -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() + 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/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index ea5246c59a..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,41 +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() }; - EntityItemID id {}; +// 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; - findPriorityZone->id = zoneItem->getEntityItemID(); - } - } + 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 @@ -152,15 +158,22 @@ 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); - bool currentlyHasPriority = 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(findPriorityZone.id.toRfc4122()); + packet->write(zoneId.toRfc4122()); nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); } _avatar->setNeedsHeroCheck(false); @@ -227,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; } @@ -244,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) { @@ -303,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++; @@ -361,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); @@ -463,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 { @@ -484,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/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 78f52aed98..cbc89d7724 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -91,13 +91,11 @@ macro(SET_PACKAGING_PARAMETERS) endif () if ((PRODUCTION_BUILD OR PR_BUILD) AND NOT STABLE_BUILD) - set(GIT_PR_COMMIT $ENV{GIT_PR_COMMIT}) - #set(GIT_COMMIT_HASH ${GIT_PR_COMMIT}) - string(SUBSTRING ${GIT_PR_COMMIT} 0 7 GIT_COMMIT_HASH) + 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 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 diff --git a/cmake/macros/TargetPolyvox.cmake b/cmake/macros/TargetPolyvox.cmake index 576b454f57..1779ab21f7 100644 --- a/cmake/macros/TargetPolyvox.cmake +++ b/cmake/macros/TargetPolyvox.cmake @@ -21,6 +21,23 @@ macro(TARGET_POLYVOX) list(APPEND POLYVOX_LIBRARY_DEBUG ${POLYVOX_UTIL_LIBRARY_DEBUG}) select_library_configurations(POLYVOX) list(APPEND POLYVOX_INCLUDE_DIRS ${VCPKG_INSTALL_ROOT}/include) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(_LIB_GLOBS) + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(_PV_LIBRARIES ${POLYVOX_LIBRARY_DEBUG}) + else() + set(_PV_LIBRARIES ${POLYVOX_LIBRARY_RELEASE}) + endif() + foreach(_lib ${_PV_LIBRARIES}) + list(APPEND _LIB_GLOBS ${_lib}*) + endforeach() + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND cp + ARGS -d ${_LIB_GLOBS} ${CMAKE_BINARY_DIR} + ) + endif() endif() target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) target_include_directories(${TARGET_NAME} PUBLIC ${POLYVOX_INCLUDE_DIRS}) diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake index ee57fb4af3..fa05ec55eb 100644 --- a/cmake/macros/TargetQuazip.cmake +++ b/cmake/macros/TargetQuazip.cmake @@ -10,4 +10,4 @@ macro(TARGET_QUAZIP) 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}) -endmacro() \ No newline at end of file +endmacro() diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9eec7df86e..c2de9c78ce 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3616,25 +3616,18 @@ void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer message) { - QUuid avatar = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - QUuid zone = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid zoneID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - if (avatar.isNull()) { + if (avatarID.isNull()) { qCWarning(domain_server) << "Ignoring null avatar presence"; return; } - auto limitedNodeList = DependencyManager::get(); - auto matchingNode = limitedNodeList->nodeWithUUID(avatar); - if (!matchingNode) { - qCWarning(domain_server) << "Ignoring avatar presence for unknown avatar" << avatar; - return; - } - QString verifiedUsername = matchingNode->getPermissions().getVerifiedUserName(); static const int SCREENSHARE_EXPIRATION_SECONDS = 24 * 60 * 60; - screensharePresence(zone.isNull() ? "" : zone.toString(), verifiedUsername, SCREENSHARE_EXPIRATION_SECONDS); + screensharePresence(zoneID.isNull() ? "" : zoneID.toString(), avatarID, SCREENSHARE_EXPIRATION_SECONDS); } -void DomainServer::screensharePresence(QString roomname, QString username, int expirationSeconds) { +void DomainServer::screensharePresence(QString roomname, QUuid avatarID, int expirationSeconds) { if (!DependencyManager::get()->hasValidAccessToken()) { static std::once_flag presenceAuthorityWarning; std::call_once(presenceAuthorityWarning, [] { @@ -3642,14 +3635,33 @@ void DomainServer::screensharePresence(QString roomname, QString username, int e }); 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"] = username; + screenshare["username"] = verifiedUsername; screenshare["roomname"] = roomname; if (expirationSeconds > 0) { screenshare["expiration"] = expirationSeconds; @@ -3663,11 +3675,18 @@ void DomainServer::screensharePresence(QString roomname, QString username, int e ); } -void DomainServer::handleSuccessfulScreensharePresence(QNetworkReply* requestReply) { +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) { diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 034ccb5a18..95b4b784cb 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -78,7 +78,7 @@ public: bool isAssetServerEnabled(); - void screensharePresence(QString roomname, QString username, int expiration_seconds = 0); + void screensharePresence(QString roomname, QUuid avatarID, int expiration_seconds = 0); public slots: /// Called by NodeList to inform us a node has been added @@ -132,7 +132,7 @@ private slots: void handleSuccessfulICEServerAddressUpdate(QNetworkReply* requestReply); void handleFailedICEServerAddressUpdate(QNetworkReply* requestReply); - void handleSuccessfulScreensharePresence(QNetworkReply* requestReply); + void handleSuccessfulScreensharePresence(QNetworkReply* requestReply, QJsonObject callbackData); void handleFailedScreensharePresence(QNetworkReply* requestReply); void updateReplicatedNodes(); diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py index 9578f56e0d..34c1b13563 100644 --- a/hifi_vcpkg.py +++ b/hifi_vcpkg.py @@ -75,6 +75,7 @@ endif() 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') @@ -82,7 +83,7 @@ endif() self.vcpkgUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/builds/vcpkg-win32-client.zip?versionId=tSFzbw01VkkVFeRQ6YuAY4dro2HxJR9U' self.vcpkgHash = 'a650db47a63ccdc9904b68ddd16af74772e7e78170b513ea8de5a3b47d032751a3b73dcc7526d88bcb500753ea3dd9880639ca842bb176e2bddb1710f9a58cd3' self.hostTriplet = 'x64-windows' - if ('CI_BUILD' in os.environ) and os.environ["CI_BUILD"] == "Github" and (not self.noClean): + if usePrebuilt: self.prebuiltArchive = "https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/builds/vcpkg-win32.zip?versionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx" elif 'Darwin' == system: self.exe = os.path.join(self.path, 'vcpkg') @@ -90,7 +91,7 @@ endif() self.vcpkgUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/builds/vcpkg-osx-client.tgz?versionId=j0b4azo_zTlH_Q9DElEWOz1UMYZ2nqQw' self.vcpkgHash = '519d666d02ef22b87c793f016ca412e70f92e1d55953c8f9bd4ee40f6d9f78c1df01a6ee293907718f3bbf24075cc35492fb216326dfc50712a95858e9cbcb4d' self.hostTriplet = 'x64-osx' - if ('CI_BUILD' in os.environ) and os.environ["CI_BUILD"] == "Github" and (not self.noClean): + if usePrebuilt: self.prebuiltArchive = "https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/builds/vcpkg-osx.tgz?versionId=6JrIMTdvpBF3MAsjA92BMkO79Psjzs6Z" else: self.exe = os.path.join(self.path, 'vcpkg') diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 9030666609..4dcb0778d4 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -185,7 +185,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 @@ -323,6 +326,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" @@ -347,7 +354,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/src/Application.cpp b/interface/src/Application.cpp index 221ab211f4..7252a1d707 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3395,6 +3395,10 @@ void Application::initializeUi() { setIsInterstitialMode(true); + +#if defined(DISABLE_QML) && defined(Q_OS_LINUX) + resumeAfterLoginDialogActionTaken(); +#endif } @@ -5662,6 +5666,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); @@ -5671,6 +5676,7 @@ void Application::resumeAfterLoginDialogActionTaken() { } updateSystemTabletMode(); +#endif { auto userInputMapper = DependencyManager::get(); diff --git a/interface/src/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp index e1da1699d7..beb1363aac 100644 --- a/interface/src/RefreshRateManager.cpp +++ b/interface/src/RefreshRateManager.cpp @@ -17,12 +17,64 @@ static const int VR_TARGET_RATE = 90; +/**jsdoc + *

Refresh rate profile.

+ * + * + * + * + * + * + * + * + * + *
Refresh Rate ProfileDescription
"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} RefreshRateProfile + */ static const std::array REFRESH_RATE_PROFILE_TO_STRING = { { "Eco", "Interactive", "Realtime" } }; +/**jsdoc + *

Interface states that affect the refresh rate.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Refresh Rate RegimeDescription
"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} RefreshRateRegime + */ static const std::array REFRESH_RATE_REGIME_TO_STRING = { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } }; +/**jsdoc + *

Interface operates in different modes to provide different user experiences (UX).

+ * + * + * + * + * + * + * + * + * + *
UX ModeDescription
"Desktop"Desktop user experience mode.
"VR"VR user experience mode.
+ * + * @typedef {string} UXMode + */ static const std::array UX_MODE_TO_STRING = { { "Desktop", "VR" } }; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cc12e4fcee..d8353796b6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -436,38 +436,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, @@ -1019,7 +1053,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; @@ -1029,11 +1063,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); @@ -1045,14 +1078,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; 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/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/ScreenshareScriptingInterface.cpp b/interface/src/scripting/ScreenshareScriptingInterface.cpp index 89f5ee6970..3bf8336fe4 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.cpp +++ b/interface/src/scripting/ScreenshareScriptingInterface.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "EntityScriptingInterface.h" @@ -37,14 +38,48 @@ ScreenshareScriptingInterface::ScreenshareScriptingInterface() { _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) { @@ -90,7 +125,8 @@ static const EntityTypes::EntityType LOCAL_SCREENSHARE_WEB_ENTITY_TYPE = EntityT 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. -static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0771f); +// 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"; @@ -124,7 +160,7 @@ void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZon // 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()) { + if (!screenshareExecutable.exists() || !(screenshareExecutable.isFile() || screenshareExecutable.isBundle())) { qDebug() << "Screenshare executable doesn't exist at" << SCREENSHARE_EXE_PATH; stopScreenshare(); emit screenshareError(); @@ -173,6 +209,7 @@ void ScreenshareScriptingInterface::stopScreenshare() { _projectAPIKey = ""; _sessionID = ""; _isPresenter = false; + _waitingForAuthorization = false; } // Called when the Metaverse returns the information necessary to start/view a screen share. @@ -241,7 +278,9 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR EntityItemProperties localScreenshareWebEntityProps; localScreenshareWebEntityProps.setType(LOCAL_SCREENSHARE_WEB_ENTITY_TYPE); localScreenshareWebEntityProps.setMaxFPS(LOCAL_SCREENSHARE_WEB_ENTITY_FPS); - localScreenshareWebEntityProps.setLocalPosition(LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION); + 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); diff --git a/interface/src/scripting/ScreenshareScriptingInterface.h b/interface/src/scripting/ScreenshareScriptingInterface.h index 22a65e169e..ec8f3cd619 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.h +++ b/interface/src/scripting/ScreenshareScriptingInterface.h @@ -18,9 +18,11 @@ #include #include +#include class ScreenshareScriptingInterface : public QObject, public Dependency { Q_OBJECT + Q_PROPERTY(float localWebEntityZOffset MEMBER _localWebEntityZOffset NOTIFY localWebEntityZOffsetChanged) public: ScreenshareScriptingInterface(); ~ScreenshareScriptingInterface(); @@ -32,8 +34,10 @@ 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); @@ -43,16 +47,16 @@ private: #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{ PathUtils::projectRootPath() + "/screenshare/screenshare-darwin-x64/hifi-screenshare.app" }; + 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/screenshare-other-os/hifi-screenshare" }; + 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() + "/hifi-screenshare/hifi-screenshare.app" }; + 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" }; @@ -63,6 +67,15 @@ private: 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{ "" }; @@ -71,6 +84,9 @@ private: QUuid _screenshareZoneID; QUuid _smartboardEntityID; bool _isPresenter{ false }; + + QUuid _lastAuthorizedZoneID; + bool _waitingForAuthorization{ false }; }; #endif // hifi_ScreenshareScriptingInterface_h diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index b87e3a3dbc..794d1c8eb3 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,157 +36,412 @@ 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} 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 refresh rate target per the current refreshRateMode and + * refreshRateRegime if in desktop mode; a higher rate if in VR mode. + * Read-only. + * @property {RefreshRateProfile} refreshRateMode - The current refresh rate profile. + * Read-only. + * @property {RefreshRateRegime} refreshRateRegime - The current refresh rate regime. + * Read-only. + * @property {UXMode} 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} 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. + * @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} 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. @@ -287,6 +546,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) @@ -352,37 +612,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. @@ -419,6 +677,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 @@ -433,13 +726,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 @@ -447,13 +733,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 @@ -461,6 +740,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 @@ -468,6 +775,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 @@ -727,7 +1041,6 @@ signals: */ void entityPacketsInKbpsChanged(); - /**jsdoc * Triggered when the value of the downloads property changes. * @function Stats.downloadsChanged @@ -778,11 +1091,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 @@ -883,6 +1195,20 @@ signals: */ void lodStatusChanged(); + /**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 @@ -926,39 +1252,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. @@ -981,6 +1286,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 @@ -1045,15 +1371,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(); @@ -1063,6 +1382,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 @@ -1086,250 +1412,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 @@ -1387,11 +1469,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. @@ -1401,11 +1483,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/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/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/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5502986cff..d456961b69 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -335,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; @@ -787,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); @@ -2064,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(); @@ -2342,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/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/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 319dfc922f..a3bb72fa51 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); @@ -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) { @@ -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()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 63d8183899..fbd941bd85 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -337,6 +337,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); @@ -699,6 +700,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/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index d54998e74f..1ddc972029 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; } @@ -118,6 +119,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; @@ -194,6 +196,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; } @@ -214,6 +217,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; @@ -260,6 +264,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 { @@ -471,9 +476,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 d6647e701e..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 }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index f17caf22db..b1b8875804 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -138,23 +138,28 @@ bool KeyboardMouseDevice::isWheelByTouchPad(QWheelEvent* event) { QPoint delta = event->angleDelta(); int deltaValueX = abs(delta.x()); int deltaValueY = abs(delta.y()); - const int MAX_WHEEL_DELTA_REPEAT = 20; const int COMMON_WHEEL_DELTA_VALUE = 120; - if (deltaValueX != 0) { - if (abs(_lastWheelDelta.x()) == deltaValueX) { - _wheelDeltaRepeatCount.setX(_wheelDeltaRepeatCount.x() + 1); - } else { - _wheelDeltaRepeatCount.setX(0); + // 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; } - return deltaValueX != COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT; - } - if (deltaValueY != 0) { - if (abs(_lastWheelDelta.y()) == deltaValueY) { - _wheelDeltaRepeatCount.setY(_wheelDeltaRepeatCount.y() + 1); - } else { - _wheelDeltaRepeatCount.setY(0); + 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 deltaValueY != COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT; } return false; } @@ -166,8 +171,9 @@ void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { QPoint delta = event->angleDelta(); float deltaX = (float)delta.x(); float deltaY = (float)delta.y(); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (deltaX > 0 ? deltaX : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (deltaX < 0 ? -deltaX : 0.0f); + 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); 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-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 2e94ccf0ea..7dc4d0dc13 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -30,14 +30,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.h b/libraries/networking/src/AddressManager.h index 7a67371ab5..8bdb777f96 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -68,6 +68,66 @@ const QString GET_PLACE = "/api/v1/places/%1"; * 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..ddc750664f 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -91,8 +91,8 @@ 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 * @@ -318,9 +318,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 +334,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 +354,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 75904d8122..805e5d3a09 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -277,6 +277,7 @@ enum class EntityVersion : PacketVersion { ShadowBiasAndDistance, TextEntityFonts, ScriptServerKinematicMotion, + ScreenshareZone, // Add new versions above here NUM_PACKET_TYPE, 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/ProceduralMaterialCache.cpp b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp index a97cb294b4..8a790cee46 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,17 @@ 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 opacityMap 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 - The mode defining which side of the geometry should be rendered. Values can be: *
    @@ -186,7 +191,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/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/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 4cf4a9fc42..7e3f556c2d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -731,6 +731,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); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 94381ede02..3891f60d92 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -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/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/screenshare/CMakeLists.txt b/screenshare/CMakeLists.txt index 1e675c09a4..d91d8c50b1 100644 --- a/screenshare/CMakeLists.txt +++ b/screenshare/CMakeLists.txt @@ -23,13 +23,6 @@ if (WIN32) set(EXECUTABLE_PATH "${SCREENSHARE_DESTINATION}/${SCREENSHARE_EXEC_NAME}") optional_win_executable_signing() -elseif (APPLE) - set(PACKAGED_SCREENSHARE_FOLDER "hifi-screenshare-darwin-x64/${SCREENSHARE_EXEC_NAME}") - install( - DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_SCREENSHARE_FOLDER}" - USE_SOURCE_PERMISSIONS - DESTINATION ${SCREENSHARE_INSTALL_DIR} - ) endif() # DO build the Screenshare Electron app when building the `ALL_BUILD` target. diff --git a/screenshare/src/screenshareApp.js b/screenshare/src/screenshareApp.js index 2abc151988..6a33b827d4 100644 --- a/screenshare/src/screenshareApp.js +++ b/screenshare/src/screenshareApp.js @@ -7,6 +7,8 @@ // 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) { @@ -188,6 +190,10 @@ function stopSharing() { // Callback to start publishing after we have setup the chromium stream function gotStream(stream) { + if (localStream) { + stopSharing(); + } + localStream = stream; startTokboxPublisher(localStream); @@ -206,6 +212,9 @@ function onAccessApproved(desktop_id) { console.log('Desktop Capture access rejected.'); return; } + + + document.getElementById('screenshare').style.visibility = "block"; desktopSharing = true; navigator.webkitGetUserMedia({ @@ -214,13 +223,13 @@ function onAccessApproved(desktop_id) { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: desktop_id, - minWidth: 1280, maxWidth: 1280, - minHeight: 720, - maxHeight: 720 + maxHeight: 720, + maxFrameRate: 7 } } }, gotStream, handleError); + remote.getCurrentWindow().minimize(); } @@ -248,11 +257,15 @@ var publisher; function startTokboxPublisher(stream) { publisher = document.createElement("div"); var publisherOptions = { - videoSource: stream.getVideoTracks()[0], + audioFallbackEnabled: false, audioSource: null, + fitMode: 'contain', + frameRate: 7, + height: 720, insertMode: 'append', - width: 1280, - height: 720 + publishAudio: false, + videoSource: stream.getVideoTracks()[0], + width: 1280 }; publisher = OT.initPublisher(publisher, publisherOptions, function(error){ 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/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 8b160e0630..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." }, diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index 828fd70e6f..ee4a11f91a 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", diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index b5a9b77bf4..64d44cc270 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -497,6 +497,12 @@ const GROUPS = [ options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, propertyID: "avatarPriority", }, + { + label: "Screen-share", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "screenshare", + } ] }, diff --git a/tools/animedit/.gitignore b/tools/animedit/.gitignore new file mode 100644 index 0000000000..86a9657caa --- /dev/null +++ b/tools/animedit/.gitignore @@ -0,0 +1 @@ +*.pro.user \ No newline at end of file diff --git a/tools/animedit/LICENCE b/tools/animedit/LICENCE new file mode 100644 index 0000000000..fb888fd1b7 --- /dev/null +++ b/tools/animedit/LICENCE @@ -0,0 +1,4 @@ +Copyright (c) 2019 High Fidelity, Inc. All rights reserved. + +Distributed under the Apache License, Version 2.0. +See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/tools/animedit/README.md b/tools/animedit/README.md new file mode 100644 index 0000000000..f314b4e5b7 --- /dev/null +++ b/tools/animedit/README.md @@ -0,0 +1,14 @@ +animedit +------------- +avatar-animation.json editor for High Fidelity. + +Use QtCreator to build. + +Known issues: + +* When switching node types, clear old types fields & set new fields to default values. +* Name field does not change when it has focus and leftHandPane selection is changed. + + + + diff --git a/tools/animedit/animedit.pro b/tools/animedit/animedit.pro new file mode 100644 index 0000000000..0e55ac5d1e --- /dev/null +++ b/tools/animedit/animedit.pro @@ -0,0 +1,35 @@ +QT += quick +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Refer to the documentation for the +# deprecated API to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + treeitem.cpp \ + treemodel.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Additional import path used to resolve QML modules just for Qt Quick Designer +QML_DESIGNER_IMPORT_PATH = + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + treeitem.h \ + treemodel.h diff --git a/tools/animedit/main.cpp b/tools/animedit/main.cpp new file mode 100644 index 0000000000..ae592b5c02 --- /dev/null +++ b/tools/animedit/main.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// 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 "treemodel.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + // expose model + TreeModel model; + engine.rootContext()->setContextProperty("theModel", &model); + + const QUrl url(QStringLiteral("qrc:/qml/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) { + // failure loading main.qml + QCoreApplication::exit(-1); + } + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/tools/animedit/qml.qrc b/tools/animedit/qml.qrc new file mode 100644 index 0000000000..25fab138aa --- /dev/null +++ b/tools/animedit/qml.qrc @@ -0,0 +1,26 @@ + + + qml/main.qml + qml/TreeDelegate.qml + qml/fields/BooleanField.qml + qml/fields/IdField.qml + qml/fields/JSONField.qml + qml/fields/NumberArrayField.qml + qml/fields/NumberField.qml + qml/fields/StringField.qml + qml/fields/TypeField.qml + qml/nodes/BlendDirectional.qml + qml/nodes/BlendLinear.qml + qml/nodes/BlendLinearMove.qml + qml/nodes/ClipData.qml + qml/nodes/DefaultPose.qml + qml/nodes/InverseKinematics.qml + qml/nodes/Manipulator.qml + qml/nodes/Overlay.qml + qml/nodes/PoleVector.qml + qml/nodes/RandomStateMachine.qml + qml/nodes/StateMachine.qml + qml/nodes/SplineIK.qml + qml/nodes/TwoBoneIK.qml + + diff --git a/tools/animedit/qml/TreeDelegate.qml b/tools/animedit/qml/TreeDelegate.qml new file mode 100644 index 0000000000..f4f21591c9 --- /dev/null +++ b/tools/animedit/qml/TreeDelegate.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 + +Item { + Text { + anchors.fill: parent + color: styleData.textColor + elide: styleData.elideMode + text: styleData.value + } +} diff --git a/tools/animedit/qml/fields/BooleanField.qml b/tools/animedit/qml/fields/BooleanField.qml new file mode 100644 index 0000000000..8ba2b93855 --- /dev/null +++ b/tools/animedit/qml/fields/BooleanField.qml @@ -0,0 +1,44 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property bool value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + CheckBox { + id: valueCheckBox + x: 100 + y: 5 + width: 200 + checked: value ? Qt.Checked : Qt.Unchecked + onCheckedChanged: { + setValue(checked); + } + } +} diff --git a/tools/animedit/qml/fields/IdField.qml b/tools/animedit/qml/fields/IdField.qml new file mode 100644 index 0000000000..b3ee5b6ac9 --- /dev/null +++ b/tools/animedit/qml/fields/IdField.qml @@ -0,0 +1,41 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + +Row { + id: row + x: 0 + y: 0 + height: 20 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string theValue + + function setValue(newValue) { + var ROLE_NAME = 0x0101; + theModel.setData(leftHandPane.currentIndex, newValue, ROLE_NAME); + } + + Text { + id: element + y: 5 + width: 100 + text: qsTr("Id:") + font.pixelSize: 12 + } + + TextField { + id: textField + x: 100 + width: 200 + text: theValue + onEditingFinished: { + setValue(text); + } + } +} diff --git a/tools/animedit/qml/fields/JSONField.qml b/tools/animedit/qml/fields/JSONField.qml new file mode 100644 index 0000000000..f7b16b82cd --- /dev/null +++ b/tools/animedit/qml/fields/JSONField.qml @@ -0,0 +1,52 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Column { + id: row + width: parent.width + height: parent.height + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property var value + property bool optional + + spacing: 5 + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: row.width + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextArea { + id: valueTextField + x: 0 + width: (keyText.width - 20) + height: (parent.height - 120) + wrapMode: TextEdit.NoWrap + + // TODO: validate + + text: JSON.stringify(value, null, 4) + + onEditingFinished: { + value = JSON.parse(text); + setValue(value); + } + } +} diff --git a/tools/animedit/qml/fields/NumberArrayField.qml b/tools/animedit/qml/fields/NumberArrayField.qml new file mode 100644 index 0000000000..dd393dc259 --- /dev/null +++ b/tools/animedit/qml/fields/NumberArrayField.qml @@ -0,0 +1,54 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property var value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextField { + id: valueTextField + x: 100 + width: 200 + + // first start with a regex for an array of numbers + // ^\[\s*(\d+)(\s*,\s*(\d+))*\]$|\[\s*\] + // then a regex for a floating point number + // \d+\.?\d* + // then substitue the second into the \d+ of the first, yeilding this monstrocity. + // ^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\] + + //validator: RegExpValidator { regExp: /^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\]/ } + + text: JSON.stringify(value) + onEditingFinished: { + value = JSON.parse(text); + setValue(value); + } + } +} diff --git a/tools/animedit/qml/fields/NumberField.qml b/tools/animedit/qml/fields/NumberField.qml new file mode 100644 index 0000000000..88c952d205 --- /dev/null +++ b/tools/animedit/qml/fields/NumberField.qml @@ -0,0 +1,45 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property real value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextField { + id: valueTextField + x: 100 + width: 200 + inputMethodHints: Qt.ImhFormattedNumbersOnly + text: value + onEditingFinished: { + value = text; + setValue(parseInt(text, 10)); + } + } +} diff --git a/tools/animedit/qml/fields/StringField.qml b/tools/animedit/qml/fields/StringField.qml new file mode 100644 index 0000000000..7ce5cd4c6c --- /dev/null +++ b/tools/animedit/qml/fields/StringField.qml @@ -0,0 +1,44 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property string value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextField { + id: valueTextField + x: 100 + width: 200 + text: value + onEditingFinished: { + value = text; + setValue(text); + } + } +} diff --git a/tools/animedit/qml/fields/TypeField.qml b/tools/animedit/qml/fields/TypeField.qml new file mode 100644 index 0000000000..0a9a711074 --- /dev/null +++ b/tools/animedit/qml/fields/TypeField.qml @@ -0,0 +1,67 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + +Row { + id: row + + function getTypes() { + return [ + "clip", + "blendDirectional", + "blendLinear", + "overlay", + "stateMachine", + "randomSwitchStateMachine", + "manipulator", + "inverseKinematics", + "defaultPose", + "twoBoneIK", + "splineIK", + "poleVectorConstraint" + ]; + } + + function indexFromString(str) { + var index = getTypes().indexOf(str); + return (index === -1) ? 0 : index; + } + + x: 0 + y: 0 + height: 20 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string theValue: "clip" + property int theIndex: indexFromString(theValue) + + function setValue(newValue) { + var ROLE_TYPE = 0x0102; + theModel.setData(leftHandPane.currentIndex, newValue, ROLE_TYPE); + rightHandPane.reset(); + } + + Text { + id: element + y: 5 + width: 100 + text: qsTr("Type:") + font.pixelSize: 12 + } + + ComboBox { + id: comboBox + x: 100 + width: 200 + model: getTypes() + currentIndex: theIndex + onActivated: { + setValue(currentText); + } + } +} diff --git a/tools/animedit/qml/main.qml b/tools/animedit/qml/main.qml new file mode 100644 index 0000000000..2d0c277132 --- /dev/null +++ b/tools/animedit/qml/main.qml @@ -0,0 +1,262 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "fields" +import "nodes" + +ApplicationWindow { + id: root + visible: true + width: 1600 + height: 1000 + color: "#ffffff" + opacity: 1 + title: qsTr("AnimEdit") + menuBar: appMenuBar + + SplitView { + id: splitView + anchors.fill: parent + + TreeView { + id: leftHandPane + width: 1000 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.top: parent.top + model: theModel + itemDelegate: TreeDelegate {} + + TableViewColumn { + role: "name" + title: "Name" + width: 500 + } + + TableViewColumn { + role: "type" + title: "Type" + } + + onClicked: { + rightHandPane.setIndex(index); + } + + function expandTreeView() { + function expandAll(index) { + leftHandPane.expand(index); + var children = theModel.getChildrenModelIndices(index); + for (var i = 0; i < children.length; i++) { + leftHandPane.expand(children[i]); + expandAll(children[i]); + } + } + + var index = theModel.index(0, 0); + expandAll(index); + } + } + + Rectangle { + id: rightHandPane + color: "#adadad" + height: parent.height + width: 500 + anchors.left: leftHandPane.right + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + function createCustomData(qml, index) { + var component = Qt.createComponent(qml, Component.PreferSynchronous); + if (component.status === Component.Ready) { + var obj = component.createObject(rightHandPaneColumn); + obj.setIndex(index); + } else if (component.status === Component.Error) { + console.log("ERROR: " + component.errorString()); + } else if (component.status === Component.Loading) { + console.log("ERROR: NOT READY"); + } + } + + function reset() { + setIndex(leftHandPane.currentIndex); + } + + function setIndex(index) { + var ROLE_NAME = 0x0101; + var ROLE_TYPE = 0x0102; + var ROLE_DATA = 0x0103; + + var idValue = theModel.data(index, ROLE_NAME); + var typeValue = theModel.data(index, ROLE_TYPE); + + idField.theValue = idValue; + typeField.theValue = typeValue; + + // delete previous custom data obj, if present + var orig = rightHandPaneColumn.children[2]; + if (orig) { + orig.destroy(); + } + + if (typeValue === "clip") { + createCustomData("nodes/ClipData.qml", index); + } else if (typeValue === "blendDirectional") { + createCustomData("nodes/BlendDirectional.qml", index); + } else if (typeValue === "blendLinear") { + createCustomData("nodes/BlendLinear.qml", index); + } else if (typeValue === "blendLinearMove") { + createCustomData("nodes/BlendLinearMove.qml", index); + } else if (typeValue === "overlay") { + createCustomData("nodes/Overlay.qml", index); + } else if (typeValue === "stateMachine") { + createCustomData("nodes/StateMachine.qml", index); + } else if (typeValue === "randomSwitchStateMachine") { + createCustomData("nodes/RandomStateMachine.qml", index); + } else if (typeValue === "inverseKinematics") { + createCustomData("nodes/InverseKinematics.qml", index); + } else if (typeValue === "twoBoneIK") { + createCustomData("nodes/TwoBoneIK.qml", index); + } else if (typeValue === "defaultPose") { + createCustomData("nodes/DefaultPose.qml", index); + } else if (typeValue === "manipulator") { + createCustomData("nodes/Manipulator.qml", index); + } else if (typeValue === "splineIK") { + createCustomData("nodes/SplineIK.qml", index); + } else if (typeValue === "poleVectorConstraint") { + createCustomData("nodes/PoleVector.qml", index); + } + } + + Column { + id: rightHandPaneColumn + + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + + spacing: 6 + + IdField { + id: idField + } + + TypeField { + id: typeField + } + } + } + } + + MenuBar { + id: appMenuBar + Menu { + title: "File" + MenuItem { + text: "Open..." + onTriggered: openFileDialog.open() + } + MenuItem { + text: "Save As..." + onTriggered: saveAsFileDialog.open() + } + } + Menu { + title: "Edit" + MenuItem { + text: "Add New Node as Child" + onTriggered: { + theModel.newNode(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Delete Selected" + onTriggered: { + theModel.deleteNode(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Insert New Node Above" + onTriggered: { + theModel.insertNodeAbove(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Copy Node Only" + onTriggered: { + theModel.copyNode(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Copy Node And Children" + onTriggered: { + theModel.copyNodeAndChildren(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Paste Over" + onTriggered: { + theModel.pasteOver(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Paste As Child" + onTriggered: { + theModel.pasteAsChild(leftHandPane.currentIndex); + } + } + } + } + + FileDialog { + id: openFileDialog + title: "Open an animation json file" + folder: shortcuts.home + nameFilters: ["Json files (*.json)"] + onAccepted: { + var path = openFileDialog.fileUrl.toString(); + // remove prefixed "file:///" + path = path.replace(/^(file:\/{3})/,""); + // unescape html codes like '%23' for '#' + var cleanPath = decodeURIComponent(path); + console.log("You chose: " + cleanPath); + theModel.loadFromFile(cleanPath); + leftHandPane.expandTreeView(); + } + onRejected: { + console.log("Canceled"); + } + } + + FileDialog { + id: saveAsFileDialog + title: "Save an animation json file" + folder: shortcuts.home + nameFilters: ["Json files (*.json)"] + selectExisting: false + onAccepted: { + var path = saveAsFileDialog.fileUrl.toString(); + // remove prefixed "file:///" + path = path.replace(/^(file:\/{3})/,""); + // unescape html codes like '%23' for '#' + var cleanPath = decodeURIComponent(path); + console.log("You chose: " + cleanPath); + theModel.saveToFile(cleanPath); + } + onRejected: { + console.log("Canceled"); + } + } +} diff --git a/tools/animedit/qml/nodes/BlendDirectional.qml b/tools/animedit/qml/nodes/BlendDirectional.qml new file mode 100644 index 0000000000..efdea74662 --- /dev/null +++ b/tools/animedit/qml/nodes/BlendDirectional.qml @@ -0,0 +1,129 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "alphaVar", "centerId", "upId", "downId", "leftId", "rightId", "upLeftId", "upRightId", "downLeftId", "downRightId"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberArrayField { + id: alphaField + key: "alpha" + value: [0, 0, 0] + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } + + StringField { + id: centerIdField + key: "centerId" + value: "" + optional: true + } + + StringField { + id: upIdField + key: "upId" + value: "" + optional: true + } + + StringField { + id: downIdField + key: "downId" + value: "" + optional: true + } + + StringField { + id: leftIdField + key: "leftId" + value: "" + optional: true + } + + StringField { + id: rightIdField + key: "rightId" + value: "" + optional: true + } + + StringField { + id: upLeftIdField + key: "upLeftId" + value: "" + optional: true + } + + StringField { + id: upRightIdField + key: "upRightId" + value: "" + optional: true + } + + StringField { + id: downLeftIdField + key: "downLeftId" + value: "" + optional: true + } + + StringField { + id: downRightIdField + key: "downRightId" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/BlendLinear.qml b/tools/animedit/qml/nodes/BlendLinear.qml new file mode 100644 index 0000000000..5ed15d9ea7 --- /dev/null +++ b/tools/animedit/qml/nodes/BlendLinear.qml @@ -0,0 +1,73 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "blendType", "alphaVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 0.0 + } + + StringField { + id: blendTypeField + key: "blendType" + value: "" + optional: true + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/BlendLinearMove.qml b/tools/animedit/qml/nodes/BlendLinearMove.qml new file mode 100644 index 0000000000..cde0c83fd7 --- /dev/null +++ b/tools/animedit/qml/nodes/BlendLinearMove.qml @@ -0,0 +1,85 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "desiredSpeed", "characteristicSpeeds", "alphaVar", "desiredSpeedVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 0.0 + } + + NumberField { + id: desiredSpeedField + key: "desiredSpeed" + value: 0.0 + } + + NumberArrayField { + id: characteristicSpeedsField + key: "characteristicSpeeds" + value: [] + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } + + StringField { + id: desiredSpeedVarField + key: "desiredSpeedVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/ClipData.qml b/tools/animedit/qml/nodes/ClipData.qml new file mode 100644 index 0000000000..9949064393 --- /dev/null +++ b/tools/animedit/qml/nodes/ClipData.qml @@ -0,0 +1,148 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["url", "startFrame", "endFrame", "timeScale", "loopFlag", "mirrorFlag", + "blendType", "baseURL", "baseFrame", "startFrameVar", "endFrameVar", + "timeScaleVar", "loopFlagVar", "mirrorFlagVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: urlField + key: "url" + value: "qrc:///avatar/animations/idle.fbx" + } + + NumberField { + id: startFrameField + key: "startFrame" + value: 0.0 + } + + NumberField { + id: endFrameField + key: "endFrame" + value: 0.0 + } + + NumberField { + id: timeScaleField + key: "timeScale" + value: 0.0 + } + + BooleanField { + id: loopFlagField + key: "loopFlag" + value: false + } + + BooleanField { + id: mirrorFlagField + key: "mirrorFlag" + value: false + optional: true + } + + StringField { + id: blendTypeField + key: "blendType" + value: "" + optional: true + } + + StringField { + id: baseURLField + key: "baseURL" + value: "" + optional: true + } + + NumberField { + id: baseFrameField + key: "baseFrame" + value: 0.0 + optional: true + } + + StringField { + id: startFrameVarField + key: "startFrameVar" + value: "" + optional: true + } + + StringField { + id: endFrameVarField + key: "endFrameVar" + value: "" + optional: true + } + + StringField { + id: timeScaleVarField + key: "timeScaleVar" + value: "" + optional: true + } + + StringField { + id: loopFlagVarField + key: "loopFlagVar" + value: "" + optional: true + } + + StringField { + id: mirrorFlagVarField + key: "mirrorFlagVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/DefaultPose.qml b/tools/animedit/qml/nodes/DefaultPose.qml new file mode 100644 index 0000000000..eb16dfb0c0 --- /dev/null +++ b/tools/animedit/qml/nodes/DefaultPose.qml @@ -0,0 +1,53 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = []; // This node has no data values + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } +} + diff --git a/tools/animedit/qml/nodes/InverseKinematics.qml b/tools/animedit/qml/nodes/InverseKinematics.qml new file mode 100644 index 0000000000..b401869c32 --- /dev/null +++ b/tools/animedit/qml/nodes/InverseKinematics.qml @@ -0,0 +1,71 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + width: parent.width + height: (parent.height - 50) + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["solutionSource", "solutionSourceVar", "targets"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: solutionSourceField + key: "solutionSource" + value: "relaxToUnderPoses" + } + + StringField { + id: solutionSourceVarField + key: "solutionSourceVar" + value: "solutionSource" + } + + JSONField { + id: statesField + key: "targets" + value: {} + } +} + diff --git a/tools/animedit/qml/nodes/Manipulator.qml b/tools/animedit/qml/nodes/Manipulator.qml new file mode 100644 index 0000000000..6a651ae73b --- /dev/null +++ b/tools/animedit/qml/nodes/Manipulator.qml @@ -0,0 +1,72 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + width: parent.width + height: parent.height + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "joints", "alphaVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 1.0 + } + + JSONField { + id: statesField + key: "joints" + value: {} + } + + StringField { + optional: true + id: alphaVarField + key: "alphaVar" + value: "" + } +} + diff --git a/tools/animedit/qml/nodes/Overlay.qml b/tools/animedit/qml/nodes/Overlay.qml new file mode 100644 index 0000000000..96c974d6e5 --- /dev/null +++ b/tools/animedit/qml/nodes/Overlay.qml @@ -0,0 +1,80 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["boneSet", "alpha", "boneSetVar", "alphaVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: boneSetField + key: "boneSet" + value: "fullBody" + optional: true + } + + NumberField { + id: alphaField + key: "alpha" + value: 0.0 + } + + StringField { + id: boneSetVarField + key: "boneSetVar" + value: "" + optional: true + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/PoleVector.qml b/tools/animedit/qml/nodes/PoleVector.qml new file mode 100644 index 0000000000..bbf5e96a2f --- /dev/null +++ b/tools/animedit/qml/nodes/PoleVector.qml @@ -0,0 +1,96 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["referenceVector", "enabled", "baseJointName", "midJointName", "tipJointName", + "enabledVar", "poleVectorVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberArrayField { + id: referenceVectorField + key: "referenceVector" + value: [1, 0, 0] + } + + BooleanField { + id: enabledField + key: "enabled" + value: false + } + + StringField { + id: baseJointNameField + key: "baseJointName" + value: "" + } + + StringField { + id: midJointNameField + key: "midJointName" + value: "" + } + + StringField { + id: tipJointNameField + key: "tipJointName" + value: "" + } + + StringField { + id: enabledVarField + key: "enabledVar" + value: "" + } + + StringField { + id: poleVectorVarField + key: "poleVectorVar" + value: "" + } +} + diff --git a/tools/animedit/qml/nodes/RandomStateMachine.qml b/tools/animedit/qml/nodes/RandomStateMachine.qml new file mode 100644 index 0000000000..357fc4b2bf --- /dev/null +++ b/tools/animedit/qml/nodes/RandomStateMachine.qml @@ -0,0 +1,109 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + width: parent.width + height: (parent.height - 150) + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["currentState", "randomSwitchTimeMin", "randomSwitchTimeMax", "triggerRandomSwitch", + "triggerTimeMin", "triggerTimeMax", "transitionVar", "states"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: currentStateField + key: "currentState" + value: "" + } + + NumberField { + id: randomSwitchTimeMinField + key: "randomSwitchTimeMin" + value: 1.0 + optional: true + } + + NumberField { + id: randomSwitchTimeMaxField + key: "randomSwitchTimeMax" + value: 10.0 + optional: true + } + + StringField { + id: triggerRandomSwitchField + key: "triggerRandomSwitch" + value: "" + optional: true + } + + NumberField { + id: triggerTimeMinField + key: "triggerTimeMin" + value: 1.0 + optional: true + } + + NumberField { + id: triggerTimeMaxField + key: "triggerTimeMax" + value: 10.0 + optional: true + } + + StringField { + id: transitionVarField + key: "transitionVar" + value: "" + optional: true + } + + JSONField { + id: statesField + key: "states" + value: {} + } +} + diff --git a/tools/animedit/qml/nodes/SplineIK.qml b/tools/animedit/qml/nodes/SplineIK.qml new file mode 100644 index 0000000000..df1c8931a3 --- /dev/null +++ b/tools/animedit/qml/nodes/SplineIK.qml @@ -0,0 +1,139 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "enabled", "interpDuration", "baseJointName", "midJointName", "tipJointName", + "basePositionVar", "baseRotationVar", "midPositionVar", "midRotationVar", "tipPositionVar", "tipRotationVar", + "alphaVar", "enabledVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 1.0 + } + + BooleanField { + id: enabledField + key: "enabled" + value: false + } + + NumberField { + id: interpDurationField + key: "interpDuration" + value: 15.0 + } + + StringField { + id: baseJointNameField + key: "baseJointName" + value: "" + } + + StringField { + id: midJointNameField + key: "midJointName" + value: "" + } + + StringField { + id: tipJointNameField + key: "tipJointName" + value: "" + } + + StringField { + id: basePositionVarField + key: "basePositionVar" + value: "" + } + + StringField { + id: baseRotationVarField + key: "baseRotationVar" + value: "" + } + + StringField { + id: midPositionVarField + key: "midPositionVar" + value: "" + } + + StringField { + id: midRotationVarField + key: "midRotationVar" + value: "" + } + + StringField { + id: tipPositionVarField + key: "tipPositionVar" + value: "" + } + + StringField { + id: tipRotationVarField + key: "tipRotationVar" + value: "" + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + } + + StringField { + id: enabledVarField + key: "enabledVar" + value: "" + } +} + diff --git a/tools/animedit/qml/nodes/StateMachine.qml b/tools/animedit/qml/nodes/StateMachine.qml new file mode 100644 index 0000000000..aec4c30d27 --- /dev/null +++ b/tools/animedit/qml/nodes/StateMachine.qml @@ -0,0 +1,65 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: parent.height + width: parent.width + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["currentState", "states"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: currentStateField + key: "currentState" + value: "default" + } + + JSONField { + id: statesField + key: "states" + value: {} + } +} + diff --git a/tools/animedit/qml/nodes/TwoBoneIK.qml b/tools/animedit/qml/nodes/TwoBoneIK.qml new file mode 100644 index 0000000000..6bdae5c1be --- /dev/null +++ b/tools/animedit/qml/nodes/TwoBoneIK.qml @@ -0,0 +1,120 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "enabled", "interpDuration", "baseJointName", "midJointName", "tipJointName", "midHingeAxis", + "alphaVar", "enabledVar", "endEffectorRotationVarVar", "endEffectorPositionVarVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 1.0 + } + + BooleanField { + id: enabledField + key: "enabled" + value: false + } + + NumberField { + id: interpDurationField + key: "interpDuration" + value: 15.0 + } + + StringField { + id: baseJointNameField + key: "baseJointName" + value: "" + } + + StringField { + id: midJointNameField + key: "midJointName" + value: "" + } + + StringField { + id: tipJointNameField + key: "tipJointName" + value: "" + } + + NumberArrayField { + id: midHingeAxisField + key: "midHingeAxis" + value: [1, 0, 0] + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + } + + StringField { + id: enabledVarField + key: "enabledVar" + value: "" + } + + StringField { + id: endEffectorRotationVarVarField + key: "endEffectorRotationVarVar" + value: "" + } + + StringField { + id: endEffectorPositionVarVarField + key: "endEffectorPositionVarVar" + value: "" + } +} + diff --git a/tools/animedit/treeitem.cpp b/tools/animedit/treeitem.cpp new file mode 100644 index 0000000000..172f78a280 --- /dev/null +++ b/tools/animedit/treeitem.cpp @@ -0,0 +1,91 @@ +// +// TreeItem +// +// Created by Anthony Thibault on 6/5/2019 +// 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 "treeitem.h" +#include +#include + +TreeItem::TreeItem(const QList& data) { + m_parentItem = nullptr; + m_itemData = data; +} + +TreeItem::~TreeItem() { + qDeleteAll(m_childItems); +} + +void TreeItem::appendChild(TreeItem *item) { + item->m_parentItem = this; + m_childItems.append(item); +} + +int TreeItem::findChild(TreeItem* child) { + for (int i = 0; i < m_childItems.size(); i++) { + if (m_childItems[i] == child) { + return i; + } + } + return -1; +} + +void TreeItem::removeChild(int index) { + // TODO: delete TreeItem + m_childItems.removeAt(index); +} + +void TreeItem::insertChild(int index, TreeItem* child) { + child->m_parentItem = this; + m_childItems.insert(index, child); +} + +TreeItem* TreeItem::child(int row) { + return m_childItems.value(row); +} + +int TreeItem::childCount() const { + return m_childItems.count(); +} + +int TreeItem::columnCount() const { + return m_itemData.count(); +} + +QVariant TreeItem::data(int column) const { + return m_itemData.value(column); +} + +bool TreeItem::setData(int column, const QVariant& value) { + m_itemData[column] = QVariant(value); + return true; +} + +TreeItem* TreeItem::parentItem() { + return m_parentItem; +} + +int TreeItem::row() const { + if (m_parentItem) { + return m_parentItem->m_childItems.indexOf(const_cast(this)); + } + + return 0; +} + +TreeItem* TreeItem::cloneNode() const { + return new TreeItem(m_itemData); +} + +TreeItem* TreeItem::cloneNodeAndChildren() const { + TreeItem* newNode = new TreeItem(m_itemData); + for (int i = 0; i < m_childItems.size(); ++i) { + newNode->appendChild(m_childItems[i]->cloneNodeAndChildren()); + } + return newNode; +} diff --git a/tools/animedit/treeitem.h b/tools/animedit/treeitem.h new file mode 100644 index 0000000000..bc7afb455a --- /dev/null +++ b/tools/animedit/treeitem.h @@ -0,0 +1,45 @@ +// +// TreeItem +// +// Created by Anthony Thibault on 6/5/2019 +// 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_TreeItem_h +#define hifi_TreeItem_h + +#include +#include + +class TreeItem +{ +public: + explicit TreeItem(const QList& data); + ~TreeItem(); + + void appendChild(TreeItem* child); + int findChild(TreeItem* child); + void removeChild(int index); + void insertChild(int index, TreeItem* child); + + TreeItem* child(int row); + int childCount() const; + int columnCount() const; + QVariant data(int column) const; + bool setData(int column, const QVariant& value); + int row() const; + TreeItem* parentItem(); + + TreeItem* cloneNode() const; + TreeItem* cloneNodeAndChildren() const; + +private: + QList m_childItems; + QList m_itemData; + TreeItem* m_parentItem; +}; + +#endif diff --git a/tools/animedit/treemodel.cpp b/tools/animedit/treemodel.cpp new file mode 100644 index 0000000000..28c6101f78 --- /dev/null +++ b/tools/animedit/treemodel.cpp @@ -0,0 +1,418 @@ +// +// TreeModel +// +// Created by Anthony Thibault on 6/5/2019 +// 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 "treeitem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "treemodel.h" + +static TreeItem* newBlankTreeItem() { + QList columnData; + columnData << "newNode"; + columnData << "clip"; + columnData << QJsonObject(); // blank + return new TreeItem(columnData); +} + +TreeModel::TreeModel(QObject* parent) : QAbstractItemModel(parent) { + _roleNameMapping[TreeModelRoleName] = "name"; + _roleNameMapping[TreeModelRoleType] = "type"; + _roleNameMapping[TreeModelRoleData] = "data"; + + QList rootData; + rootData << "Name" << "Type" << "Data"; + _rootItem = new TreeItem(rootData); + _clipboard = nullptr; +} + +TreeModel::~TreeModel() { + delete _rootItem; +} + +QHash TreeModel::roleNames() const { + return _roleNameMapping; +} + +Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const { + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + return QAbstractItemModel::flags(index); +} + +QVariant TreeModel::data(const QModelIndex& index, int role) const { + TreeItem* item = getItem(index); + return item->data(role - Qt::UserRole - 1); +} + +QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + return _rootItem->data(section); + } + + return QVariant(); +} + +QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const { + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + TreeItem *parentItem; + + if (!parent.isValid()) { + parentItem = _rootItem; + } else { + parentItem = static_cast(parent.internalPointer()); + } + + TreeItem *childItem = parentItem->child(row); + if (childItem) { + return createIndex(row, column, childItem); + } else { + return QModelIndex(); + } +} + +QModelIndex TreeModel::parent(const QModelIndex& index) const { + if (!index.isValid()) { + return QModelIndex(); + } + + TreeItem *childItem = static_cast(index.internalPointer()); + TreeItem *parentItem = childItem->parentItem(); + + if (parentItem == _rootItem) { + return QModelIndex(); + } + + return createIndex(parentItem->row(), 0, parentItem); +} + +int TreeModel::rowCount(const QModelIndex& parent) const { + TreeItem* parentItem = getItem(parent); + return parentItem->childCount(); +} + +int TreeModel::columnCount(const QModelIndex& parent) const { + return _rootItem->columnCount(); +} + +bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { + TreeItem* item = getItem(index); + + bool returnValue = item->setData(role - Qt::UserRole - 1, value); + + emit dataChanged(index, index); + + return returnValue; +} + +bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) { + return false; +} + +bool TreeModel::insertColumns(int position, int columns, const QModelIndex& parent) { + return false; +} + +bool TreeModel::removeColumns(int position, int columns, const QModelIndex& parent) { + return false; +} + +bool TreeModel::insertRows(int position, int rows, const QModelIndex& parent) { + return false; +} + +bool TreeModel::removeRows(int position, int rows, const QModelIndex& parent) { + return false; +} + +void TreeModel::loadFromFile(const QString& filename) { + + beginResetModel(); + + QFile file(filename); + if (!file.exists()) { + qCritical() << "TreeModel::loadFromFile, failed to open file" << filename; + } else if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "TreeModel::loadFromFile, failed to open file" << filename; + } else { + qDebug() << "TreeModel::loadFromFile, success opening file" << filename; + QByteArray contents = file.readAll(); + QJsonParseError error; + auto doc = QJsonDocument::fromJson(contents, &error); + if (error.error != QJsonParseError::NoError) { + qCritical() << "TreeModel::loadFromFile, failed to parse json, error" << error.errorString(); + } else { + QJsonObject obj = doc.object(); + + // version + QJsonValue versionVal = obj.value("version"); + if (!versionVal.isString()) { + qCritical() << "TreeModel::loadFromFile, bad string \"version\""; + return; + } + QString version = versionVal.toString(); + + // check version + if (version != "1.0" && version != "1.1") { + qCritical() << "TreeModel::loadFromFile, bad version number" << version << "expected \"1.0\" or \"1.1\""; + return; + } + + // root + QJsonValue rootVal = obj.value("root"); + if (!rootVal.isObject()) { + qCritical() << "TreeModel::loadFromFile, bad object \"root\""; + return; + } + + QList columnData; + columnData << QString("root"); + columnData << QString("root"); + columnData << QString("root"); + + // create root item + _rootItem = new TreeItem(columnData); + _rootItem->appendChild(loadNode(rootVal.toObject())); + } + } + + endResetModel(); +} + +void TreeModel::saveToFile(const QString& filename) { + + QJsonObject obj; + obj.insert("version", "1.1"); + + const int FIRST_CHILD = 0; + obj.insert("root", jsonFromItem(_rootItem->child(FIRST_CHILD))); + + QJsonDocument doc(obj); + QByteArray byteArray = doc.toJson(QJsonDocument::Indented); + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + qCritical() << "TreeModel::safeToFile, failed to open file" << filename; + } else { + file.write(byteArray); + } +} + +void TreeModel::newNode(const QModelIndex& parent) { + TreeItem* parentItem = _rootItem; + if (parent.isValid()) { + parentItem = static_cast(parent.internalPointer()); + } + + beginInsertRows(parent, parentItem->childCount(), parentItem->childCount()); + TreeItem* childItem = newBlankTreeItem(); + parentItem->appendChild(childItem); + + endInsertRows(); +} + +void TreeModel::deleteNode(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + TreeItem* parentItem = item->parentItem(); + int childNum = parentItem->findChild(item); + + if (childNum >= 0) { + beginRemoveRows(createIndex(0, 0, reinterpret_cast(parentItem)), childNum, childNum); + parentItem->removeChild(childNum); + endRemoveRows(); + } +} + +void TreeModel::insertNodeAbove(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + TreeItem* parentItem = item->parentItem(); + int childNum = parentItem->findChild(item); + + if (childNum >= 0) { + TreeItem* newItem = newBlankTreeItem(); + QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast(parentItem)); + + // remove item + beginRemoveRows(parentIndex, childNum, childNum); + parentItem->removeChild(childNum); + endRemoveRows(); + + // append item to newItem + newItem->appendChild(item); + + // then insert newItem + beginInsertRows(parentIndex, childNum, childNum); + parentItem->insertChild(childNum, newItem); + endInsertRows(); + } +} + +QVariantList TreeModel::getChildrenModelIndices(const QModelIndex& index) { + QVariantList indices; + + TreeItem* parent = static_cast(index.internalPointer()); + for (int i = 0; i < parent->childCount(); ++i) { + TreeItem* child = parent->child(i); + indices.push_back(createIndex(i, 0, reinterpret_cast(child))); + } + return indices; +} + +void TreeModel::copyNode(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + // TODO: delete previous clipboard + _clipboard = item->cloneNode(); +} + +void TreeModel::copyNodeAndChildren(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + // TODO: delete previous clipboard + _clipboard = item->cloneNodeAndChildren(); +} + +void TreeModel::pasteOver(const QModelIndex& index) { + if (_clipboard) { + TreeItem* item = static_cast(index.internalPointer()); + TreeItem* parentItem = item->parentItem(); + int childNum = parentItem->findChild(item); + + if (childNum >= 0) { + QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast(parentItem)); + + // remove item + beginRemoveRows(parentIndex, childNum, childNum); + parentItem->removeChild(childNum); + endRemoveRows(); + + // then insert clone of _clipboard + beginInsertRows(parentIndex, childNum, childNum); + parentItem->insertChild(childNum, _clipboard->cloneNodeAndChildren()); + endInsertRows(); + } + } +} + +void TreeModel::pasteAsChild(const QModelIndex& index) { + if (_clipboard) { + TreeItem* parentItem = _rootItem; + if (index.isValid()) { + parentItem = static_cast(index.internalPointer()); + } + + beginInsertRows(index, parentItem->childCount(), parentItem->childCount()); + parentItem->appendChild(_clipboard->cloneNodeAndChildren()); + + endInsertRows(); + } +} + +TreeItem* TreeModel::loadNode(const QJsonObject& jsonObj) { + + // id + auto idVal = jsonObj.value("id"); + if (!idVal.isString()) { + qCritical() << "loadNode, bad string \"id\""; + return nullptr; + } + QString id = idVal.toString(); + + // type + auto typeVal = jsonObj.value("type"); + if (!typeVal.isString()) { + qCritical() << "loadNode, bad object \"type\", id =" << id; + return nullptr; + } + QString typeStr = typeVal.toString(); + + // data + auto dataValue = jsonObj.value("data"); + if (!dataValue.isObject()) { + qCritical() << "AnimNodeLoader, bad string \"data\", id =" << id; + return nullptr; + } + + QList columnData; + columnData << id; + columnData << typeStr; + columnData << dataValue.toVariant(); + + // create node + TreeItem* node = new TreeItem(columnData); + + // children + auto childrenValue = jsonObj.value("children"); + if (!childrenValue.isArray()) { + qCritical() << "AnimNodeLoader, bad array \"children\", id =" << id; + return nullptr; + } + auto childrenArray = childrenValue.toArray(); + for (const auto& childValue : childrenArray) { + if (!childValue.isObject()) { + qCritical() << "AnimNodeLoader, bad object in \"children\", id =" << id; + return nullptr; + } + TreeItem* child = loadNode(childValue.toObject()); + if (child) { + node->appendChild(child); + } else { + return nullptr; + } + } + + return node; +} + +TreeItem* TreeModel::getItem(const QModelIndex& index) const { + if (index.isValid()) { + TreeItem *item = static_cast(index.internalPointer()); + if (item) { + return item; + } + } + return _rootItem; +} + +QJsonObject TreeModel::jsonFromItem(TreeItem* treeItem) { + QJsonObject obj; + + const int ID_COLUMN = 0; + obj.insert("id", treeItem->data(ID_COLUMN).toJsonValue()); + + const int TYPE_COLUMN = 1; + obj.insert("type", treeItem->data(TYPE_COLUMN).toJsonValue()); + + const int DATA_COLUMN = 2; + QVariant data = treeItem->data(DATA_COLUMN); + if (data.canConvert()) { + obj.insert("data", data.value().toVariant().toJsonValue()); + } else { + obj.insert("data", data.toJsonValue()); + } + + QJsonArray children; + for (int i = 0; i < treeItem->childCount(); i++) { + children.push_back(jsonFromItem(treeItem->child(i))); + } + obj.insert("children", children); + + return obj; +} diff --git a/tools/animedit/treemodel.h b/tools/animedit/treemodel.h new file mode 100644 index 0000000000..3e18f24395 --- /dev/null +++ b/tools/animedit/treemodel.h @@ -0,0 +1,79 @@ +// +// TreeModel +// +// Created by Anthony Thibault on 6/5/2019 +// 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_TreeModel_h +#define hifi_TreeModel_h + +#include +#include +#include +#include +#include +#include + +class TreeItem; + +class TreeModel : public QAbstractItemModel { + Q_OBJECT + +public: + enum TreeModelRoles + { + TreeModelRoleName = Qt::UserRole + 1, + TreeModelRoleType, + TreeModelRoleData + }; + + explicit TreeModel(QObject* parent = nullptr); + ~TreeModel() override; + + // QAbstractItemModel interface + QHash roleNames() const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + // read methods + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = TreeModelRoleName) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + // write methods + bool setData(const QModelIndex& index, const QVariant& value, int role = TreeModelRoleName) override; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role = TreeModelRoleName) override; + bool insertColumns(int position, int columns, const QModelIndex& parent = QModelIndex()) override; + bool removeColumns(int position, int columns, const QModelIndex& parent = QModelIndex()) override; + bool insertRows(int position, int rows, const QModelIndex& parent = QModelIndex()) override; + bool removeRows(int position, int rows, const QModelIndex& parent = QModelIndex()) override; + + // invokable from qml + Q_INVOKABLE void loadFromFile(const QString& filename); + Q_INVOKABLE void saveToFile(const QString& filename); + Q_INVOKABLE void newNode(const QModelIndex& parent); + Q_INVOKABLE void deleteNode(const QModelIndex& index); + Q_INVOKABLE void insertNodeAbove(const QModelIndex& index); + Q_INVOKABLE QVariantList getChildrenModelIndices(const QModelIndex& index); + Q_INVOKABLE void copyNode(const QModelIndex& index); + Q_INVOKABLE void copyNodeAndChildren(const QModelIndex& index); + Q_INVOKABLE void pasteOver(const QModelIndex& index); + Q_INVOKABLE void pasteAsChild(const QModelIndex& index); + +private: + TreeItem* loadNode(const QJsonObject& jsonObj); + TreeItem* getItem(const QModelIndex& index) const; + QJsonObject jsonFromItem(TreeItem* treeItem); + + TreeItem* _rootItem; + QHash _roleNameMapping; + TreeItem* _clipboard; +}; + +#endif diff --git a/tools/ci-scripts/codesign.pfx.gpg b/tools/ci-scripts/codesign.pfx.gpg new file mode 100644 index 0000000000..826465316f Binary files /dev/null and b/tools/ci-scripts/codesign.pfx.gpg differ diff --git a/tools/ci-scripts/linux-gha/Dockerfile b/tools/ci-scripts/linux-gha/Dockerfile new file mode 100644 index 0000000000..6809ec0460 --- /dev/null +++ b/tools/ci-scripts/linux-gha/Dockerfile @@ -0,0 +1,32 @@ +FROM simonwalton1/hifi_base_ubuntu:1.1 +MAINTAINER DevOps Team (devops@highfidelity.io) + +EXPOSE 40100 40101 40102 +EXPOSE 40100/udp 40101/udp 40102/udp +EXPOSE 48000/udp 48001/udp 48002/udp 48003/udp 48004/udp 48005/udp 48006/udp + +RUN mkdir -p /etc/hifi/server/plugins /etc/hifi/server/resources /etc/hifi/server/imageformats/ && \ + ln -s /usr/local/Qt5.12.3/5.12.3/gcc_64/plugins/imageformats/* /etc/hifi/server/imageformats/ +COPY ./assignment-client /etc/hifi/server/ +#COPY ./oven /etc/hifi/server/ +COPY ./domain-server /etc/hifi/server/ +COPY ./plugins/hifiCodec/libhifiCodec.so /etc/hifi/server/plugins/ +RUN true +COPY ./plugins/pcmCodec/libpcmCodec.so /etc/hifi/server/plugins/ +# Dummy statement +RUN true +COPY ./*.so /lib/ +RUN ln -sf /lib/libquazip5.so /lib/libquazip5.so.1 +COPY ./domain-server/resources/ /etc/hifi/server/resources/ +RUN true +COPY ./hifi.conf /etc/supervisor/conf.d/hifi.conf +RUN for fn in /usr/local/Qt5.12.3/5.12.3/gcc_64/plugins/imageformats/*.so; do \ + if [ ! -x $fn ]; then ln -s $fn /etc/hifi/server/imageformats; fi; done +RUN chmod +x /etc/hifi/server/domain-server +RUN chmod +x /etc/hifi/server/assignment-client + +# Ensure `domain-server` and `assignment-client` execute. +RUN /etc/hifi/server/domain-server --version > /etc/hifi/server/version && \ + /etc/hifi/server/assignment-client --version >> /etc/hifi/server/version + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/hifi.conf"] diff --git a/tools/ci-scripts/linux-gha/Dockerfile_linuxbase b/tools/ci-scripts/linux-gha/Dockerfile_linuxbase new file mode 100644 index 0000000000..be38fef91a --- /dev/null +++ b/tools/ci-scripts/linux-gha/Dockerfile_linuxbase @@ -0,0 +1,24 @@ +# Docker file for hifi_base_ubuntu:1.1 +FROM ubuntu:18.04 +MAINTAINER DevOps Team (devops@highfidelity.io) + +EXPOSE 40100 40101 40102 +EXPOSE 40100/udp 40101/udp 40102/udp +EXPOSE 48000/udp 48001/udp 48002/udp 48003/udp 48004/udp 48005/udp 48006/udp + +RUN apt-get update && apt-get install -y software-properties-common apt-utils curl +RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 15FF1AAE && add-apt-repository "deb http://debian.highfidelity.com stable main" +RUN echo UTC >/etc/timezone +# Installing via dependency causes interactive hang: +RUN apt-get -y install tzdata +RUN curl http://archive.ubuntu.com/ubuntu/pool/main/b/bison/libbison-dev_3.0.4.dfsg-1build1_amd64.deb >/tmp/libbison-dev.deb +RUN dpkg -i /tmp/libbison-dev.deb && rm /tmp/libbison-dev.deb +RUN apt-get update && apt-get -y --allow-unauthenticated install libglib2.0-0 libgl1-mesa-dev gdb python-pip build-essential \ + openssl libssl-dev libssl1.0.0 unzip flex bison gperf perl libsqlite3-dev \ + libfontconfig1-dev libicu-dev libfreetype6 libssl-dev libpng-dev libjpeg-dev \ + python libx11-dev libxext-dev libtbb2 hifiqt5.12.3 \ + ntp unzip libwww-perl libdatetime-perl make pkg-config libnss3 libxi6 \ + libxcursor1 libxcomposite1 libasound2 libxtst6 libxslt1.1 supervisor +# Additional packages for v1.1: +RUN apt-get -y --allow-unauthenticated install mesa-common-dev libegl1 libglvnd-dev \ + libdouble-conversion1 libpulse0 diff --git a/tools/ci-scripts/linux-gha/hifi.conf b/tools/ci-scripts/linux-gha/hifi.conf new file mode 100644 index 0000000000..32066617d6 --- /dev/null +++ b/tools/ci-scripts/linux-gha/hifi.conf @@ -0,0 +1,60 @@ +[supervisord] +user=root +nodaemon=true + +[program:domain-server] +command=/etc/hifi/server/domain-server +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:audio-mixer] +command=/etc/hifi/server/assignment-client -t 0 -a localhost -p 48000 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:avatar-mixer] +command=/etc/hifi/server/assignment-client -t 1 -a localhost -p 48001 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:entities-server] +command=/etc/hifi/server/assignment-client -t 6 -a localhost -p 48006 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:asset-server] +command=/etc/hifi/server/assignment-client -t 3 -a localhost -p 48003 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:entity-script-server] +command=/etc/hifi/server/assignment-client -t 5 -a localhost -p 48005 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:messages-mixer] +command=/etc/hifi/server/assignment-client -t 4 -a localhost -p 48004 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + +[program:scripted-agent] +command=/etc/hifi/server/assignment-client -t 2 -a localhost --max 100 +autorestart=unexpected +directory=/etc/hifi/server +stderr_logfile=/var/log/hifi-err.log +stdout_logfile=/var/log/hifi-out.log + diff --git a/tools/ci-scripts/postbuild.py b/tools/ci-scripts/postbuild.py index f229855b21..99493d9856 100644 --- a/tools/ci-scripts/postbuild.py +++ b/tools/ci-scripts/postbuild.py @@ -31,14 +31,14 @@ elif sys.platform == "darwin": def computeArchiveName(prefix): RELEASE_TYPE = os.getenv("RELEASE_TYPE", "DEV") RELEASE_NUMBER = os.getenv("RELEASE_NUMBER", "") - GIT_PR_COMMIT_SHORT = os.getenv("SHA7", "") - if GIT_PR_COMMIT_SHORT == '': - GIT_PR_COMMIT_SHORT = os.getenv("GIT_PR_COMMIT_SHORT", "") + GIT_COMMIT_SHORT = os.getenv("SHA7", "") + if GIT_COMMIT_SHORT == '': + GIT_COMMIT_SHORT = os.getenv("GIT_COMMIT_SHORT", "") if RELEASE_TYPE == "PRODUCTION": - BUILD_VERSION = "{}-{}".format(RELEASE_NUMBER, GIT_PR_COMMIT_SHORT) + BUILD_VERSION = "{}-{}".format(RELEASE_NUMBER, GIT_COMMIT_SHORT) elif RELEASE_TYPE == "PR": - BUILD_VERSION = "PR{}-{}".format(RELEASE_NUMBER, GIT_PR_COMMIT_SHORT) + BUILD_VERSION = "PR{}-{}".format(RELEASE_NUMBER, GIT_COMMIT_SHORT) else: BUILD_VERSION = "dev" diff --git a/tools/ci-scripts/pr_comment.py b/tools/ci-scripts/pr_comment.py new file mode 100644 index 0000000000..a231c86708 --- /dev/null +++ b/tools/ci-scripts/pr_comment.py @@ -0,0 +1,24 @@ +# Post build script +import os +import sys +import json +import glob +from github import Github + + +def main(): + bucket_name = os.environ['BUCKET_NAME'] + upload_prefix = os.environ['UPLOAD_PREFIX'] + context = json.loads(os.environ['GITHUB_CONTEXT']) + baseUrl = 'https://{}.s3.amazonaws.com/{}/'.format(bucket_name, upload_prefix) + g = Github(os.environ['GITHUB_TOKEN']) + repo = g.get_repo(context['repository']) + pr = repo.get_pull(context['event']['number']) + + path = os.path.join(os.getcwd(), os.environ['ARTIFACT_PATTERN']) + files = glob.glob(path, recursive=False) + for archiveFile in files: + filePath, fileName = os.path.split(archiveFile) + pr.create_issue_comment("Build artifact uploaded as [{}]({}{})".format(fileName, baseUrl, fileName)) + +main() diff --git a/tools/ci-scripts/upload.py b/tools/ci-scripts/upload.py index 5674ce9e05..f43fbfd574 100644 --- a/tools/ci-scripts/upload.py +++ b/tools/ci-scripts/upload.py @@ -10,18 +10,15 @@ from github import Github def main(): bucket_name = os.environ['BUCKET_NAME'] upload_prefix = os.environ['UPLOAD_PREFIX'] - context = json.loads(os.environ['GITHUB_CONTEXT']) - baseUrl = 'https://{}.s3.amazonaws.com/{}/'.format(bucket_name, upload_prefix) + release_number = os.environ['RELEASE_NUMBER'] + full_prefix = upload_prefix + '/' + release_number[0:-2] + '/' + release_number S3 = boto3.client('s3') - g = Github(os.environ['GITHUB_TOKEN']) - repo = g.get_repo(context['repository']) - pr = repo.get_pull(context['event']['number']) - path = os.path.join(os.getcwd(), os.environ['ARTIFACT_PATTERN']) files = glob.glob(path, recursive=False) for archiveFile in files: filePath, fileName = os.path.split(archiveFile) - pr.create_issue_comment("Build artifact uploaded as [{}]({}{})".format(fileName, baseUrl, fileName)) - S3.upload_file(os.path.join(filePath, fileName), bucket_name, upload_prefix + '/' + fileName) + S3.upload_file(os.path.join(filePath, fileName), bucket_name, full_prefix + '/' + fileName) + print("Uploaded Artifact to S3: https://{}.s3-us-west-2.amazonaws.com/{}/{}".format(bucket_name, full_prefix, fileName)) + print("Finished") main() diff --git a/tools/gpu-frame-player/CMakeLists.txt b/tools/gpu-frame-player/CMakeLists.txt index a6a91ee6ef..141917000f 100644 --- a/tools/gpu-frame-player/CMakeLists.txt +++ b/tools/gpu-frame-player/CMakeLists.txt @@ -25,3 +25,17 @@ target_opengl() #target_vulkan() package_libraries_for_deployment() + +if (BUILD_GPU_FRAME_PLAYER_ONLY) + +# setup install of executable and things copied by fixup/windeployqt +install( + DIRECTORY "$/" + DESTINATION "." + COMPONENT "client" + PATTERN "*.pdb" EXCLUDE + PATTERN "*.lib" EXCLUDE + PATTERN "*.exp" EXCLUDE +) + +endif() \ No newline at end of file diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index aa2b81c0a8..0da2a34c48 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -57,6 +57,7 @@ exports.handlers = { '../../libraries/physics/src', '../../libraries/platform/src/platform/backend', '../../libraries/plugins/src/plugins', + '../../libraries/procedural/src/procedural', '../../libraries/pointers/src', '../../libraries/render-utils/src', '../../libraries/script-engine/src',
Log some values.Demonstrate assertion behavior.Nested groups.Time some processing.