Merge branch 'master' into cullFace

This commit is contained in:
HifiExperiments 2019-12-11 00:49:58 -08:00 committed by GitHub
commit 5fc55d1763
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 6319 additions and 891 deletions
.github/workflows
CMakeLists.txt
android
assignment-client
cmake/macros
domain-server/src
hifi_vcpkg.py
interface
launchers/qt
libraries
screenshare
scripts
developer
system/create
assets/data
edit.js
entityProperties/html/js
tools

View file

@ -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

46
.github/workflows/dump.yml vendored Normal file
View file

@ -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"

251
.github/workflows/master_build.yml vendored Normal file
View file

@ -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 }}

121
.github/workflows/pr_build.yml vendored Normal file
View file

@ -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

View file

@ -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

View file

@ -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 \

View file

@ -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
$<TARGET_FILE_DIR:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>)
else()
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink
$<TARGET_FILE:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>/oven)
endif()
if (WIN32)
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
$<TARGET_FILE_DIR:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>)
else()
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink
$<TARGET_FILE:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>/oven)
endif()
endif (BUILD_TOOLS)
if (WIN32)
package_libraries_for_deployment()

View file

@ -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<float>::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<float>::max() };
float screenshareZoneVolume { priorityZoneVolume };
EntityItemID screenshareZoneid{};
static bool operation(const OctreeElementPointer& element, void* extraData) {
auto findPriorityZone = static_cast<FindPriorityZone*>(extraData);
if (element->getAACube().contains(findPriorityZone->position)) {
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) {
if (item->getType() == EntityTypes::Zone
&& item->contains(findPriorityZone->position)) {
auto zoneItem = static_pointer_cast<ZoneEntityItem>(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<FindContainingZone*>(extraData);
if (element->getAACube().contains(findContainingZone->position)) {
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&findContainingZone](EntityItemPointer item) {
if (item->getType() == EntityTypes::Zone && item->contains(findContainingZone->position)) {
auto zoneItem = static_pointer_cast<ZoneEntityItem>(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<NodeList>();
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<AvatarTraits::TraitType>(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt));
auto traitType =
static_cast<AvatarTraits::TraitType>(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()) {

View file

@ -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;

View file

@ -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

View file

@ -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})

View file

@ -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()
endmacro()

View file

@ -3616,25 +3616,18 @@ void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMes
}
void DomainServer::processAvatarZonePresencePacket(QSharedPointer<ReceivedMessage> 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<LimitedNodeList>();
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<AccountManager>()->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<LimitedNodeList>();
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<LimitedNodeList>();
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) {

View file

@ -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();

View file

@ -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')

View file

@ -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()

View file

@ -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<ToolbarScriptingInterface>()->getToolbar("com.highfidelity.interface.toolbar.system");
toolbar->writeProperty("visible", true);
@ -5671,6 +5676,7 @@ void Application::resumeAfterLoginDialogActionTaken() {
}
updateSystemTabletMode();
#endif
{
auto userInputMapper = DependencyManager::get<UserInputMapper>();

View file

@ -17,12 +17,64 @@
static const int VR_TARGET_RATE = 90;
/**jsdoc
* <p>Refresh rate profile.</p>
*
* <table>
* <thead>
* <tr><th>Refresh Rate Profile</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"Eco"</code></td><td>Low refresh rate, which is reduced when Interface doesn't have focus or is
* minimized.</td></tr>
* <tr><td><code>"Interactive"</code></td><td>Medium refresh rate, which is reduced when Interface doesn't have focus or is
* minimized.</td></tr>
* <tr><td><code>"Realtime"</code></td><td>High refresh rate, even when Interface doesn't have focus or is minimized.
* </tbody>
* </table>
*
* @typedef {string} RefreshRateProfile
*/
static const std::array<std::string, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILE_TO_STRING =
{ { "Eco", "Interactive", "Realtime" } };
/**jsdoc
* <p>Interface states that affect the refresh rate.</p>
*
* <table>
* <thead>
* <tr><th>Refresh Rate Regime</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"FocusActive"</code></td><td>Interface has focus and the user is active or is in VR.</td></tr>
* <tr><td><code>"FocusInactive"</code></td><td>Interface has focus and the user is inactive.</td></tr>
* <tr><td><code>"Unfocus"</code></td><td>Interface doesn't have focus.</td></tr>
* <tr><td><code>"Minimized"</code></td><td>Interface is minimized.</td></tr>
* <tr><td><code>"StartUp"</code></td><td>Interface is starting up.</td></tr>
* <tr><td><code>"ShutDown"</code></td><td>Interface is shutting down.</td></tr>
* </tbody>
* </table>
*
* @typedef {string} RefreshRateRegime
*/
static const std::array<std::string, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIME_TO_STRING =
{ { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } };
/**jsdoc
* <p>Interface operates in different modes to provide different user experiences (UX).</p>
*
* <table>
* <thead>
* <tr><th>UX Mode</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"Desktop"</code></td><td>Desktop user experience mode.</td></tr>
* <tr><td><code>"VR"</code></td><td>VR user experience mode.</td></tr>
* </tbody>
* </table>
*
* @typedef {string} UXMode
*/
static const std::array<std::string, RefreshRateManager::UXMode::UX_NUM> UX_MODE_TO_STRING =
{ { "Desktop", "VR" } };

View file

@ -436,38 +436,72 @@ class MyAvatar : public Avatar {
const bool DEFAULT_STRAFE_ENABLED = true;
public:
/**jsdoc
* The <code>DriveKeys</code> 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
* <p>Logical keys that drive your avatar and camera.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Name</th><th>Description</th></tr>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>TRANSLATE_X</td><td>Move the user's avatar in the direction of its x-axis, if the
* camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>1</code></td><td>TRANSLATE_Y</td><td>Move the user's avatar in the direction of its y-axis, if the
* camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>2</code></td><td>TRANSLATE_Z</td><td>Move the user's avatar in the direction of its z-axis, if the
* camera isn't in independent or mirror modes</td></tr>
* <tr><td><code>3</code></td><td>YAW</td><td>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.</td></tr>
* <tr><td><code>4</code></td><td>STEP_TRANSLATE_X</td><td>No action.</td></tr>
* <tr><td><code>5</code></td><td>STEP_TRANSLATE_Y</td><td>No action.</td></tr>
* <tr><td><code>6</code></td><td>STEP_TRANSLATE_Z</td><td>No action.</td></tr>
* <tr><td><code>7</code></td><td>STEP_YAW</td><td>Rotate the user's avatar about its y-axis in a step increment, if
* the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>8</code></td><td>PITCH</td><td>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.</td></tr>
* <tr><td><code>9</code></td><td>ZOOM</td><td>Zoom the camera in or out.</td></tr>
* <tr><td><code>10</code></td><td>DELTA_YAW</td><td>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.</td></tr>
* <tr><td><code>11</code></td><td>DELTA_PITCH</td><td>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.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.TRANSLATE_X}</code></td><td>Move the user's avatar in the direction of its
* x-axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.TRANSLATE_Y}</code></td><td>Move the user's avatar in the direction of its
* -axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.TRANSLATE_Z}</code></td><td>Move the user's avatar in the direction of its
* z-axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.YAW}</code></td><td>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.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.STEP_TRANSLATE_X}</code></td><td>No action.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.STEP_TRANSLATE_Y}</code></td><td>No action.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.STEP_TRANSLATE_Z}</code></td><td>No action.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.STEP_YAW}</code></td><td>Rotate the user's avatar about its y-axis in a
* step increment, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.PITCH}</code></td><td>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.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.ZOOM}</code></td><td>Zoom the camera in or out.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.DELTA_YAW}</code></td><td>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.</td></tr>
* <tr><td><code>{@link DriveKeys|DriveKeys.DELTA_PITCH}</code></td><td>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.</td></tr>
* </tbody>
* </table>
* @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 <caption>Disable rotating your avatar using the keyboard for a couple of seconds.</caption>
* 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} <code>true</code> if the drive key is disabled, <code>false</code> if it isn't.
*/
Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const;

View file

@ -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
* <code>"Unknown user"</code>. <em>Read-only.</em>
* @property {boolean} loggedIn - <code>true</code> if the user is logged in, otherwise <code>false</code>.
@ -61,6 +62,86 @@ class AccountServicesScriptingInterface : public QObject {
* &mdash; typically <code>"https://metaverse.highfidelity.com"</code>. <em>Read-only.</em>
*/
/**jsdoc
* The <code>Account</code> 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
* <code>"Unknown user"</code>. <em>Read-only.</em>
* @property {boolean} loggedIn - <code>true</code> if the user is logged in, otherwise <code>false</code>.
* <em>Read-only.</em>
* @property {string} findableBy - The user's visibility to other users:
* <ul>
* <li><code>"none"</code> &mdash; user appears offline.</li>
* <li><code>"friends"</code> &mdash; user is visible only to friends.</li>
* <li><code>"connections"</code> &mdash; user is visible to friends and connections.</li>
* <li><code>"all"</code> &mdash; user is visible to everyone.</li>
* </ul>
* @property {string} metaverseServerURL - The metaverse server that the user is authenticated against when logged in
* &mdash; typically <code>"https://metaverse.highfidelity.com"</code>. <em>Read-only.</em>
*
* @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 <code>GlobalServices</code> 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
* <code>"Unknown user"</code>. <em>Read-only.</em>
* @property {boolean} loggedIn - <code>true</code> if the user is logged in, otherwise <code>false</code>.
* <em>Read-only.</em>
* @property {string} findableBy - The user's visibility to other users:
* <ul>
* <li><code>"none"</code> &mdash; user appears offline.</li>
* <li><code>"friends"</code> &mdash; user is visible only to friends.</li>
* <li><code>"connections"</code> &mdash; user is visible to friends and connections.</li>
* <li><code>"all"</code> &mdash; user is visible to everyone.</li>
* </ul>
* @property {string} metaverseServerURL - The metaverse server that the user is authenticated against when logged in
* &mdash; typically <code>"https://metaverse.highfidelity.com"</code>. <em>Read-only.</em>
*
* @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)

View file

@ -14,19 +14,56 @@
#include <QObject>
/**jsdoc
* The <code>DialogsMamnager</code> 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
* <em>Currently performs no action.</em>
* @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.
* <p><strong>Warning:</strong> Currently isn't always triggered.</p>
* @function DialogsManager.addressBarShown
* @param {boolean} visible - <code>true</code> if the Goto dialog has been opened, <code>false</code> if it has been
* closed.
* @returns {Signal}
*/
void addressBarShown(bool visible);
};

View file

@ -18,6 +18,7 @@
#include <AccountManager.h>
#include <AddressManager.h>
#include <DependencyManager.h>
#include <NodeList.h>
#include <UUID.h>
#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<NodeList>();
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacketOnClient");
};
ScreenshareScriptingInterface::~ScreenshareScriptingInterface() {
stopScreenshare();
}
void ScreenshareScriptingInterface::processAvatarZonePresencePacketOnClient(QSharedPointer<ReceivedMessage> 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);

View file

@ -18,9 +18,11 @@
#include <QNetworkReply>
#include <PathUtils.h>
#include <ReceivedMessage.h>
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<ReceivedMessage> 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<QProcess> _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

File diff suppressed because it is too large Load diff

13
launchers/qt/BUILD.md Normal file
View file

@ -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.

34
launchers/qt/readme.md Normal file
View file

@ -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.

View file

@ -38,6 +38,10 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -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();
});

View file

@ -18,6 +18,7 @@
#include <mutex>
#include <queue>
#include <QFuture>
#include <QtCore/QtGlobal>
#include <QtCore/QByteArray>
#include <QtCore/QElapsedTimer>
@ -506,7 +507,8 @@ private:
#endif
AudioSolo _solo;
QFuture<void> _localPrepInjectorFuture;
QReadWriteLock _hmdNameLock;
Mutex _checkDevicesMutex;
QTimer* _checkDevicesTimer { nullptr };

View file

@ -39,6 +39,10 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -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<QString, TextEffect>& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; }
const QHash<QString, TextEffect> stringToTextEffectLookup = [] {
QHash<QString, TextEffect> 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 <caption>Create a zone that casts a red key light along the x-axis.</caption>
* 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<QString> EntityItemProperties::listChangedProperties() {
if (avatarPriorityChanged()) {
out += "avatarPriority";
}
if (screenshareChanged()) {
out += "screenshare";
}
// Polyvox
if (voxelVolumeSizeChanged()) {

View file

@ -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, "");

View file

@ -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,

View file

@ -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;
}

View file

@ -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 };

View file

@ -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);

View file

@ -37,6 +37,10 @@ class TextureCacheScriptingInterface : public ScriptableResourceCache, public De
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -37,6 +37,10 @@ class ModelCacheScriptingInterface : public ScriptableResourceCache, public Depe
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -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* "

View file

@ -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 {

View file

@ -68,6 +68,66 @@ const QString GET_PLACE = "/api/v1/places/%1";
* <em>Read-only.</em>
*/
/**jsdoc
* The <code>AddressManager</code> 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.
* <em>Read-only.</em>
* @property {string} hostname - The name of the domain for your current metaverse address (e.g., <code>"DomainName"</code>,
* <code>localhost</code>, or an IP address). Is blank if you're in a serverless domain.
* <em>Read-only.</em>
* @property {string} href - Your current metaverse address (e.g., <code>"hifi://domainname/15,-10,26/0,0,0,1"</code>)
* regardless of whether or not you're connected to the domain. Starts with <code>"file:///"</code> if you're in a
* serverless domain.
* <em>Read-only.</em>
* @property {boolean} isConnected - <code>true</code> if you're connected to the domain in your current <code>href</code>
* metaverse address, otherwise <code>false</code>.
* @property {string} pathname - The location and orientation in your current <code>href</code> metaverse address
* (e.g., <code>"/15,-10,26/0,0,0,1"</code>).
* <em>Read-only.</em>
* @property {string} placename - The place name in your current <code>href</code> metaverse address
* (e.g., <code>"DomainName"</code>). Is blank if your <code>hostname</code> is an IP address.
* <em>Read-only.</em>
* @property {string} protocol - The protocol of your current <code>href</code> metaverse address (e.g., <code>"hifi"</code>).
* <em>Read-only.</em>
*
* @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

View file

@ -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). <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). <em>Read-only.</em>
*/
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*/
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 <caption>Report cached resources.</caption>
* // 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 <caption>Prefetch a resource and wait until it has loaded.</caption>
* // 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";

View file

@ -277,6 +277,7 @@ enum class EntityVersion : PacketVersion {
ShadowBiasAndDistance,
TextEntityFonts,
ScriptServerKinematicMotion,
ScreenshareZone,
// Add new versions above here
NUM_PACKET_TYPE,

View file

@ -93,7 +93,7 @@ public:
* collisions.
*/
/**jsdoc
/**jsdoc
* <p>A type of pick.</p>
* <table>
* <thead>

View file

@ -24,7 +24,7 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* The <code>TextureCache</code> API manages texture cache resources.
* The <code>MaterialCache</code> 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. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -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: <code>"hifi_pbr"</code>, <code>"hifi_shader_simple"</code>.
* @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 <code>0.0</code> &ndash;
* <code>1.0</code>. A {@link RGBS} value can be either RGB or sRGB.
@ -144,12 +144,17 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
* value for transparency.
* <code>"hifi_pbr"</code> model only.
* @property {string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be:
* <code>"OPACITY_MAP_OPAQUE"</code> for ignoring the opacity map information.
* <code>"OPACITY_MAP_MASK"</code> for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque.
* <code>"OPACITY_MAP_BLEND"</code> for using the opacity map for alpha blending the material surface with the background.
* <ul>
* <li><code>"OPACITY_MAP_OPAQUE"</code> for ignoring the <code>opacityMap</code> information.</li>
* <li><code>"OPACITY_MAP_MASK"</code> for using the <code>opacityMap</code> as a mask, where only the texel greater
* than <code>opacityCutoff</code> are visible and rendered opaque.</li>
* <li><code>"OPACITY_MAP_BLEND"</code> for using the <code>opacityMap</code> for alpha blending the material surface
* with the background.</li>
* </ul>
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> 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 <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the
* <code>opacityMap</code> when <code>opacityMapMode</code> is <code>"OPACITY_MAP_MASK"</code>, range <code>0.0</code>
* &ndash; <code>1.0</code>.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {string} cullFaceMode - The mode defining which side of the geometry should be rendered. Values can be:
* <ul>
@ -186,7 +191,7 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {Mat4|string} texCoordTransform1 - The transform to use for <code>occlusionMap</code> and <code>lightMap</code>.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {string} lightmapParams - Parameters for controlling how lightMap is used.
* @property {string} lightmapParams - Parameters for controlling how <code>lightMap</code> is used.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* <p><em>Currently not used.</em></p>
* @property {string} materialParams - Parameters for controlling the material projection and repetition.

View file

@ -26,25 +26,190 @@
#include <QtCore/QHash>
#include <QtScript/QScriptable>
/**jsdoc
* The <code>console</code> 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.
* <p>If a {@link console.group} is in effect, the message is indented by an amount proportional to the group level.</p>
* @function console.log
* @param {...*} [message] - The message values to log.
* @example <caption>Log some values.</caption>
* 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.
* <p>Note: Script execution continues whether or not the test condition fails.</p>
* @function console.assert
* @param {boolean} assertion - The test condition value.
* @param {...*} [message] - The message values to log if the assertion evaluates to <code>false</code>.
* @example <caption>Demonstrate assertion behavior.</caption>
* 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 <caption>Nested groups.</caption>
* 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 <caption>Time some processing.</caption>
* 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 &gt; Scripting &gt; Script Log debug window.
* @function console.clear
*/
void clear();
private:

View file

@ -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.
* <p>Alternatively, you can use {@link Script.print} or one of the {@link console} API methods.</p>
* @function print
* @param {...*} [message] - The message values to print.
*/
globalObject().setProperty("print", newFunction(debugPrint));
QScriptValue audioEffectOptionsConstructorValue = newFunction(AudioEffectOptions::constructor);

View file

@ -499,17 +499,11 @@ public:
Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
/**jsdoc
* Prints a message to the program log.
* <p>Alternatively, you can use {@link print}, {@link console.log}, or one of the other {@link console} methods.</p>
* Prints a message to the program log and emits {@link Script.printedMessage}.
* <p>Alternatively, you can use {@link print} or one of the {@link console} API methods.</p>
* @function Script.print
* @param {string} message - The message to print.
*/
/**jsdoc
* Prints a message to the program log.
* <p>This is an alias of {@link Script.print}.</p>
* @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.

View file

@ -33,13 +33,20 @@
#include "MainWindow.h"
/**jsdoc
* The <code>OffscreenFlags</code> API enables gamepad joystick navigation of UI.
*
* <p><em>This API currently has no effect and is not used.</em></p>
*
* @namespace OffscreenFlags
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
* @property {boolean} navigationFocused
* @property {boolean} navigationFocusDisabled
*
* @property {boolean} navigationFocused - <code>true</code> if UI has joystick navigation focus, <code>false</code> if it
* doesn't.
* @property {boolean} navigationFocusDisabled - <code>true</code> if UI joystick navigation focus is disabled,
* <code>false</code> 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 <code>navigationFocused</code> property changes.
* @function OffscreenFlags.navigationFocusedChanged
* @returns {Signal}
*/
void navigationFocusedChanged();
/**jsdoc
* Triggered when the value of the <code>navigationFocusDisabled</code> property changes.
* @function OffscreenFlags.navigationFocusDisabledChanged
* @returns {Signal}
*/

View file

@ -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.

View file

@ -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){

File diff suppressed because it is too large Load diff

View file

@ -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."
},

View file

@ -393,7 +393,8 @@ const DEFAULT_ENTITY_PROPERTIES = {
},
shapeType: "box",
bloomMode: "inherit",
avatarPriority: "inherit"
avatarPriority: "inherit",
screenshare: "inherit",
},
Model: {
collisionShape: "none",

View file

@ -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",
}
]
},

1
tools/animedit/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pro.user

4
tools/animedit/LICENCE Normal file
View file

@ -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

14
tools/animedit/README.md Normal file
View file

@ -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.

View file

@ -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

36
tools/animedit/main.cpp Normal file
View file

@ -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 <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#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();
}

26
tools/animedit/qml.qrc Normal file
View file

@ -0,0 +1,26 @@
<RCC>
<qresource prefix="/">
<file>qml/main.qml</file>
<file>qml/TreeDelegate.qml</file>
<file>qml/fields/BooleanField.qml</file>
<file>qml/fields/IdField.qml</file>
<file>qml/fields/JSONField.qml</file>
<file>qml/fields/NumberArrayField.qml</file>
<file>qml/fields/NumberField.qml</file>
<file>qml/fields/StringField.qml</file>
<file>qml/fields/TypeField.qml</file>
<file>qml/nodes/BlendDirectional.qml</file>
<file>qml/nodes/BlendLinear.qml</file>
<file>qml/nodes/BlendLinearMove.qml</file>
<file>qml/nodes/ClipData.qml</file>
<file>qml/nodes/DefaultPose.qml</file>
<file>qml/nodes/InverseKinematics.qml</file>
<file>qml/nodes/Manipulator.qml</file>
<file>qml/nodes/Overlay.qml</file>
<file>qml/nodes/PoleVector.qml</file>
<file>qml/nodes/RandomStateMachine.qml</file>
<file>qml/nodes/StateMachine.qml</file>
<file>qml/nodes/SplineIK.qml</file>
<file>qml/nodes/TwoBoneIK.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,10 @@
import QtQuick 2.0
Item {
Text {
anchors.fill: parent
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

262
tools/animedit/qml/main.qml Normal file
View file

@ -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");
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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: {}
}
}

View file

@ -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: ""
}
}

View file

@ -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
}
}

View file

@ -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: ""
}
}

View file

@ -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: {}
}
}

View file

@ -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: ""
}
}

View file

@ -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: {}
}
}

View file

@ -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: ""
}
}

View file

@ -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 <QDebug>
#include <QStringList>
TreeItem::TreeItem(const QList<QVariant>& 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<TreeItem*>(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;
}

45
tools/animedit/treeitem.h Normal file
View file

@ -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 <QList>
#include <QVariant>
class TreeItem
{
public:
explicit TreeItem(const QList<QVariant>& 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<TreeItem*> m_childItems;
QList<QVariant> m_itemData;
TreeItem* m_parentItem;
};
#endif

View file

@ -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 <QDebug>
#include <QFile>
#include <QtGlobal>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QStringList>
#include <QJSValue>
#include "treemodel.h"
static TreeItem* newBlankTreeItem() {
QList<QVariant> 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<QVariant> rootData;
rootData << "Name" << "Type" << "Data";
_rootItem = new TreeItem(rootData);
_clipboard = nullptr;
}
TreeModel::~TreeModel() {
delete _rootItem;
}
QHash<int, QByteArray> 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<TreeItem*>(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<TreeItem*>(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<QVariant> 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<TreeItem*>(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<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
beginRemoveRows(createIndex(0, 0, reinterpret_cast<quintptr>(parentItem)), childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
}
}
void TreeModel::insertNodeAbove(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
TreeItem* newItem = newBlankTreeItem();
QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(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<TreeItem*>(index.internalPointer());
for (int i = 0; i < parent->childCount(); ++i) {
TreeItem* child = parent->child(i);
indices.push_back(createIndex(i, 0, reinterpret_cast<quintptr>(child)));
}
return indices;
}
void TreeModel::copyNode(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
// TODO: delete previous clipboard
_clipboard = item->cloneNode();
}
void TreeModel::copyNodeAndChildren(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
// TODO: delete previous clipboard
_clipboard = item->cloneNodeAndChildren();
}
void TreeModel::pasteOver(const QModelIndex& index) {
if (_clipboard) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(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<TreeItem*>(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<QVariant> 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<TreeItem*>(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<QJSValue>()) {
obj.insert("data", data.value<QJSValue>().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;
}

View file

@ -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 <QAbstractItemModel>
#include <QJsonObject>
#include <QModelIndex>
#include <QObject>
#include <QString>
#include <QVariant>
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<int, QByteArray> 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<int, QByteArray> _roleNameMapping;
TreeItem* _clipboard;
};
#endif

Binary file not shown.

View file

@ -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"]

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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()

View file

@ -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()

View file

@ -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 "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
DESTINATION "."
COMPONENT "client"
PATTERN "*.pdb" EXCLUDE
PATTERN "*.lib" EXCLUDE
PATTERN "*.exp" EXCLUDE
)
endif()

View file

@ -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',