Merge branch 'overte-org:master' into mouselook_refactor

This commit is contained in:
Armored-Dragon 2024-07-01 16:44:02 -05:00 committed by GitHub
commit f2e1ae9a62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
117 changed files with 1696 additions and 710 deletions

View file

@ -10,8 +10,10 @@ on:
pull_request:
types: [opened, synchronize, reopened, labeled]
push:
branches:
- master
tags:
# Release tags. E.g. 2024.06.1
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
- "[0-9][0-9][0-9][0-9].[0-9][0-9].**"
env:
BUILD_TYPE: Release
@ -19,8 +21,8 @@ env:
UPLOAD_BUCKET: overte-public
UPLOAD_REGION: fra1
UPLOAD_ENDPOINT: "https://fra1.digitaloceanspaces.com"
CMAKE_BACKTRACE_URL: ${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}
CMAKE_BACKTRACE_TOKEN: server_${{ github.event.number }}_${{ github.sha }}
# Disable VCPKG caching to save time.
VCPKG_FEATURE_FLAGS: -binarycaching
jobs:
build:
@ -33,73 +35,77 @@ jobs:
- os: debian-11
image: docker.io/overte/overte-server-build:0.1.3-debian-11-amd64
arch: amd64
runner: linux_amd64
# https://github.com/testflows/TestFlows-GitHub-Hetzner-Runners/wiki/Meta-Labels
# self_hosted makes the Hetzner auto-scaler put up the job.
# type-cx52 is a Hetzner VPS server type. In this case cs52 is a server with 16-cores and 32GB of RAM.
# image-x86-app-docker-ce is a Hetzner image.
# https://github.com/testflows/TestFlows-GitHub-Hetzner-Runners/wiki/Specifying-The-Runner-Image
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
- os: debian-11
image: docker.io/overte/overte-server-build:0.1.3-debian-11-aarch64
arch: aarch64
runner: linux_aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
- os: debian-12
image: docker.io/overte/overte-server-build:0.1.3-debian-12-amd64
arch: amd64
runner: linux_amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
- os: debian-12
image: docker.io/overte/overte-server-build:0.1.3-debian-12-aarch64
arch: aarch64
runner: linux_aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
- os: ubuntu-20.04
image: docker.io/overte/overte-server-build:0.1.3-ubuntu-20.04-amd64
arch: amd64
runner: linux_amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
- os: ubuntu-22.04
image: docker.io/overte/overte-server-build:0.1.3-ubuntu-22.04-amd64
arch: amd64
runner: linux_amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
- os: ubuntu-22.04
image: docker.io/overte/overte-server-build:0.1.3-ubuntu-22.04-aarch64
arch: aarch64
runner: linux_aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
- os: ubuntu-24.04
image: docker.io/overte/overte-server-build:0.1.3-ubuntu-24.04-amd64
arch: amd64
runner: linux_amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
- os: ubuntu-24.04
image: docker.io/overte/overte-server-build:0.1.3-ubuntu-24.04-aarch64
arch: aarch64
runner: linux_aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
- os: fedora-38
image: docker.io/overte/overte-server-build:0.1.3-fedora-38-amd64
- os: fedora-39
image: docker.io/overte/overte-server-build:0.1.4-fedora-39-amd64
arch: amd64
runner: linux_amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
- os: fedora-38
image: docker.io/overte/overte-server-build:0.1.3-fedora-38-aarch64
- os: fedora-39
image: docker.io/overte/overte-server-build:0.1.4-fedora-39-aarch64
arch: aarch64
runner: linux_aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
# Packaging broken; See: https://github.com/overte-org/overte/issues/968
#~ - os: fedora-39
#~ image: docker.io/overte/overte-server-build:0.1.4-fedora-39-amd64
#~ arch: amd64
#~ runner: linux_amd64
- os: fedora-40
image: docker.io/overte/overte-server-build:0.1.4-fedora-40-amd64
arch: amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
#~ - os: fedora-39
#~ image: docker.io/overte/overte-server-build:0.1.4-fedora-39-aarch64
#~ arch: aarch64
#~ runner: linux_aarch64
- os: fedora-40
image: docker.io/overte/overte-server-build:0.1.4-fedora-40-aarch64
arch: aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
- os: rockylinux-9
image: docker.io/overte/overte-server-build:0.1.3-rockylinux-9-amd64
arch: amd64
runner: linux_amd64
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
fail-fast: false
@ -143,7 +149,7 @@ jobs:
fi
# Tagged builds. E.g. release or release candidate builds.
if [ "${{github.event_name}}" != "pull_request" ]; then
if [ "${{github.ref_type}}" == "tag" ]; then
echo "PRODUCTION_BUILD=true" >> $GITHUB_ENV
fi
@ -177,16 +183,30 @@ jobs:
echo "UPLOAD_PREFIX=build/overte/master" >> $GITHUB_ENV
echo "RELEASE_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
else # tagged
echo "DEBVERSION=${{ github.run_number }}-${{ github.ref_name }}-$GIT_COMMIT_SHORT-${{ matrix.os }}" >> $GITHUB_ENV
echo "RPMVERSION=${${{ github.ref_name }}//-/.}.${{ github.run_number }}.$GIT_COMMIT_SHORT" >> $GITHUB_ENV
echo "DEBVERSION=${{ github.ref_name }}-$GIT_COMMIT_SHORT-${{ matrix.os }}" >> $GITHUB_ENV
echo "RPMVERSION=${{ github.ref_name }}.$GIT_COMMIT_SHORT" >> $GITHUB_ENV
fi
if [[ "${{ github.ref_name }}" != "master" && "${{ github.ref_name }}" != "pull_request" ]]; then # tagged
echo "RELEASE_NUMBER=/${{ github.ref_name }}" >> $GITHUB_ENV
if [ "${{ github.ref_name }}" == *"rc"* ]; then # release candidate
echo "UPLOAD_PREFIX=build/overte/release-candidate" >> $GITHUB_ENV
if [ "${{ github.ref_type }}" == "tag" ]; then # tagged
echo "RELEASE_NUMBER=${{ github.ref_name }}" >> $GITHUB_ENV
if [[ "${{ github.ref_name }}" == *"rc"* ]]; then # release candidate
# The uploader already creates a subfolder for each RELEASE_NUMBER.
echo "UPLOAD_PREFIX=build/overte/release-candidate/" >> $GITHUB_ENV
else # release
echo "UPLOAD_PREFIX=build/overte/release" >> $GITHUB_ENV
echo "UPLOAD_PREFIX=build/overte/release/" >> $GITHUB_ENV
fi
fi
echo "BUILD_NUMBER=$GIT_COMMIT_SHORT" >> $GITHUB_ENV
if [ -z "$CMAKE_BACKTRACE_URL" ]; then
if [ "${{ github.ref_type }}" == "tag" ]; then
export CMAKE_BACKTRACE_URL="${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}"
export CMAKE_BACKTRACE_TOKEN="${{ github.ref_name }}_${{ matrix.os }}_${{ github.sha }}"
else
# We're building a PR, default to the PR endpoint
export CMAKE_BACKTRACE_URL="https://o4504831972343808.ingest.sentry.io/api/4504832427950080/minidump/?sentry_key=f511de295975461b8f92a36f4a4a4f32"
export CMAKE_BACKTRACE_TOKEN="server_pr_${{ github.event.number }}_${{ github.sha }}"
fi
fi
@ -207,6 +227,8 @@ jobs:
echo "ARTIFACT_PATTERN=overte-server-$RPMVERSION-1.fc38.$INSTALLER_EXT" >> $GITHUB_ENV
elif [ "${{ matrix.os }}" == "fedora-39" ]; then
echo "ARTIFACT_PATTERN=overte-server-$RPMVERSION-1.fc39.$INSTALLER_EXT" >> $GITHUB_ENV
elif [ "${{ matrix.os }}" == "fedora-40" ]; then
echo "ARTIFACT_PATTERN=overte-server-$RPMVERSION-1.fc40.$INSTALLER_EXT" >> $GITHUB_ENV
else
echo "Error! ARTIFACT_PATTERN not set!"
exit 1 # Fail
@ -214,7 +236,7 @@ jobs:
fi
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
@ -227,11 +249,6 @@ jobs:
working-directory: build
shell: bash
run: |
if [ -z "$CMAKE_BACKTRACE_URL" ] ; then
# We're building a PR, default to the PR endpoint
export CMAKE_BACKTRACE_URL="https://o4504831972343808.ingest.sentry.io/api/4504832427950080/minidump/?sentry_key=f511de295975461b8f92a36f4a4a4f32"
export CMAKE_BACKTRACE_TOKEN="server_pr_${{ github.event.number }}_${{ github.sha }}"
fi
cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release $CMAKE_EXTRA
@ -286,8 +303,7 @@ jobs:
df -h
- name: Upload artifact to GitHub
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_PATTERN }}
path: pkg-scripts/${{ env.ARTIFACT_PATTERN }}

View file

@ -1,6 +1,6 @@
# Copyright 2013-2019 High Fidelity, Inc.
# Copyright 2020-2022 Vircadia contributors
# Copyright 2021-2022 Overte e.V.
# Copyright 2021-2024 Overte e.V.
# SPDX-License-Identifier: Apache-2.0
name: Master CI Build
@ -26,6 +26,8 @@ env:
UPLOAD_ENDPOINT: "https://fra1.digitaloceanspaces.com"
CMAKE_BACKTRACE_URL: ${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}
CMAKE_BACKTRACE_TOKEN: master_${{ github.event.number }}_${{ github.sha }}
# Disable VCPKG caching to save time.
VCPKG_FEATURE_FLAGS: -binarycaching
# OSX-specific variables
DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer
@ -120,7 +122,7 @@ jobs:
rm -rf ~/overte-files
rm -rf ~/.cache
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
@ -242,14 +244,12 @@ jobs:
working-directory: ${{runner.workspace}}/build
run: cat ./_CPack_Packages/win64/NSIS/NSISOutput.log
- name: Upload artifact
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS') || matrix.build_type == 'android' # Automatic Linux packaging is not implemented
shell: bash
working-directory: ${{runner.workspace}}/build
env:
AWS_ACCESS_KEY_ID: ${{ secrets.s3_access_key_id }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.s3_secret_access_key }}
run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py
- name: Upload artifact to GitHub
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_PATTERN }}
path: ./build/${{ env.ARTIFACT_PATTERN }}
if-no-files-found: error
#- name: Archive symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')

View file

@ -1,4 +1,4 @@
# Copyright 2022-2023 Overte e.V.
# Copyright 2022-2024 Overte e.V.
# SPDX-License-Identifier: Apache-2.0
name: Master API-docs CI Build and Deploy
@ -14,7 +14,7 @@ jobs:
name: Build and deploy API-docs
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install dependencies
working-directory: tools/jsdoc

View file

@ -1,4 +1,4 @@
# Copyright 2022-2023 Overte e.V.
# Copyright 2022-2024 Overte e.V.
# SPDX-License-Identifier: Apache-2.0
name: Master Doxygen CI Build and Deploy
@ -14,7 +14,7 @@ jobs:
name: Build and deploy Doxygen documentation
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install dependencies
run: |

View file

@ -1,6 +1,6 @@
# Copyright 2013-2019 High Fidelity, Inc.
# Copyright 2020-2022 Vircadia contributors.
# Copyright 2021-2022 Overte e.V.
# Copyright 2021-2024 Overte e.V.
# SPDX-License-Identifier: Apache-2.0
name: Pull Request CI Build
@ -24,6 +24,8 @@ env:
# We can't use secrets or actions here, so the actual value has to be hardcoded.
CMAKE_BACKTRACE_URL: "https://o4504831972343808.ingest.sentry.io/api/4504832427950080/minidump/?sentry_key=f511de295975461b8f92a36f4a4a4f32"
CMAKE_BACKTRACE_TOKEN: PR_${{ github.event.number }}_${{ github.sha }}
# Disable VCPKG caching to save time.
VCPKG_FEATURE_FLAGS: -binarycaching
UPLOAD_BUCKET: overte-public
UPLOAD_REGION: fra1
@ -54,7 +56,12 @@ jobs:
#- os: macOS-10.15
# build_type: full
- os: Ubuntu 20.04
runner: linux_amd64
# https://github.com/testflows/TestFlows-GitHub-Hetzner-Runners/wiki/Meta-Labels
# self_hosted makes the Hetzner auto-scaler put up the job.
# type-cx52 is a Hetzner VPS server type. In this case cs52 is a server with 16-cores and 32GB of RAM.
# image-x86-app-docker-ce is a Hetzner image.
# https://github.com/testflows/TestFlows-GitHub-Hetzner-Runners/wiki/Specifying-The-Runner-Image
runner: [self_hosted, type-cx52, image-x86-app-docker-ce]
arch: amd64
build_type: full
apt-dependencies: pkg-config libxext-dev libdouble-conversion-dev libpcre2-16-0 libpulse0 libharfbuzz-dev libnss3 libnspr4 libxdamage1 libasound2 # add missing dependencies to docker image when convenient
@ -65,7 +72,7 @@ jobs:
# apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0 python3-github python3-distro
# Do not change the names of self-hosted runners without knowing what you are doing, as they correspond to labels that have to be set on the runner.
- os: Ubuntu 22.04
runner: linux_aarch64
runner: [self_hosted, type-cax41, image-arm-app-docker-ce]
arch: aarch64
build_type: full
image: docker.io/overte/overte-full-build:0.1.1-ubuntu-22.04-aarch64
@ -165,7 +172,7 @@ jobs:
rm -rf ~/overte-files
rm -rf ~/.cache
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
@ -335,7 +342,7 @@ jobs:
- name: Upload Artifact
if: startsWith(matrix.os, 'Windows') || startsWith(matrix.os, 'macOS')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_PATTERN }}
path: ./build/${{ env.ARTIFACT_PATTERN }}

212
.github/workflows/release_build.yml vendored Normal file
View file

@ -0,0 +1,212 @@
# Copyright 2013-2019 High Fidelity, Inc.
# Copyright 2020-2022 Vircadia contributors
# Copyright 2021-2024 Overte e.V.
# SPDX-License-Identifier: Apache-2.0
name: Windows Release Build
on:
push:
tags:
# Release tags. E.g. 2024.06.1
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
- "[0-9][0-9][0-9][0-9].[0-9][0-9].**"
env:
APP_NAME: interface
BUILD_TYPE: Release
CI_BUILD: Github
GIT_COMMIT: ${{ github.sha }}
PRODUCTION_BUILD: true
RELEASE_TYPE: PRODUCTION
RELEASE_NUMBER: ${{ github.ref_name }}
STABLE_BUILD: 1
UPLOAD_BUCKET: overte-public
UPLOAD_REGION: fra1
UPLOAD_ENDPOINT: "https://fra1.digitaloceanspaces.com"
CMAKE_BACKTRACE_URL: ${{ secrets.SENTRY_MINIDUMP_ENDPOINT }}
CMAKE_BACKTRACE_TOKEN: ${{ github.ref_name }}_Windows_${{ github.sha }}
# Disable VCPKG caching to save time.
VCPKG_FEATURE_FLAGS: -binarycaching
# WIN-specific variables
PreferredToolArchitecture: X64
jobs:
build:
strategy:
matrix:
include:
- os: windows-2019
build_type: full
fail-fast: false
runs-on: ${{matrix.os}}
steps:
- name: Configure build environment 1
shell: bash
id: buildenv1
run: |
echo "GIT_COMMIT_SHORT=`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_ENV
if [[ "${{ github.ref_name }}" == *"rc"* ]]; then # release candidate
# The uploader already creates a subfolder for each RELEASE_NUMBER.
echo "UPLOAD_PREFIX=build/overte/release-candidate/" >> $GITHUB_ENV
else # release
echo "UPLOAD_PREFIX=build/overte/release/" >> $GITHUB_ENV
fi
echo ::set-output name=github_sha_short::`echo $GIT_COMMIT | cut -c1-7`
echo "JOB_NAME=${{matrix.os}}, ${{matrix.build_type}}" >> $GITHUB_ENV
echo "APP_TARGET_NAME=$APP_NAME" >> $GITHUB_ENV
echo "PYTHON_EXEC=python" >> $GITHUB_ENV
echo "ZIP_COMMAND=7z" >> $GITHUB_ENV
echo "ZIP_ARGS=a" >> $GITHUB_ENV
echo "INSTALLER_EXT=exe" >> $GITHUB_ENV
echo "CMAKE_EXTRA=-A x64" >> $GITHUB_ENV
echo "SYMBOL_REGEX=\(exe\|dll\|pdb\)" >> $GITHUB_ENV
echo "SYMBOLS_ARCHIVE=$RELEASE_NUMBER-${{ github.sha }}-win-symbols.zip" >> $GITHUB_ENV
# echo "HF_PFX_PASSPHRASE=${{secrets.pfx_key}}" >> $GITHUB_ENV
# echo "HF_PFX_FILE=${{runner.workspace}}\build\codesign.pfx" >> $GITHUB_ENV
# Configuration is broken into two steps because you can't set an env var and also reference it in the same step
- name: Configure build environment 2
shell: bash
run: |
echo "BUILD_NUMBER=$GIT_COMMIT_SHORT" >> $GITHUB_ENV
echo "ARTIFACT_PATTERN=Overte-$RELEASE_NUMBER.$INSTALLER_EXT" >> $GITHUB_ENV
echo "CLIENT_ONLY=FALSE" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
- name: Override NSIS
shell: pwsh
if: startsWith(matrix.os, 'windows')
run: choco install nsis --allow-downgrade --version=3.06.1
- name: Install Python modules
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
shell: bash
run: $PYTHON_EXEC -m pip install boto3 PyGithub
- 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 -DVCPKG_BUILD_TYPE=release -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY -DBYPASS_SIGNING:BOOLEAN=TRUE $CMAKE_EXTRA
- name: Build application
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target $APP_TARGET_NAME $CMAKE_BUILD_EXTRA
- name: Build domain server
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
- name: Build assignment client
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
- name: Build console
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
- name: Build installer
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
echo "Retry code from https://unix.stackexchange.com/a/137639"
function fail {
echo $1 >&2
exit 1
}
function retry {
local n=1
local max=5
local delay=15
while true; do
"$@" && break || {
if [[ $n -lt $max ]]; then
((n++))
echo "Command failed. Attempt $n/$max:"
sleep $delay;
else
fail "The command has failed after $n attempts."
fi
}
done
}
retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
#- name: Sign installer (Windows)
# if: startsWith(matrix.os, 'windows')
# 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:ARTIFACT_PATTERN}
- name: Output system stats
if: ${{ always() }}
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
echo "Disk usage:"
df -h
- name: Output installer logs
if: failure() && startsWith(matrix.os, 'windows')
shell: bash
working-directory: ${{runner.workspace}}/build
run: cat ./_CPack_Packages/win64/NSIS/NSISOutput.log
- name: Upload artifact to S3
shell: bash
working-directory: ${{runner.workspace}}/build
env:
AWS_ACCESS_KEY_ID: ${{ secrets.s3_access_key_id }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.s3_secret_access_key }}
run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py
- name: Upload artifact to GitHub
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_PATTERN }}
path: ${{runner.workspace}}/build/${{ env.ARTIFACT_PATTERN }}
if-no-files-found: error
- name: Archive symbols
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
working-directory: ${{runner.workspace}}
shell: bash
run: |
SYMBOLS_TEMP="symbols-temp"
mkdir $SYMBOLS_TEMP
find "./build" -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \;
cd $SYMBOLS_TEMP
$ZIP_COMMAND $ZIP_ARGS ../$SYMBOLS_ARCHIVE .
- name: Upload debug symbols to GitHub
uses: actions/upload-artifact@v4
with:
name: ${{ env.SYMBOLS_ARCHIVE }}
path: ${{runner.workspace}}/${{ env.SYMBOLS_ARCHIVE }}
if-no-files-found: error
- name: Clear Working Directories
if: contains(matrix.runner, 'linux_aarch64')
shell: bash
run: |
rm -rf ./*
rm -rf ~/overte-files
rm -rf ~/.cache

View file

@ -111,10 +111,7 @@ CMAKE_BACKTRACE_TOKEN
// The release version, e.g., 2021.3.2.
RELEASE_NUMBER
// The release name, e.g., Eos.
RELEASE_NAME
// The build commit, e.g., use a Git hash for the most recent commit in the branch - fd6973b.
BUILD_NUMBER
// The type of release.

View file

@ -185,11 +185,11 @@ endif()
if(OVERTE_WARNINGS_AS_ERRORS)
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR (CMAKE_CXX_COMPILER_ID MATCHES "" AND WIN32))
set(ENV{CXXFLAGS} "$ENV{CXXFLAGS} /WX")
set(ENV{CFLAGS} "$ENV{CXXFLAGS} /WX")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX")
set(CMAKE_CFLAGS "${CMAKE_CFLAGS} /WX")
else()
set(ENV{CXXFLAGS} "$ENV{CXXFLAGS} -Werror")
set(ENV{CFLAGS} "$ENV{CXXFLAGS} -Werror")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
set(CMAKE_CFLAGS "${CMAKE_CFLAGS} -Werror")
endif()
endif()

View file

@ -106,7 +106,7 @@ For code signing to work, you will need to set the `HF_PFX_FILE` and `HF_PFX_PAS
1. Ensure you have all the prerequisites fulfilled from the [MacOS Build Guide](BUILD_OSX.md).
2. Perform a clean CMake in your build folder. e.g.
```bash
BUILD_GLOBAL_SERVICES=STABLE USE_STABLE_GLOBAL_SERVICES=1 RELEASE_BUILD=PRODUCTION BUILD_NUMBER="Insert Build Identifier here e.g. short hash of your last Git commit" RELEASE_NAME="Insert Release Name Here" STABLE_BUILD=1 PRODUCTION_BUILD=1 RELEASE_NUMBER="Insert Release Version Here e.g. 1.1.0" RELEASE_TYPE=PRODUCTION cmake -DCMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk" -DCLIENT_ONLY=1 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DOSX_SDK=10.12 ..
BUILD_GLOBAL_SERVICES=STABLE USE_STABLE_GLOBAL_SERVICES=1 RELEASE_BUILD=PRODUCTION BUILD_NUMBER="Insert Build Identifier here e.g. short hash of your last Git commit" STABLE_BUILD=1 PRODUCTION_BUILD=1 RELEASE_NUMBER="Insert Release Version Here e.g. 1.1.0" RELEASE_TYPE=PRODUCTION cmake -DCMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk" -DCLIENT_ONLY=1 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DOSX_SDK=10.12 ..
```
3. Pick a method to build and package your release.

View file

@ -6,10 +6,6 @@ if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
message( FATAL_ERROR "Only 64 bit builds supported." )
endif()
if (USE_CCACHE OR "$ENV{USE_CCACHE}")
configure_ccache()
endif()
if (WIN32)
add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)

View file

@ -1,45 +0,0 @@
#
# ConfigureCCache.cmake
# cmake/macros
#
# Created by Clement Brisset on 10/10/18.
# Copyright 2018 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
#
macro(configure_ccache)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
message(STATUS "Configuring ccache")
# Set up wrapper scripts
set(C_LAUNCHER "${CCACHE_PROGRAM}")
set(CXX_LAUNCHER "${CCACHE_PROGRAM}")
set(LAUNCH_C_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-c.in")
set(LAUNCH_CXX_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-cxx.in")
set(LAUNCH_C "${CMAKE_BINARY_DIR}/CMakeFiles/launch-c")
set(LAUNCH_CXX "${CMAKE_BINARY_DIR}/CMakeFiles/launch-cxx")
configure_file(${LAUNCH_C_IN} ${LAUNCH_C})
configure_file(${LAUNCH_CXX_IN} ${LAUNCH_CXX})
execute_process(COMMAND chmod a+rx ${LAUNCH_C} ${LAUNCH_CXX})
if(CMAKE_GENERATOR STREQUAL "Xcode")
# Set Xcode project attributes to route compilation and linking
# through our scripts
set(CMAKE_XCODE_ATTRIBUTE_CC ${LAUNCH_C})
set(CMAKE_XCODE_ATTRIBUTE_CXX ${LAUNCH_CXX})
set(CMAKE_XCODE_ATTRIBUTE_LD ${LAUNCH_C})
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${LAUNCH_CXX})
else()
# Support Unix Makefiles and Ninja
set(CMAKE_C_COMPILER_LAUNCHER ${LAUNCH_C})
set(CMAKE_CXX_COMPILER_LAUNCHER ${LAUNCH_CXX})
endif()
else()
message(WARNING "Could not find ccache")
endif()
endmacro()

View file

@ -23,7 +23,6 @@ macro(SET_PACKAGING_PARAMETERS)
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
set_from_env(RELEASE_NAME RELEASE_NAME "")
set_from_env(STABLE_BUILD STABLE_BUILD 0)
set_from_env(PRELOADED_STARTUP_LOCATION PRELOADED_STARTUP_LOCATION "")

View file

@ -0,0 +1,38 @@
diff --git a/artery-font/serialization.hpp b/artery-font/serialization.hpp
index 69263a8..6075eda 100644
--- a/artery-font/serialization.hpp
+++ b/artery-font/serialization.hpp
@@ -109,15 +109,16 @@ template <ReadFunction READ, typename REAL, template <typename> class LIST, clas
bool decode(ArteryFont<REAL, LIST, BYTE_ARRAY, STRING> &font, void *userData) {
uint32 totalLength = 0;
uint32 prevLength = 0;
- uint32 checksum = crc32Init();
+ //uint32 checksum = crc32Init();
byte dump[4];
#define ARTERY_FONT_DECODE_READ(target, len) { \
if (READ((target), (len), userData) != int(len)) \
return false; \
totalLength += (len); \
- for (int _i = 0; _i < int(len); ++_i) \
- checksum = crc32Update(checksum, reinterpret_cast<const byte *>(target)[_i]); \
}
+ // for (int _i = 0; _i < int(len); ++_i) \
+ // checksum = crc32update(checksum, reinterpret_cast<const byte *>(target)[_i]); \
+ //}
#define ARTERY_FONT_DECODE_REALIGN() { \
if (totalLength&0x03u) { \
uint32 len = 0x04u-(totalLength&0x03u); \
@@ -228,10 +229,10 @@ bool decode(ArteryFont<REAL, LIST, BYTE_ARRAY, STRING> &font, void *userData) {
ARTERY_FONT_DECODE_READ(&footer, sizeof(footer)-sizeof(footer.checksum));
if (footer.magicNo != ARTERY_FONT_FOOTER_MAGIC_NO)
return false;
- uint32 finalChecksum = checksum;
+ //uint32 finalChecksum = checksum;
ARTERY_FONT_DECODE_READ(&footer.checksum, sizeof(footer.checksum));
- if (footer.checksum != finalChecksum)
- return false;
+ //if (footer.checksum != finalChecksum)
+ // return false;
if (totalLength != footer.totalLength)
return false;
}

View file

@ -0,0 +1,15 @@
# header-only library
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO Chlumsky/artery-font-format
REF 34134bde3cea35a93c2ae5703fa8d3d463793400
SHA512 6b2fc0de9ca7b367c9b98f829ce6cee858f1252b12a49b6f1e89a5a2fdb109e20ef812f0b30495195ca0b177adae32d5e238fdc305724857ced098be2d29a6af
HEAD_REF master
PATCHES "disable-checksum.patch"
)
file(COPY "${SOURCE_PATH}/artery-font" DESTINATION "${CURRENT_PACKAGES_DIR}/include")
# Handle copyright
configure_file("${SOURCE_PATH}/LICENSE.txt" "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" COPYONLY)

View file

@ -0,0 +1,7 @@
{
"name": "artery-font-format",
"version": "1.0.1",
"description": "Header-only C++ library that facilitates encoding and decoding of the Artery Atlas Font format",
"homepage": "https://github.com/Chlumsky/artery-font-format",
"license": "MIT"
}

View file

@ -5,4 +5,4 @@
Source: hifi-deps
Version: 0.1.5-github-actions
Description: Collected dependencies for High Fidelity applications
Build-Depends: bullet3, cgltf, draco, etc2comp, glad, glm, node, nvtt, openexr (!android), openssl (windows), opus, polyvox, tbb (!android), vhacd, webrtc (!android|!(linux&arm)), zlib
Build-Depends: artery-font-format, bullet3, cgltf, draco, etc2comp, glad, glm, node, nvtt, openexr (!android), openssl (windows), opus, polyvox, tbb (!android), vhacd, webrtc (!android|!(linux&arm)), zlib

View file

@ -25,7 +25,6 @@ namespace BuildInfo {
const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@";
const QString ORGANIZATION_DOMAIN = "overte.org";
const QString VERSION = "@BUILD_VERSION@";
const QString RELEASE_NAME = "@RELEASE_NAME@";
const QString BUILD_NUMBER = "@BUILD_NUMBER@";
const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@";
const QString BUILD_TIME = "@BUILD_TIME@";

View file

@ -1,12 +0,0 @@
#!/bin/sh
# Xcode generator doesn't include the compiler as the
# first argument, Ninja and Makefiles do. Handle both cases.
if [[ "$1" = "${CMAKE_C_COMPILER}" ]] ; then
shift
fi
export CCACHE_CPP2=true
export CCACHE_HARDLINK=true
export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches
exec "${C_LAUNCHER}" "${CMAKE_C_COMPILER}" "$@"

View file

@ -1,12 +0,0 @@
#!/bin/sh
# Xcode generator doesn't include the compiler as the
# first argument, Ninja and Makefiles do. Handle both cases.
if [[ "$1" = "${CMAKE_CXX_COMPILER}" ]] ; then
shift
fi
export CCACHE_CPP2=true
export CCACHE_HARDLINK=true
export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches
exec "${CXX_LAUNCHER}" "${CMAKE_CXX_COMPILER}" "$@"

View file

@ -157,7 +157,7 @@ endif()
u_major = int( distro.major_version() or '0' )
if distro.id() == 'ubuntu' or distro.id() == 'linuxmint':
if (distro.id() == 'ubuntu' and u_major == 20) or distro.id() == 'linuxmint' and u_major == 20:
self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.10-2023.10.01-kde_d2122ee587cceb5b2f4130b7074f86db9aca570e-ubuntu-20.04-amd64.tar.xz'
self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.14-2024.06.17-kde_570f5b2105df1ea052bec0d6dbf8a00137274371-ubuntu-20.04-amd64.tar.xz'
elif (distro.id() == 'ubuntu' and u_major > 20) or (distro.id() == 'linuxmint' and u_major > 20):
self.__no_qt_package_error()
else:
@ -170,9 +170,7 @@ endif()
if distro.id() == 'ubuntu':
u_major = int( distro.major_version() )
if u_major == 18:
self.qtUrl = 'http://motofckr9k.ddns.net/vircadia_packages/qt5-install-5.15.2-ubuntu-18.04-aarch64_test.tar.xz'
elif u_major == 20:
if u_major == 20:
self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2-ubuntu-20.04-aarch64.tar.xz'
elif u_major > 20:
self.__no_qt_package_error()
@ -182,9 +180,7 @@ endif()
elif distro.id() == 'debian':
u_major = int( distro.major_version() )
if u_major == 10:
self.qtUrl = 'https://data.moto9000.moe/vircadia_packages/qt5-install-5.15.2-debian-10-aarch64.tar.xz'
elif u_major > 10:
if u_major > 10:
self.__no_qt_package_error()
else:
self.__unsupported_error()

View file

@ -4,7 +4,6 @@
//
// Created by Vlad Stelmahovsky on 15/5/2018.
// Copyright 2018 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -43,10 +42,6 @@ QString AboutUtil::getBuildVersion() const {
return BuildInfo::VERSION;
}
QString AboutUtil::getReleaseName() const {
return BuildInfo::RELEASE_NAME;
}
QString AboutUtil::getQtVersion() const {
return qVersion();
}

View file

@ -30,7 +30,6 @@
* <em>Read-only.</em>
* @property {string} buildDate - The build date of Interface that is currently running. <em>Read-only.</em>
* @property {string} buildVersion - The build version of Interface that is currently running. <em>Read-only.</em>
* @property {string} releaseName - The release codename of the version that Interface is currently running. <em>Read-only.</em>
* @property {string} qtVersion - The Qt version used in Interface that is currently running. <em>Read-only.</em>
* @property {string} qtWebEngineVersion - The Qt WebEngine version used in Interface that is currently running. <em>Read-only.</em>
* @property {string} qtChromiumVersion - The Qt Chromium version used in Interface that is currently running. <em>Read-only.</em>
@ -40,7 +39,6 @@
* print("Interface platform: " + About.platform);
* print("Interface build date: " + About.buildDate);
* print("Interface version: " + About.buildVersion);
* print("Interface release name: " + About.releaseName);
* print("Qt version: " + About.qtVersion);
*/
@ -71,7 +69,6 @@ class AboutUtil : public QObject {
Q_PROPERTY(QString platform READ getPlatformName CONSTANT)
Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT)
Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT)
Q_PROPERTY(QString releaseName READ getReleaseName CONSTANT)
Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT)
Q_PROPERTY(QString qtWebEngineVersion READ getQtWebEngineVersion CONSTANT)
Q_PROPERTY(QString qtChromiumVersion READ getQtChromiumVersion CONSTANT)
@ -83,7 +80,6 @@ public:
QString getPlatformName() const { return "Overte"; }
QString getBuildDate() const;
QString getBuildVersion() const;
QString getReleaseName() const;
QString getQtVersion() const;
QString getQtWebEngineVersion() const;
QString getQtChromiumVersion() const;

View file

@ -2480,7 +2480,7 @@ void Application::initialize(const QCommandLineParser &parser) {
// Setup the mouse ray pick and related operators
{
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::getPickEntities() | PickScriptingInterface::getPickLocalEntities()), 0.0f, true);
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::getPickEntities() | PickScriptingInterface::getPickLocalEntities()), 0.0f, 0.0f, true);
mouseRayPick->parentTransform = std::make_shared<MouseTransformNode>();
mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE);
auto mouseRayPickID = DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, mouseRayPick);
@ -7251,10 +7251,6 @@ void Application::updateWindowTitle() const {
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
+ " " + applicationVersion();
if (BuildInfo::RELEASE_NAME != "") {
buildVersion += " - " + BuildInfo::RELEASE_NAME;
}
QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" :
nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
@ -7657,7 +7653,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptManagerPoint
{
auto connection = std::make_shared<QMetaObject::Connection>();
*connection = scriptManager->connect(scriptManager.get(), &ScriptManager::scriptEnding, [scriptManager, connection]() {
*connection = scriptManager->connect(scriptManager.get(), &ScriptManager::scriptEnding, [this, scriptManager, connection]() {
// Request removal of controller routes with callbacks to a given script engine
auto userInputMapper = DependencyManager::get<UserInputMapper>();
// scheduleScriptEndpointCleanup will have the last instance of shared pointer to script manager
@ -8598,6 +8594,14 @@ SharedSoundPointer Application::getSampleSound() const {
return _sampleSound;
}
void Application::showVRKeyboardForHudUI(bool show) {
if (show) {
DependencyManager::get<Keyboard>()->setRaised(true, true);
} else {
DependencyManager::get<Keyboard>()->setRaised(false);
}
}
void Application::loadLODToolsDialog() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));

View file

@ -431,6 +431,16 @@ public slots:
Q_INVOKABLE void loadAddAvatarBookmarkDialog() const;
Q_INVOKABLE void loadAvatarBrowser() const;
Q_INVOKABLE SharedSoundPointer getSampleSound() const;
/**
* @brief Shows/hides VR keyboard input for Overlay windows
*
* This is used by QML scripts to show and hide VR keyboard. Unlike JS API Keyboard.raised = true,
* with showVRKeyboardForHudUI the input is passed to the active window on the overlay first.
*
* @param show
* If set to true, then keyboard is shown, for false it's hidden.
*/
Q_INVOKABLE void showVRKeyboardForHudUI(bool show);
void showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const QString& name) const;

View file

@ -64,7 +64,7 @@ void LODManager::setRenderTimes(float presentTime, float engineRunTime, float ba
}
void LODManager::autoAdjustLOD(float realTimeDelta) {
std::lock_guard<std::mutex> lock{ _automaticLODLock };
std::lock_guard<std::mutex> lock(_automaticLODLock);
// The "render time" is the worse of:
// - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N
@ -300,7 +300,7 @@ void LODManager::resetLODAdjust() {
}
void LODManager::setAutomaticLODAdjust(bool value) {
std::lock_guard<std::mutex> lock{ _automaticLODLock };
std::lock_guard<std::mutex> lock(_automaticLODLock);
_automaticLODAdjust = value;
saveSettings();
emit autoLODChanged();

View file

@ -5768,8 +5768,7 @@ void MyAvatar::FollowHelper::deactivate() {
}
void MyAvatar::FollowHelper::deactivate(CharacterController::FollowType type) {
int int_type = static_cast<int>(type);
assert(int_type >= 0 && int_type < static_cast<int>(CharacterController::FollowType::Count));
assert(type < CharacterController::FollowType::Count);
_timeRemaining[(int)type] = 0.0f;
}
@ -5777,16 +5776,14 @@ void MyAvatar::FollowHelper::deactivate(CharacterController::FollowType type) {
// eg. activate(FollowType::Rotation, true) snaps the FollowHelper's rotation immediately
// to the rotation of its _followDesiredBodyTransform.
void MyAvatar::FollowHelper::activate(CharacterController::FollowType type, const bool snapFollow) {
int int_type = static_cast<int>(type);
assert(int_type >= 0 && int_type < static_cast<int>(CharacterController::FollowType::Count));
assert(type < CharacterController::FollowType::Count);
// TODO: Perhaps, the follow time should be proportional to the displacement.
_timeRemaining[(int)type] = snapFollow ? CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP : FOLLOW_TIME;
}
bool MyAvatar::FollowHelper::isActive(CharacterController::FollowType type) const {
int int_type = static_cast<int>(type);
assert(int_type >= 0 && int_type < static_cast<int>(CharacterController::FollowType::Count));
assert(type < CharacterController::FollowType::Count);
return _timeRemaining[(int)type] > 0.0f;
}

View file

@ -33,14 +33,19 @@ PickQuery::PickType LaserPointer::getType() const {
}
void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
//V8TODO pathProps are not a thing anymore
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
if (renderState) {
updateRenderState(renderState->getPathID(), pathProps);
QVariant lineWidth = pathProps.toMap()["lineWidth"];
QVariantMap pathPropsMap = pathProps.toMap();
QVariant lineWidth = pathPropsMap["lineWidth"];
if (lineWidth.isValid()) {
renderState->setLineWidth(lineWidth.toFloat());
}
if (pathPropsMap.contains("linePoints")) {
QVariantList linePoints = pathPropsMap["linePoints"].toList();
renderState->setNumPoints(linePoints.length());
}
}
}
@ -133,18 +138,18 @@ LaserPointer::RenderState::RenderState(const QUuid& startID, const QUuid& pathID
StartEndRenderState(startID, endID), _pathID(pathID)
{
if (!getPathID().isNull()) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
{
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_IGNORE_PICK_INTERSECTION;
_pathIgnorePicks = entityScriptingInterface->getEntityPropertiesInternal(getPathID(), desiredProperties, false).getIgnorePickIntersection();
}
{
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_STROKE_WIDTHS;
auto widths = entityScriptingInterface->getEntityPropertiesInternal(getPathID(), desiredProperties, false).getStrokeWidths();
_lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0];
}
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_IGNORE_PICK_INTERSECTION;
desiredProperties += PROP_LINE_POINTS;
desiredProperties += PROP_STROKE_WIDTHS;
auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityPropertiesInternal(getPathID(), desiredProperties, false);
auto widths = properties.getStrokeWidths();
_lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0];
setNumPoints(properties.getLinePoints().length());
_pathIgnorePicks = properties.getIgnorePickIntersection();
}
}
@ -171,28 +176,52 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3&
if (!getPathID().isNull()) {
EntityItemProperties properties;
QVector<glm::vec3> points;
const size_t numPoints = getNumPoints();
points.append(glm::vec3(0.0f));
points.append(end - origin);
const glm::vec3 endPoint = end - origin;
if (numPoints > 2) {
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_VISIBLE;
auto oldProperties = DependencyManager::get<EntityScriptingInterface>()->getEntityPropertiesInternal(getPathID(), desiredProperties, false);
bool hasUnmodifiedEndPoint = false;
glm::vec3 unmodifiedEndPoint;
float directionLength = glm::length(endPoint);
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
if (rayPickResult && rayPickResult->pickVariant.contains("unmodifiedDirection")) {
unmodifiedEndPoint = directionLength * vec3FromVariant(rayPickResult->pickVariant["unmodifiedDirection"]);
hasUnmodifiedEndPoint = true;
}
// Segment points are evenly spaced between origin and end
for (size_t i = 1; i < numPoints - 1; i++) {
const float frac = ((float)i / (numPoints - 1));
if (!oldProperties.getVisible() || !_hasSetLinePoints || !hasUnmodifiedEndPoint) {
points.append(frac * endPoint);
} else {
points.append(frac * directionLength * glm::normalize(glm::mix(unmodifiedEndPoint, endPoint, frac)));
}
}
_hasSetLinePoints = true;
}
points.append(endPoint);
properties.setPosition(origin);
properties.setRotation(glm::quat(1.0f, 0.0f ,0.0f ,0.0f));
properties.setRotation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f));
properties.setLinePoints(points);
properties.setVisible(true);
properties.setIgnorePickIntersection(doesPathIgnorePicks());
QVector<glm::vec3> normals;
normals.append(glm::vec3(0.0f, 0.0f, 1.0f));
normals.append(glm::vec3(0.0f, 0.0f, 1.0f));
normals.fill(glm::vec3(0.0f, 0.0f, 1.0f), (int)numPoints);
properties.setNormals(normals);
QVector<float> widths;
float width = getLineWidth() * parentScale;
widths.append(width);
widths.append(width);
widths.fill(width, (int)numPoints);
properties.setStrokeWidths(widths);
DependencyManager::get<EntityScriptingInterface>()->editEntity(getPathID(), properties);
}
}
std::shared_ptr<StartEndRenderState> LaserPointer::buildRenderState(const QVariantMap& propMap, const QList<EntityItemProperties> &entityProperties) {
// FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers
QUuid startID;
if (propMap["startPropertyIndex"].isValid()) {
int startPropertyIndex = propMap["startPropertyIndex"].toInt();
@ -205,11 +234,11 @@ std::shared_ptr<StartEndRenderState> LaserPointer::buildRenderState(const QVaria
QUuid pathID;
if (propMap["pathPropertyIndex"].isValid()) {
// laser paths must be PolyLine
int pathPropertyIndex = propMap["pathPropertyIndex"].toInt();
if (pathPropertyIndex >= 0 && pathPropertyIndex < entityProperties.length()) {
//pathMap["type"].toString() == "PolyLine"
EntityItemProperties pathProperties(entityProperties[pathPropertyIndex]);
// laser paths must be PolyLine
pathProperties.setType(EntityTypes::EntityType::PolyLine);
pathProperties.getGrab().setGrabbable(false);
pathID = DependencyManager::get<EntityScriptingInterface>()->addEntityInternal(pathProperties, entity::HostType::LOCAL);
}

View file

@ -13,7 +13,7 @@
#include "PathPointer.h"
#include<EntityItemProperties.h>
#include <EntityItemProperties.h>
class LaserPointer : public PathPointer {
using Parent = PathPointer;
@ -24,21 +24,29 @@ public:
RenderState(const QUuid& startID, const QUuid& pathID, const QUuid& endID);
const QUuid& getPathID() const { return _pathID; }
const bool& doesPathIgnorePicks() const { return _pathIgnorePicks; }
void setLineWidth(float width) { _lineWidth = width; }
float getLineWidth() const { return _lineWidth; }
void setNumPoints(size_t numPoints) { _numPoints = std::max(numPoints, (size_t)2); }
size_t getNumPoints() const { return _numPoints; }
const bool& doesPathIgnorePicks() const { return _pathIgnorePicks; }
void cleanup() override;
void disable() override;
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd,
bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance,
const PickResultPointer& pickResult) override;
private:
QUuid _pathID;
bool _pathIgnorePicks;
float _lineWidth;
float _lineWidth { 0.0f };
size_t _numPoints { 0 };
bool _pathIgnorePicks { false };
bool _hasSetLinePoints { false };
};
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
@ -67,7 +75,6 @@ protected:
private:
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
};
#endif // hifi_LaserPointer_h

View file

@ -207,9 +207,11 @@ void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float al
}
}
void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
StartEndRenderState::update(origin, end, surfaceNormal, parentScale, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd,
bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance,
const PickResultPointer& pickResult) {
StartEndRenderState::update(origin, end, surfaceNormal, parentScale, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength,
distance, pickResult);
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult && render::Item::isValidID(_pathID)) {
render::Transaction transaction;

View file

@ -144,13 +144,11 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
auto renderState = _renderStates.find(_currentRenderState);
auto defaultRenderState = _defaultRenderStates.find(_currentRenderState);
float parentScale = 1.0f;
//if (_scaleWithParent) {
if (_enabled && _scaleWithParent) {
glm::vec3 dimensions = DependencyManager::get<PickManager>()->getParentTransform(_pickUID).getScale();
parentScale = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z);
}
//if (!_currentRenderState.empty() && renderState != _renderStates.end() &&
if (_enabled && !_currentRenderState.empty() && renderState != _renderStates.end() &&
(type != IntersectionType::NONE || _pathLength > 0.0f)) {
glm::vec3 origin = getPickOrigin(pickResult);
@ -162,14 +160,14 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
defaultRenderState->second.second->disable();
}
} else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) {
//} else if (!_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) {
if (renderState != _renderStates.end() && renderState->second->isEnabled()) {
renderState->second->disable();
}
glm::vec3 origin = getPickOrigin(pickResult);
glm::vec3 end = getPickEnd(pickResult, defaultRenderState->second.first);
defaultRenderState->second.second->update(origin, end, Vectors::UP, parentScale, _distanceScaleEnd, _centerEndY,
_faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first, pickResult);
_faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first,
pickResult);
} else if (!_currentRenderState.empty()) {
if (renderState != _renderStates.end() && renderState->second->isEnabled()) {
renderState->second->disable();
@ -205,12 +203,12 @@ void PathPointer::editRenderState(const std::string& state, const QVariant& star
}
void PathPointer::updateRenderState(const QUuid& id, const QVariant& props) {
// FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
// FIXME: updating render state props no longer works since removing 3D Overlays
//if (!id.isNull() && props.isValid()) {
// QVariantMap propMap = props.toMap();
// propMap.remove("visible");
// qApp->getOverlays().editOverlay(id, propMap);
//}
}
Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) {
@ -300,8 +298,9 @@ void StartEndRenderState::disable() {
_enabled = false;
}
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd,
bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance,
const PickResultPointer& pickResult) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (!getStartID().isNull()) {
EntityItemProperties properties;

View file

@ -43,16 +43,17 @@ public:
virtual void cleanup();
virtual void disable();
virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult);
virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd,
bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance,
const PickResultPointer& pickResult);
bool isEnabled() const { return _enabled; }
protected:
QUuid _startID;
QUuid _endID;
bool _startIgnorePicks;
bool _endIgnorePicks;
bool _startIgnorePicks { false };
bool _endIgnorePicks { false };
glm::vec3 _startDim;
glm::vec3 _endDim;

View file

@ -121,6 +121,7 @@ PickFilter getPickFilter(unsigned int filter) {
* @property {Vec3} [dirOffset] - Synonym for <code>direction</code>.
* @property {Quat} [orientation] - Alternative property for specifying <code>direction</code>. The value is applied to the
* default <code>direction</code> value.
* @property {number} [delay=0.0] - The delay, in seconds, to apply to the ray direction.
* @property {PickType} pickType - The type of pick when getting these properties from {@link Picks.getPickProperties} or
* {@link Picks.getPickScriptParameters}. A ray pick's type is {@link PickType.Ray}.
* @property {Vec3} baseScale - Returned from {@link Picks.getPickProperties} when the pick has a parent with varying scale
@ -173,7 +174,14 @@ std::shared_ptr<PickQuery> PickScriptingInterface::buildRayPick(const QVariantMa
direction = vec3FromVariant(propMap["dirOffset"]);
}
auto rayPick = std::make_shared<RayPick>(position, direction, filter, maxDistance, enabled);
float delayHalf = 0.0f;
if (propMap["delay"].isValid()) {
// We want to be within 0.1% of the target in <delay> seconds
// https://twitter.com/FreyaHolmer/status/1757836988495847568
delayHalf = -std::max(propMap["delay"].toFloat(), 0.0f) / log2(0.001);
}
auto rayPick = std::make_shared<RayPick>(position, direction, filter, maxDistance, delayHalf, enabled);
setParentTransform(rayPick, propMap);
return rayPick;
@ -455,6 +463,10 @@ void PickScriptingInterface::setIncludeItems(unsigned int uid, const ScriptValue
DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
void PickScriptingInterface::setDelay(unsigned int uid, float delay) {
DependencyManager::get<PickManager>()->setDelay(uid, delay);
}
bool PickScriptingInterface::isLeftHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isLeftHand(uid);
}
@ -497,6 +509,15 @@ void PickScriptingInterface::setPerFrameTimeBudget(unsigned int numUsecs) {
DependencyManager::get<PickManager>()->setPerFrameTimeBudget(numUsecs);
}
float PickScriptingInterface::getHandLaserDelay() const {
return _handLaserDelaySetting.get();
}
void PickScriptingInterface::setHandLaserDelay(float delay) {
_handLaserDelaySetting.set(delay);
emit handLaserDelayChanged(delay);
}
void PickScriptingInterface::setParentTransform(std::shared_ptr<PickQuery> pick, const QVariantMap& propMap) {
QUuid parentUuid;
int parentJointIndex = 0;

View file

@ -16,6 +16,7 @@
#include <PhysicsEngine.h>
#include <Pick.h>
#include <PickFilter.h>
#include <SettingHandle.h>
class ScriptEngine;
class ScriptValue;
@ -72,6 +73,7 @@ class ScriptValue;
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD surface. <em>Read-only.</em>
*
* @property {number} perFrameTimeBudget - The maximum time, in microseconds, to spend per frame updating pick results.
* @property {number} handLaserDelay - The delay, in seconds, applied to the hand lasers to smooth their movement.
*/
class PickScriptingInterface : public QObject, public Dependency {
@ -105,6 +107,7 @@ class PickScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ getIntersectedAvatar CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_HUD READ getIntersectedHud CONSTANT)
Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget)
Q_PROPERTY(float handLaserDelay READ getHandLaserDelay WRITE setHandLaserDelay)
SINGLETON_DEPENDENCY
public:
@ -263,6 +266,15 @@ public:
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeItems);
/*@jsdoc
* Sets the delay of a Ray pick.
* <p><strong>Note:</strong> Not used by other pick types.</p>
* @function Picks.setDelay
* @param {number} id - The ID of the pointer.
* @param {number} delay - The desired delay in seconds.
*/
Q_INVOKABLE void setDelay(unsigned int uid, float delay);
/*@jsdoc
* Checks if a pick is associated with the left hand: a ray or parabola pick with <code>joint</code> property set to
* <code>"_CONTROLLER_LEFTHAND"</code> or <code>"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"</code>, or a stylus pick with
@ -295,6 +307,9 @@ public:
unsigned int getPerFrameTimeBudget() const;
void setPerFrameTimeBudget(unsigned int numUsecs);
float getHandLaserDelay() const;
void setHandLaserDelay(float delay);
public slots:
static constexpr unsigned int getPickBypassIgnore() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_BYPASS_IGNORE); }
@ -461,6 +476,9 @@ public slots:
*/
static constexpr unsigned int getIntersectedHud() { return IntersectionType::HUD; }
signals:
void handLaserDelayChanged(float delay);
protected:
static std::shared_ptr<PickQuery> buildRayPick(const QVariantMap& properties);
static std::shared_ptr<PickQuery> buildStylusPick(const QVariantMap& properties);
@ -468,6 +486,9 @@ protected:
static std::shared_ptr<PickQuery> buildParabolaPick(const QVariantMap& properties);
static void setParentTransform(std::shared_ptr<PickQuery> pick, const QVariantMap& propMap);
private:
Setting::Handle<float> _handLaserDelaySetting { "handLaserDelay", 0.35f };
};
#endif // hifi_PickScriptingInterface_h

View file

@ -25,7 +25,6 @@
#include <ScriptManager.h>
STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){
qDebug() << "STATIC_SCRIPT_TYPES_INITIALIZER PointerScriptingInterface";
auto scriptEngine = manager->engine().get();
scriptRegisterMetaType<RayPointerProperties, rayPointerPropertiesToScriptValue, rayPointerPropertiesFromScriptValue>(scriptEngine);
scriptRegisterMetaType<StylusPointerProperties, stylusPointerPropertiesToScriptValue, stylusPointerPropertiesFromScriptValue>(scriptEngine);
@ -92,7 +91,6 @@ unsigned int PointerScriptingInterface::createParabolaPointer(ParabolaPointerPro
return createPointerInternal(PickQuery::PickType::Parabola, properties);
}
bool PointerScriptingInterface::isPointerEnabled(unsigned int uid) const {
return DependencyManager::get<PointerManager>()->isPointerEnabled(uid);
}
@ -178,45 +176,45 @@ std::shared_ptr<Pointer> PointerScriptingInterface::buildStylus(const PointerPro
* Properties that define the visual appearance of a ray pointer when the pointer is intersecting something.
* @typedef {object} Pointers.RayPointerRenderState
* @property {string} name - When creating using {@link Pointers.createPointer}, the name of the render state.
* @property {Overlays.OverlayProperties|Uuid} [start]
* @property {Entities.EntityProperties|Uuid} [start]
* <p>When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of
* an overlay to render at the start of the ray pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the start of the ray;
* <code>null</code> if there is no overlay.
* an entity to render at the start of the ray pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the start of the ray;
* <code>null</code> if there is no entity.
*
* @property {Overlays.OverlayProperties|Uuid} [path]
* @property {Entities.EntityProperties|Uuid} [path]
* <p>When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of
* the overlay rendered for the path of the ray pointer. The <code>type</code> property must be specified and be
* <code>"line3d"</code>.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered for the path of the ray;
* <code>null</code> if there is no overlay.
* the entity rendered for the path of the ray pointer. The <code>type</code> property must be specified and be
* <code>"PolyLine"</code>.</p> Specify <code>linePoints</code> to control how many segments make up the path.
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered for the path of the ray;
* <code>null</code> if there is no entity.
*
* @property {Overlays.OverlayProperties|Uuid} [end]
* @property {Entities.EntityProperties|Uuid} [end]
* <p>When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of
* an overlay to render at the end of the ray pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the end of the ray;
* <code>null</code> if there is no overlay.
* an entity to render at the end of the ray pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the end of the ray;
* <code>null</code> if there is no entity.
*/
/*@jsdoc
* The properties of a ray pointer. These include the properties from the underlying ray pick that the pointer uses.
* @typedef {object} Pointers.RayPointerProperties
* @property {boolean} [faceAvatar=false] - <code>true</code> if the overlay rendered at the end of the ray rotates about the
* @property {boolean} [faceAvatar=false] - <code>true</code> if the entity rendered at the end of the ray rotates about the
* world y-axis to always face the avatar; <code>false</code> if it maintains its world orientation.
* @property {boolean} [centerEndY=true] - <code>true</code> if the overlay rendered at the end of the ray is centered on
* the ray end; <code>false</code> if the overlay is positioned against the surface if <code>followNormal</code> is
* @property {boolean} [centerEndY=true] - <code>true</code> if the entity rendered at the end of the ray is centered on
* the ray end; <code>false</code> if the entity is positioned against the surface if <code>followNormal</code> is
* <code>true</code>, or above the ray end if <code>followNormal</code> is <code>false</code>.
* @property {boolean} [lockEnd=false] - <code>true</code> if the end of the ray is locked to the center of the object at
* which the ray is pointing; <code>false</code> if the end of the ray is at the intersected surface.
* @property {boolean} [distanceScaleEnd=false] - <code>true</code> if the dimensions of the overlay at the end of the ray
* @property {boolean} [distanceScaleEnd=false] - <code>true</code> if the dimensions of the entity at the end of the ray
* scale linearly with distance; <code>false</code> if they aren't.
* @property {boolean} [scaleWithParent=false] - <code>true</code> if the width of the ray's path and the size of the
* start and end overlays scale linearly with the pointer parent's scale; <code>false</code> if they don't scale.
* start and end entities scale linearly with the pointer parent's scale; <code>false</code> if they don't scale.
* @property {boolean} [scaleWithAvatar=false] - A synonym for <code>scalewithParent</code>.
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* @property {boolean} [followNormal=false] - <code>true</code> if the overlay rendered at the end of the ray rotates to
* @property {boolean} [followNormal=false] - <code>true</code> if the entity rendered at the end of the ray rotates to
* follow the normal of the surface if one is intersected; <code>false</code> if it doesn't.
* @property {number} [followNormalStrength=0.0] - How quickly the overlay rendered at the end of the ray rotates to follow
* the normal of an intersected surface. If <code>0</code> or <code>1</code>, the overlay rotation follows instantaneously;
* @property {number} [followNormalStrength=0.0] - How quickly the entity rendered at the end of the ray rotates to follow
* the normal of an intersected surface. If <code>0</code> or <code>1</code>, the entity rotation follows instantaneously;
* for other values, the larger the value the more quickly the rotation follows.
* @property {Pointers.RayPointerRenderState[]|Object.<string, Pointers.RayPointerRenderState>} [renderStates]
* <p>A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual
@ -236,7 +234,7 @@ std::shared_ptr<Pointer> PointerScriptingInterface::buildStylus(const PointerPro
* @property {boolean} [hover=false] - <code>true</code> if the pointer generates {@link Entities} hover events,
* <code>false</code> if it doesn't.
* @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger
* events on the entity or overlay currently intersected.
* events on the object currently intersected.
* @property {PickType} pointerType - The type of pointer returned from {@link Pointers.getPointerProperties} or
* {@link Pointers.getPointerScriptParameters}. A laser pointer's type is {@link PickType(0)|PickType.Ray}.
* @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from
@ -378,41 +376,41 @@ std::shared_ptr<Pointer> PointerScriptingInterface::buildLaserPointer(const Poin
* Properties that define the visual appearance of a parabola pointer when the pointer is intersecting something.
* @typedef {object} Pointers.ParabolaPointerRenderState
* @property {string} name - When creating using {@link Pointers.createPointer}, the name of the render state.
* @property {Overlays.OverlayProperties|Uuid} [start]
* @property {Entities.EntityProperties|Uuid} [start]
* <p>When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of
* an overlay to render at the start of the parabola pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the start of the
* parabola; <code>null</code> if there is no overlay.
* an entity to render at the start of the parabola pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the start of the
* parabola; <code>null</code> if there is no entity.
* @property {Pointers.ParabolaPointerPath|Uuid} [path]
* <p>When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of
* the rendered path of the parabola pointer.</p>
* <p>This property is not provided when getting using {@link Pointers.getPointerProperties}.
* @property {Overlays.OverlayProperties|Uuid} [end]
* @property {Entities.EntityProperties|Uuid} [end]
* <p>When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of
* an overlay to render at the end of the ray pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the end of the parabola;
* <code>null</code> if there is no overlay.
* an entity to render at the end of the ray pointer. The <code>type</code> property must be specified.</p>
* <p>When getting using {@link Pointers.getPointerProperties}, the ID of the entity rendered at the end of the parabola;
* <code>null</code> if there is no entity.
*/
/*@jsdoc
* The properties of a parabola pointer. These include the properties from the underlying parabola pick that the pointer uses.
* @typedef {object} Pointers.ParabolaPointerProperties
* @property {boolean} [faceAvatar=false] - <code>true</code> if the overlay rendered at the end of the ray rotates about the
* @property {boolean} [faceAvatar=false] - <code>true</code> if the entity rendered at the end of the ray rotates about the
* world y-axis to always face the avatar; <code>false</code> if it maintains its world orientation.
* @property {boolean} [centerEndY=true] - <code>true</code> if the overlay rendered at the end of the ray is centered on
* the ray end; <code>false</code> if the overlay is positioned against the surface if <code>followNormal</code> is
* @property {boolean} [centerEndY=true] - <code>true</code> if the entity rendered at the end of the ray is centered on
* the ray end; <code>false</code> if the entity is positioned against the surface if <code>followNormal</code> is
* <code>true</code>, or above the ray end if <code>followNormal</code> is <code>false</code>.
* @property {boolean} [lockEnd=false] - <code>true</code> if the end of the ray is locked to the center of the object at
* which the ray is pointing; <code>false</code> if the end of the ray is at the intersected surface.
* @property {boolean} [distanceScaleEnd=false] - <code>true</code> if the dimensions of the overlay at the end of the ray
* @property {boolean} [distanceScaleEnd=false] - <code>true</code> if the dimensions of the entity at the end of the ray
* scale linearly with distance; <code>false</code> if they aren't.
* @property {boolean} [scaleWithParent=false] - <code>true</code> if the width of the ray's path and the size of the
* start and end overlays scale linearly with the pointer parent's scale; <code>false</code> if they don't scale.
* start and end entities scale linearly with the pointer parent's scale; <code>false</code> if they don't scale.
* @property {boolean} [scaleWithAvatar=false] - A synonym for <code>scalewithParent</code>.
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* @property {boolean} [followNormal=false] - <code>true</code> if the overlay rendered at the end of the ray rotates to
* @property {boolean} [followNormal=false] - <code>true</code> if the entity rendered at the end of the ray rotates to
* follow the normal of the surface if one is intersected; <code>false</code> if it doesn't.
* @property {number} [followNormalStrength=0.0] - How quickly the overlay rendered at the end of the ray rotates to follow
* the normal of an intersected surface. If <code>0</code> or <code>1</code>, the overlay rotation follows instantaneously;
* @property {number} [followNormalStrength=0.0] - How quickly the entity rendered at the end of the ray rotates to follow
* the normal of an intersected surface. If <code>0</code> or <code>1</code>, the entity rotation follows instantaneously;
* for other values, the larger the value the more quickly the rotation follows.
* @property {Pointers.ParabolaPointerRenderState[]|Object.<string, Pointers.ParabolaPointerRenderState>} [renderStates]
* <p>A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual
@ -432,7 +430,7 @@ std::shared_ptr<Pointer> PointerScriptingInterface::buildLaserPointer(const Poin
* @property {boolean} [hover=false] - <code>true</code> if the pointer generates {@link Entities} hover events,
* <code>false</code> if it doesn't.
* @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger
* events on the entity or overlay currently intersected.
* events on the object currently intersected.
* @property {PickType} pointerType - The type of pointer returned from {@link Pointers.getPointerProperties} or
* {@link Pointers.getPointerScriptParameters}. A parabola pointer's type is {@link PickType(0)|PickType.Parabola}.
* @property {number} [pickID] - The ID of the pick created alongside this pointer, returned from
@ -622,7 +620,6 @@ bool rayPointerPropertiesFromScriptValue(const ScriptValue& value, RayPointerPro
out.properties[*renderStatesName].setValue(renderStates);
}
}
qDebug() << "rayPointerPropertiesFromScriptValue" << out.properties;
return true;
}
@ -675,7 +672,6 @@ bool stylusPointerPropertiesFromScriptValue(const ScriptValue& value, StylusPoin
out.properties[*renderStatesName].setValue(renderStates);
}
}
qDebug() << "stylusPointerPropertiesFromScriptValue" << out.properties;
return true;
}
@ -714,7 +710,6 @@ bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, Parabola
pathProperties.copyFromScriptValue(path, false);
stateMap.insert("pathPropertyIndex", QVariant(out.entityProperties.length()));
out.entityProperties.append(pathProperties);
qDebug() << "parabolaPointerPropertiesFromScriptValue : added path entity";
}
if (stateMap["end"].isValid()) {
@ -723,7 +718,6 @@ bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, Parabola
endProperties.copyFromScriptValue(end, false);
stateMap.insert("endPropertyIndex", QVariant(out.entityProperties.length()));
out.entityProperties.append(endProperties);
qDebug() << "parabolaPointerPropertiesFromScriptValue : added end entity";
}
renderStates[i].setValue(stateMap);
}
@ -731,6 +725,5 @@ bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, Parabola
out.properties[*renderStatesName].setValue(renderStates);
}
}
qDebug() << "parabolaPointerPropertiesFromScriptValue" << out.properties;
return true;
}

View file

@ -103,7 +103,7 @@ public:
* ignorePickIntersection: true
* };
* var intersectedPath = {
* type: "line3d",
* type: "PolyLine",
* color: { red: 0, green: 255, blue: 0 },
* };
* var searchEnd = {
@ -114,7 +114,7 @@ public:
* ignorePickIntersection: true
* };
* var searchPath = {
* type: "line3d",
* type: "PolyLine",
* color: { red: 255, green: 0, blue: 0 },
* };
*
@ -224,8 +224,8 @@ public:
* @param {number} id - The ID of the pointer.
* @param {string} renderState - The name of the render state to edit.
* @param {Pointers.RayPointerRenderState|Pointers.ParabolaPointerRenderState} properties - The new properties for the
* render state. Only the overlay properties to change need be specified.
* @example <caption>Change the dimensions of a ray pointer's intersecting end overlay.</caption>
* render state. Only the properties to change need be specified.
* @example <caption>Change the dimensions of a ray pointer's intersecting end entity.</caption>
* var intersectEnd = {
* type: "sphere",
* dimensions: { x: 0.2, y: 0.2, z: 0.2 },
@ -234,7 +234,7 @@ public:
* ignorePickIntersection: true
* };
* var intersectedPath = {
* type: "line3d",
* type: "PolyLine",
* color: { red: 0, green: 255, blue: 0 },
* };
* var searchEnd = {
@ -245,7 +245,7 @@ public:
* ignorePickIntersection: true
* };
* var searchPath = {
* type: "line3d",
* type: "PolyLine",
* color: { red: 255, green: 0, blue: 0 },
* };
*
@ -310,7 +310,7 @@ public:
* ignorePickIntersection: true
* };
* var intersectedPath = {
* type: "line3d",
* type: "PolyLine",
* color: { red: 0, green: 255, blue: 0 },
* };
* var searchEnd = {
@ -321,7 +321,7 @@ public:
* ignorePickIntersection: true
* };
* var searchPath = {
* type: "line3d",
* type: "PolyLine",
* color: { red: 255, green: 0, blue: 0 },
* };
*
@ -423,6 +423,14 @@ public:
*/
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); }
/*@jsdoc
* Sets the delay of a Ray pointer.
* <p><strong>Note:</strong> Not used by stylus or parabola pointers.</p>
* @function Pointers.setDelay
* @param {number} id - The ID of the pointer.
* @param {number} delay - The desired delay in seconds.
*/
Q_INVOKABLE void setDelay(unsigned int uid, float delay) const { DependencyManager::get<PointerManager>()->setDelay(uid, delay); }
/*@jsdoc
* Checks if a pointer is associated with the left hand: a ray or parabola pointer with <code>joint</code> property set to

View file

@ -19,10 +19,43 @@ PickRay RayPick::getMathematicalPick() const {
return _mathPick;
}
float delayHalf = 0.0f;
withReadLock([&] {
delayHalf = _delayHalf;
});
const bool hasDelay = _prevUpdate != 0 && delayHalf > 0.0f && !isNaN(_prevDirection.x);
const float now = secTimestampNow();
const float dt = now - _prevUpdate;
float alpha = 0.0f;
if (hasDelay) {
// This equation gives a framerate-independent lerp for a moving target
// https://twitter.com/FreyaHolmer/status/1757836988495847568
alpha = 1 - exp2(-dt / delayHalf);
}
Transform currentParentTransform = parentTransform->getTransform();
glm::vec3 origin = currentParentTransform.transform(_mathPick.origin);
glm::vec3 direction = glm::normalize(currentParentTransform.transformDirection(_mathPick.direction));
return PickRay(origin, direction);
glm::vec3 direction;
glm::vec3 newDirection = glm::normalize(currentParentTransform.transformDirection(_mathPick.direction));
if (hasDelay) {
PickResultPointer result = getPrevPickResult();
if (!result || !result->doesIntersect()) {
direction = glm::normalize(glm::mix(_prevDirection, newDirection, alpha));
} else {
auto rayResult = std::static_pointer_cast<RayPickResult>(result);
glm::vec3 oldDirection = glm::normalize(rayResult->intersection - origin);
direction = glm::normalize(glm::mix(oldDirection, newDirection, alpha));
}
} else {
direction = newDirection;
}
_prevUpdate = now;
if (!isNaN(direction.x)) {
_prevDirection = direction;
}
return PickRay(origin, direction, newDirection);
}
PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
@ -65,6 +98,14 @@ PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) {
return std::make_shared<RayPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick);
}
void RayPick::setDelay(float delay) {
withWriteLock([&] {
// We want to be within 0.1% of the target in <delay> seconds
// https://twitter.com/FreyaHolmer/status/1757836988495847568
_delayHalf = -std::max(delay, 0.0f) / log2(0.001);
});
}
Transform RayPick::getResultTransform() const {
PickResultPointer result = getPrevPickResult();
if (!result) {
@ -89,8 +130,6 @@ glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const gl
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
}
glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
glm::quat invRot = glm::inverse(rotation);
glm::vec3 localPos = invRot * (worldPos - position);

View file

@ -84,9 +84,8 @@ public:
class RayPick : public Pick<PickRay> {
public:
RayPick(glm::vec3 position, glm::vec3 direction, const PickFilter& filter, float maxDistance, bool enabled) :
Pick(PickRay(position, direction), filter, maxDistance, enabled) {
}
RayPick(glm::vec3 position, glm::vec3 direction, const PickFilter& filter, float maxDistance, float delayHalf, bool enabled) :
Pick(PickRay(position, direction), filter, maxDistance, enabled), _delayHalf(delayHalf) {}
PickType getType() const override { return PickType::Ray; }
@ -98,6 +97,8 @@ public:
PickResultPointer getHUDIntersection(const PickRay& pick) override;
Transform getResultTransform() const override;
void setDelay(float delay) override;
// These are helper functions for projecting and intersecting rays
static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true);
@ -107,6 +108,10 @@ public:
private:
static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration);
float _delayHalf { 0.0f };
mutable float _prevUpdate { 0.0f };
mutable glm::vec3 _prevDirection { NAN };
};
#endif // hifi_RayPick_h

View file

@ -48,29 +48,6 @@ PickQuery::PickType StylusPointer::getType() const {
}
QUuid StylusPointer::buildStylus(const QVariantMap& properties) {
// FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers
/*QVariantMap propertiesMap;
QString modelUrl = DEFAULT_STYLUS_MODEL_URL;
if (properties["model"].isValid()) {
QVariantMap modelData = properties["model"].toMap();
if (modelData["url"].isValid()) {
modelUrl = modelData["url"].toString();
}
}
// TODO: make these configurable per pointer
propertiesMap["name"] = "stylus";
propertiesMap["url"] = modelUrl;
propertiesMap["loadPriority"] = 10.0f;
propertiesMap["solid"] = true;
propertiesMap["visible"] = false;
propertiesMap["ignorePickIntersection"] = true;
propertiesMap["drawInFront"] = false;
return qApp->getOverlays().addOverlay("model", propertiesMap);*/
EntityItemProperties entityProperties;
QString modelURL = DEFAULT_STYLUS_MODEL_URL;

View file

@ -78,7 +78,7 @@ WindowScriptingInterface::~WindowScriptingInterface() {
}
ScriptValue WindowScriptingInterface::hasFocus() {
Q_ASSERT(engine);
Q_ASSERT(engine());
return engine()->newValue(qApp->hasFocus());
}
@ -107,7 +107,7 @@ void WindowScriptingInterface::alert(const QString& message) {
/// \param const QString& message message to display
/// \return ScriptValue `true` if 'Yes' was clicked, `false` otherwise
ScriptValue WindowScriptingInterface::confirm(const QString& message) {
Q_ASSERT(engine);
Q_ASSERT(engine());
return engine()->newValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)));
}
@ -117,7 +117,7 @@ ScriptValue WindowScriptingInterface::confirm(const QString& message) {
/// \return ScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
ScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText);
Q_ASSERT(engine);
Q_ASSERT(engine());
auto sResult = engine()->newValue(result);
if (sResult.equals(engine()->newValue(""))) {
return engine()->nullValue();
@ -234,7 +234,7 @@ ScriptValue WindowScriptingInterface::browseDir(const QString& title, const QStr
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
Q_ASSERT(engine);
Q_ASSERT(engine());
return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result);
}
@ -279,7 +279,7 @@ ScriptValue WindowScriptingInterface::browse(const QString& title, const QString
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
Q_ASSERT(engine);
Q_ASSERT(engine());
return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result);
}
@ -327,7 +327,7 @@ ScriptValue WindowScriptingInterface::save(const QString& title, const QString&
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
Q_ASSERT(engine);
Q_ASSERT(engine());
return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result);
}
@ -378,7 +378,7 @@ ScriptValue WindowScriptingInterface::browseAssets(const QString& title, const Q
if (!result.isEmpty()) {
setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath());
}
Q_ASSERT(engine);
Q_ASSERT(engine());
return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result);
}

View file

@ -303,10 +303,13 @@ bool Keyboard::isRaised() const {
return resultWithReadLock<bool>([&] { return _raised; });
}
void Keyboard::setRaised(bool raised) {
void Keyboard::setRaised(bool raised, bool inputToHudUI) {
bool isRaised;
withReadLock([&] { isRaised = _raised; });
_inputToHudUI = inputToHudUI;
if (isRaised != raised) {
raiseKeyboardAnchor(raised);
raiseKeyboard(raised);
@ -585,8 +588,13 @@ void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) {
QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString);
QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
QCoreApplication::postEvent(QCoreApplication::instance(), pressEvent);
QCoreApplication::postEvent(QCoreApplication::instance(), releaseEvent);
if (_inputToHudUI) {
QCoreApplication::postEvent(qApp->getPrimaryWidget(), pressEvent);
QCoreApplication::postEvent(qApp->getPrimaryWidget(), releaseEvent);
} else {
QCoreApplication::postEvent(QCoreApplication::instance(), pressEvent);
QCoreApplication::postEvent(QCoreApplication::instance(), releaseEvent);
}
if (!getPreferMalletsOverLasers()) {
key.startTimer(KEY_PRESS_TIMEOUT_MS);

View file

@ -93,7 +93,7 @@ public:
void createKeyboard();
void registerKeyboardHighlighting();
bool isRaised() const;
void setRaised(bool raised);
void setRaised(bool raised, bool inputToHudUI = false);
void setResetKeyboardPositionOnRaise(bool reset);
bool isPassword() const;
void setPassword(bool password);
@ -190,6 +190,8 @@ private:
QSet<QUuid> _itemsToIgnore;
std::vector<QHash<QUuid, Key>> _keyboardLayers;
// Send keyboard events to hud UI if true
std::atomic<bool> _inputToHudUI { false };
bool _created { false };
};

View file

@ -18,6 +18,7 @@
#include <plugins/PluginManager.h>
#include <display-plugins/CompositorHelper.h>
#include <display-plugins/hmd/HmdDisplayPlugin.h>
#include <raypick/PickScriptingInterface.h>
#include "scripting/RenderScriptingInterface.h"
#include "Application.h"
#include "DialogsManager.h"
@ -207,6 +208,23 @@ void setupPreferences() {
preferences->addPreference(preference);
}
{
auto getter = []() -> bool { return qApp->getPreferAvatarFingerOverStylus(); };
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
}
{
auto getter = []() -> float { return DependencyManager::get<PickScriptingInterface>()->getHandLaserDelay(); };
auto setter = [](float value) { DependencyManager::get<PickScriptingInterface>()->setHandLaserDelay(value); };
auto delaySlider = new SpinnerSliderPreference(UI_CATEGORY, "Laser Delay (seconds)", getter, setter);
delaySlider->setMin(0.0f);
delaySlider->setMax(0.7f);
delaySlider->setStep(0.05f);
delaySlider->setDecimals(2.0f);
preferences->addPreference(delaySlider);
}
static const QString VIEW_CATEGORY{ "View" };
{
auto getter = [myAvatar]()->float { return myAvatar->getRealWorldFieldOfView(); };
@ -226,12 +244,6 @@ void setupPreferences() {
preferences->addPreference(preference);
}
{
auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
}
// Snapshots
static const QString SNAPSHOTS { "Snapshots" };
{

View file

@ -27,7 +27,9 @@
#endif
#ifdef WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
#include <Mmsystem.h>
#include <mmdeviceapi.h>

View file

@ -2566,7 +2566,7 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
void AttachmentDataObject::setModelURL(const QString& modelURL) {
AttachmentData data = scriptvalue_cast<AttachmentData>(thisObject());
data.modelURL = modelURL;
Q_ASSERT(engine);
Q_ASSERT(engine());
thisObject() = engine()->toScriptValue(data);
}
@ -2577,7 +2577,7 @@ QString AttachmentDataObject::getModelURL() const {
void AttachmentDataObject::setJointName(const QString& jointName) {
AttachmentData data = scriptvalue_cast<AttachmentData>(thisObject());
data.jointName = jointName;
Q_ASSERT(engine);
Q_ASSERT(engine());
thisObject() = engine()->toScriptValue(data);
}
@ -2588,7 +2588,7 @@ QString AttachmentDataObject::getJointName() const {
void AttachmentDataObject::setTranslation(const glm::vec3& translation) {
AttachmentData data = scriptvalue_cast<AttachmentData>(thisObject());
data.translation = translation;
Q_ASSERT(engine);
Q_ASSERT(engine());
thisObject() = engine()->toScriptValue(data);
}
@ -2599,7 +2599,7 @@ glm::vec3 AttachmentDataObject::getTranslation() const {
void AttachmentDataObject::setRotation(const glm::quat& rotation) {
AttachmentData data = scriptvalue_cast<AttachmentData>(thisObject());
data.rotation = rotation;
Q_ASSERT(engine);
Q_ASSERT(engine());
thisObject() = engine()->toScriptValue(data);
}
@ -2610,7 +2610,7 @@ glm::quat AttachmentDataObject::getRotation() const {
void AttachmentDataObject::setScale(float scale) {
AttachmentData data = scriptvalue_cast<AttachmentData>(thisObject());
data.scale = scale;
Q_ASSERT(engine);
Q_ASSERT(engine());
thisObject() = engine()->toScriptValue(data);
}
@ -2621,7 +2621,7 @@ float AttachmentDataObject::getScale() const {
void AttachmentDataObject::setIsSoft(bool isSoft) {
AttachmentData data = scriptvalue_cast<AttachmentData>(thisObject());
data.isSoft = isSoft;
Q_ASSERT(engine);
Q_ASSERT(engine());
thisObject() = engine()->toScriptValue(data);
}

View file

@ -1329,7 +1329,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {boolean} unlit=false - <code>true</code> if the entity is unaffected by lighting, <code>false</code> if it is lit
* by the key light and local lights.
* @property {string} font="" - The font to render the text with. It can be one of the following: <code>"Courier"</code>,
* <code>"Inconsolata"</code>, <code>"Roboto"</code>, <code>"Timeless"</code>, or a path to a .sdff file.
* <code>"Inconsolata"</code>, <code>"Roboto"</code>, <code>"Timeless"</code>, or a path to a PNG MTSDF .arfont file generated
* by the msdf-atlas-gen tool (https://github.com/Chlumsky/msdf-atlas-gen).
* @property {Entities.TextEffect} textEffect="none" - The effect that is applied to the text.
* @property {Color} textEffectColor=255,255,255 - The color of the effect.
* @property {number} textEffectThickness=0.2 - The magnitude of the text effect, range <code>0.0</code> &ndash; <code>0.5</code>.

View file

@ -69,7 +69,6 @@ void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPoint
QString messageText = QString::fromUtf8(message->readAll());
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(messageText.toUtf8(), &error);
emit receivedNewLogLines(messageText);
if(document.isNull()) {
qWarning() << "EntityScriptServerLogClient::handleEntityServerScriptLogPacket: Cannot parse JSON: " << error.errorString()
<< " Contents: " << messageText;
@ -98,21 +97,37 @@ void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPoint
case ScriptMessage::Severity::SEVERITY_INFO:
emit scriptEngines->infoEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
emit receivedNewLogLines("[ INFO {" + scriptMessage.getEntityID().toString() + "} "
+ scriptMessage.getFileName() + ":"
+ QString::number(scriptMessage.getLineNumber()) + "] "
+ scriptMessage.getMessage());
break;
case ScriptMessage::Severity::SEVERITY_PRINT:
emit scriptEngines->printedEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
emit receivedNewLogLines("[ WARNING {" + scriptMessage.getEntityID().toString() + "} "
+ scriptMessage.getFileName() + ":"
+ QString::number(scriptMessage.getLineNumber()) + "] "
+ scriptMessage.getMessage());
break;
case ScriptMessage::Severity::SEVERITY_WARNING:
emit scriptEngines->warningEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
emit receivedNewLogLines("[ WARNING {" + scriptMessage.getEntityID().toString() + "} "
+ scriptMessage.getFileName() + ":"
+ QString::number(scriptMessage.getLineNumber()) + "] "
+ scriptMessage.getMessage());
break;
case ScriptMessage::Severity::SEVERITY_ERROR:
emit scriptEngines->errorEntityMessage(scriptMessage.getMessage(), scriptMessage.getFileName(),
scriptMessage.getLineNumber(), scriptMessage.getEntityID(), true);
emit receivedNewLogLines("[ ERROR {" + scriptMessage.getEntityID().toString() + "} "
+ scriptMessage.getFileName() + ":"
+ QString::number(scriptMessage.getLineNumber()) + "] "
+ scriptMessage.getMessage());
break;
default:

View file

@ -12,7 +12,9 @@
#include <list>
#include <functional>
#include <glm/gtc/type_ptr.hpp>
#include <QLoggingCategory>
#include "glad/glad.h"
Q_LOGGING_CATEGORY(gpugl45logging, "hifi.gpu.gl45")
using namespace gpu;
@ -21,9 +23,27 @@ using namespace gpu::gl45;
GLint GL45Backend::MAX_COMBINED_SHADER_STORAGE_BLOCKS{ 0 };
GLint GL45Backend::MAX_UNIFORM_LOCATIONS{ 0 };
#ifdef GLAD_DEBUG
static void post_call_callback_gl(const char *name, void *funcptr, int len_args, ...) {
(void)funcptr;
(void)len_args;
GLenum error_code = glad_glGetError();
if (error_code != GL_NO_ERROR) {
qCWarning(gpugl45logging) << "OpenGL error" << error_code << "in" << name;
}
}
#endif
static void staticInit() {
static std::once_flag once;
std::call_once(once, [&] {
#ifdef GLAD_DEBUG
// This sets the post call callback to a logging function. By default it prints on
// stderr and skips our log. It only exists in debug builds.
glad_set_post_callback(&post_call_callback_gl);
#endif
glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, &GL45Backend::MAX_COMBINED_SHADER_STORAGE_BLOCKS);
glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &GL45Backend::MAX_UNIFORM_LOCATIONS);
});
@ -82,7 +102,7 @@ void GL45Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
uint32 startIndex = batch._params[paramOffset + 0]._uint;
GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType];
auto typeByteSize = TYPE_SIZE[_input._indexBufferType];
GLvoid* indexBufferByteOffset = reinterpret_cast<GLvoid*>(startIndex * typeByteSize + _input._indexBufferOffset);
@ -148,7 +168,7 @@ void GL45Backend::do_drawIndexedInstanced(const Batch& batch, size_t paramOffset
GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType];
auto typeByteSize = TYPE_SIZE[_input._indexBufferType];
GLvoid* indexBufferByteOffset = reinterpret_cast<GLvoid*>(startIndex * typeByteSize + _input._indexBufferOffset);
if (isStereo()) {
GLint trueNumInstances = 2 * numInstances;

View file

@ -2,3 +2,7 @@ set(TARGET_NAME graphics-scripting)
setup_hifi_library()
link_hifi_libraries(shared networking graphics model-serializers image material-networking model-networking script-engine)
include_hifi_library_headers(gpu)
if (WIN32)
add_compile_definitions(_USE_MATH_DEFINES)
endif()

View file

@ -45,7 +45,7 @@ void Mesh::setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const
// We require meshes to have a color attribute. If they don't, we default to white.
if (!_vertexFormat->hasAttribute(gpu::Stream::COLOR)) {
int channelNum = _vertexStream.getNumBuffers();
gpu::Stream::Slot channelNum = (gpu::Stream::Slot)_vertexStream.getNumBuffers();
_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), 0, gpu::Stream::PER_INSTANCE);
_vertexStream.addBuffer(_colorBuffer, 0, _vertexFormat->getChannels().at(channelNum)._stride);
}

View file

@ -30,6 +30,8 @@
#include <qfile.h>
#include <qfileinfo.h>
#include <sstream>
#include <glm/gtx/transform.hpp>
#include <shared/NsightHelpers.h>
@ -42,6 +44,18 @@
#include "FBXSerializer.h"
float atof_locale_independent(char* str) {
//TODO: Once we have C++17 we can use std::from_chars
std::istringstream streamToParse(str);
streamToParse.imbue(std::locale("C"));
float value;
if (!(streamToParse >> value)) {
qDebug(modelformat) << "cgltf: Cannot parse float from string: " << str;
return 0.0f;
}
return value;
}
#define GLTF_GET_INDICIES(accCount) int index1 = (indices[n + 0] * accCount); int index2 = (indices[n + 1] * accCount); int index3 = (indices[n + 2] * accCount);
#define GLTF_APPEND_ARRAY_1(newArray, oldArray) GLTF_GET_INDICIES(1) \
@ -148,8 +162,8 @@ bool findNodeInPointerArray(const cgltf_node *nodePointer, cgltf_node **nodes, s
return false;
}
template<typename T> bool findPointerInArray(const T *pointer, const T *array, size_t arraySize, int &index) {
for (int i = 0; i < arraySize; i++) {
template<typename T> bool findPointerInArray(const T *pointer, const T *array, size_t arraySize, size_t &index) {
for (size_t i = 0; i < arraySize; i++) {
if (&array[i] == pointer) {
index = i;
return true;
@ -187,13 +201,13 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
auto &node = _data->nodes[index];
for(size_t childIndexInParent = 0; childIndexInParent < node.children_count; childIndexInParent++) {
cgltf_node *child = node.children[childIndexInParent];
int childIndex = 0;
size_t childIndex = 0;
if (!findPointerInArray(child, _data->nodes, _data->nodes_count, childIndex)) {
qDebug(modelformat) << "findPointerInArray failed for model: " << _url;
hfmModel.loadErrorCount++;
return false;
}
parents[childIndex] = index;
parents[(int)childIndex] = index;
}
sortedNodes.push_back(index);
}
@ -918,14 +932,14 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
continue;
}
int jointIndex = 0;
size_t jointIndex = 0;
if (!findPointerInArray(node.skin->joints[clusterJoints[c]], _data->nodes, _data->nodes_count, jointIndex)) {
qCWarning(modelformat) << "Cannot find the joint " << node.skin->joints[clusterJoints[c]]->name <<" in joint array";
hfmModel.loadErrorCount++;
continue;
}
mesh.clusterIndices[prevMeshClusterIndexCount + c] =
originalToNewNodeIndexMap[jointIndex];
originalToNewNodeIndexMap[(int)jointIndex];
}
// normalize and compress to 16-bits
@ -961,14 +975,14 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
}
}
int materialIndex = 0;
size_t materialIndex = 0;
if (primitive.material != nullptr && !findPointerInArray(primitive.material, _data->materials, _data->materials_count, materialIndex)) {
qCWarning(modelformat) << "GLTFSerializer::buildGeometry: Invalid material pointer";
hfmModel.loadErrorCount++;
return false;
}
if (primitive.material != nullptr) {
part.materialID = materialIDs[materialIndex];
part.materialID = materialIDs[(int)materialIndex];
}
mesh.parts.push_back(part);
@ -1306,7 +1320,7 @@ HFMTexture GLTFSerializer::getHFMTexture(const cgltf_texture *texture) {
size_t offset = bufferView->offset;
int length = (int)bufferView->size;
int imageIndex = 0;
size_t imageIndex = 0;
if (!findPointerInArray(image, _data->images, _data->images_count, imageIndex)) {
// This should never happen. It would mean a bug in cgltf library.
qDebug(modelformat) << "GLTFSerializer::getHFMTexture: can't find texture in the array";
@ -1346,7 +1360,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const cgltf_material& m
}
if (mToonExtension["shadeMultiplyTexture"].isObject()) {
QJsonObject object = mToonExtension["shadeMultiplyTexture"].toObject();
if (object["index"].isDouble() && object["index"].toInt() < _data->textures_count) {
if (object["index"].isDouble() && object["index"].toInt() < (int)_data->textures_count) {
hfmMat.shadeTexture = getHFMTexture(&_data->textures[object["index"].toInt()]);
}
}
@ -1355,7 +1369,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const cgltf_material& m
}
if (mToonExtension["shadingShiftTexture"].isObject()) {
QJsonObject object = mToonExtension["shadingShiftTexture"].toObject();
if (object["index"].isDouble() && object["index"].toInt() < _data->textures_count) {
if (object["index"].isDouble() && object["index"].toInt() < (int)_data->textures_count) {
hfmMat.shadingShiftTexture = getHFMTexture(&_data->textures[object["index"].toInt()]);
}
}
@ -1370,7 +1384,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const cgltf_material& m
}
if (mToonExtension["matcapTexture"].isObject()) {
QJsonObject object = mToonExtension["matcapTexture"].toObject();
if (object["index"].isDouble() && object["index"].toInt() < _data->textures_count) {
if (object["index"].isDouble() && object["index"].toInt() < (int)_data->textures_count) {
hfmMat.matcapTexture = getHFMTexture(&_data->textures[object["index"].toInt()]);
}
}
@ -1388,7 +1402,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const cgltf_material& m
}
if (mToonExtension["rimMultiplyTexture"].isObject()) {
QJsonObject object = mToonExtension["rimMultiplyTexture"].toObject();
if (object["index"].isDouble() && object["index"].toInt() < _data->textures_count) {
if (object["index"].isDouble() && object["index"].toInt() < (int)_data->textures_count) {
hfmMat.rimTexture = getHFMTexture(&_data->textures[object["index"].toInt()]);
}
}
@ -1417,7 +1431,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& hfmMat, const cgltf_material& m
}
if (mToonExtension["uvAnimationMaskTexture"].isObject()) {
QJsonObject object = mToonExtension["uvAnimationMaskTexture"].toObject();
if (object["index"].isDouble() && object["index"].toInt() < _data->textures_count) {
if (object["index"].isDouble() && object["index"].toInt() < (int)_data->textures_count) {
hfmMat.uvAnimationTexture = getHFMTexture(&_data->textures[object["index"].toInt()]);
}
}

View file

@ -14,24 +14,12 @@
#ifndef hifi_GLTFSerializer_h
#define hifi_GLTFSerializer_h
#include <sstream>
#include <memory.h>
#include <QtNetwork/QNetworkReply>
#include <hfm/ModelFormatLogging.h>
#include <hfm/HFMSerializer.h>
static float atof_locale_independent(char *str) {
//TODO: Once we have C++17 we can use std::from_chars
std::istringstream streamToParse(str);
streamToParse.imbue(std::locale("C"));
float value;
if(!(streamToParse >> value)) {
qDebug(modelformat) << "cgltf: Cannot parse float from string: " << str;
return 0.0f;
}
return value;
}
float atof_locale_independent(char* str);
#define CGLTF_ATOF(str) atof_locale_independent(str)

View file

@ -54,7 +54,7 @@ class CharacterController : public btCharacterControllerInterface {
public:
enum class FollowType : uint8_t {
Rotation,
Rotation = 0,
Horizontal,
Vertical,
Count

View file

@ -170,6 +170,8 @@ public:
void setIgnoreItems(const QVector<QUuid>& items);
void setIncludeItems(const QVector<QUuid>& items);
virtual void setDelay(float delay) {}
virtual QVariantMap toVariantMap() const {
QVariantMap properties;

View file

@ -129,6 +129,13 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector<QUuid>& includ
}
}
void PickManager::setDelay(unsigned int uid, float delay) const {
auto pick = findPick(uid);
if (pick) {
pick->setDelay(delay);
}
}
Transform PickManager::getParentTransform(unsigned int uid) const {
auto pick = findPick(uid);
if (pick) {

View file

@ -48,6 +48,7 @@ public:
void setPrecisionPicking(unsigned int uid, bool precisionPicking) const;
void setIgnoreItems(unsigned int uid, const QVector<QUuid>& ignore) const;
void setIncludeItems(unsigned int uid, const QVector<QUuid>& include) const;
void setDelay(unsigned int uid, float delay) const;
Transform getParentTransform(unsigned int uid) const;
Transform getResultTransform(unsigned int uid) const;

View file

@ -74,6 +74,10 @@ void Pointer::setIncludeItems(const QVector<QUuid>& includeItems) const {
DependencyManager::get<PickManager>()->setIncludeItems(_pickUID, includeItems);
}
void Pointer::setDelay(float delay) const {
DependencyManager::get<PickManager>()->setDelay(_pickUID, delay);
}
bool Pointer::isLeftHand() const {
return DependencyManager::get<PickManager>()->isLeftHand(_pickUID);
}

View file

@ -59,6 +59,7 @@ public:
virtual void setPrecisionPicking(bool precisionPicking);
virtual void setIgnoreItems(const QVector<QUuid>& ignoreItems) const;
virtual void setIncludeItems(const QVector<QUuid>& includeItems) const;
virtual void setDelay(float delay) const;
bool isLeftHand() const;
bool isRightHand() const;

View file

@ -157,6 +157,13 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo
}
}
void PointerManager::setDelay(unsigned int uid, float delay) const {
auto pointer = find(uid);
if (pointer) {
pointer->setDelay(delay);
}
}
bool PointerManager::isLeftHand(unsigned int uid) {
auto pointer = find(uid);
if (pointer) {

View file

@ -45,6 +45,7 @@ public:
void setLength(unsigned int uid, float length) const;
void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const;
void setDelay(unsigned int uid, float delay) const;
void update();

View file

@ -38,34 +38,36 @@ using namespace recording;
static const QString HFR_EXTENSION = "hfr";
RecordingScriptingInterface::RecordingScriptingInterface() {
Locker(_mutex);
Locker lock(_mutex);
_player = DependencyManager::get<Deck>();
_recorder = DependencyManager::get<Recorder>();
}
bool RecordingScriptingInterface::isPlaying() const {
Locker(_mutex);
Locker lock(_mutex);
return _player->isPlaying();
}
bool RecordingScriptingInterface::isPaused() const {
Locker(_mutex);
Locker lock(_mutex);
return _player->isPaused();
}
float RecordingScriptingInterface::playerElapsed() const {
Locker(_mutex);
Locker lock(_mutex);
return _player->position();
}
float RecordingScriptingInterface::playerLength() const {
Locker(_mutex);
Locker lock(_mutex);
return _player->length();
}
void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader, const QString& url, const ScriptValue& callback) {
Locker(_mutex);
_player->queueClip(clipLoader->getClip());
{
Locker lock(_mutex);
_player->queueClip(clipLoader->getClip());
}
if (callback.isFunction()) {
auto engine = callback.engine();
@ -75,8 +77,6 @@ void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader,
}
void RecordingScriptingInterface::loadRecording(const QString& url, const ScriptValue& callback) {
Locker(_mutex);
auto clipLoader = DependencyManager::get<recording::ClipCache>()->getClipLoader(url);
if (clipLoader->isLoaded()) {
@ -85,6 +85,8 @@ void RecordingScriptingInterface::loadRecording(const QString& url, const Script
return;
}
Locker lock(_mutex);
// hold a strong pointer to the loading clip so that it has a chance to load
_clipLoaders.insert(clipLoader);
@ -134,12 +136,12 @@ void RecordingScriptingInterface::startPlaying() {
return;
}
Locker(_mutex);
Locker lock(_mutex);
_player->play();
}
void RecordingScriptingInterface::setPlayerVolume(float volume) {
Locker(_mutex);
Locker lock(_mutex);
_player->setVolume(std::min(std::max(volume, 0.0f), 1.0f));
}
@ -153,7 +155,7 @@ void RecordingScriptingInterface::setPlayerTime(float time) {
return;
}
Locker(_mutex);
Locker lock(_mutex);
_player->seek(time);
}
@ -162,7 +164,7 @@ void RecordingScriptingInterface::setPlayFromCurrentLocation(bool playFromCurren
}
void RecordingScriptingInterface::setPlayerLoop(bool loop) {
Locker(_mutex);
Locker lock(_mutex);
_player->loop(loop);
}
@ -183,7 +185,7 @@ void RecordingScriptingInterface::setPlayerUseSkeletonModel(bool useSkeletonMode
}
void RecordingScriptingInterface::pausePlayer() {
Locker(_mutex);
Locker lock(_mutex);
_player->pause();
}
@ -193,7 +195,7 @@ void RecordingScriptingInterface::stopPlaying() {
return;
}
Locker(_mutex);
Locker lock(_mutex);
_player->stop();
}
@ -211,7 +213,7 @@ void RecordingScriptingInterface::startRecording() {
return;
}
Locker(_mutex);
Locker lock(_mutex);
_recorder->start();
}
@ -221,7 +223,7 @@ void RecordingScriptingInterface::stopRecording() {
return;
}
Locker(_mutex);
Locker lock(_mutex);
_recorder->stop();
_lastClip = _recorder->getClip();
_lastClip->seek(0);
@ -236,7 +238,7 @@ QString RecordingScriptingInterface::getDefaultRecordingSaveDirectory() {
}
void RecordingScriptingInterface::saveRecording(const QString& filename) {
Locker(_mutex);
Locker lock(_mutex);
if (!_lastClip) {
qWarning() << "There is no recording to save";
return;
@ -251,7 +253,7 @@ bool RecordingScriptingInterface::saveRecordingToAsset(const ScriptValue& getCli
return false;
}
Locker(_mutex);
Locker lock(_mutex);
if (!_lastClip) {
qWarning() << "There is no recording to save";
return false;
@ -293,7 +295,7 @@ void RecordingScriptingInterface::loadLastRecording() {
return;
}
Locker(_mutex);
Locker lock(_mutex);
if (!_lastClip) {
qCDebug(scriptengine) << "There is no recording to load";

View file

@ -357,7 +357,7 @@ protected:
using Locker = std::unique_lock<Mutex>;
using Flag = std::atomic<bool>;
Mutex _mutex;
mutable Mutex _mutex;
QSharedPointer<recording::Deck> _player;
QSharedPointer<recording::Recorder> _recorder;

View file

@ -0,0 +1,93 @@
Copyright 2015 The Courier Prime Project Authors (https://github.com/quoteunquoteapps/CourierPrime).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View file

@ -1,51 +0,0 @@
/*
SIL Open Font License v1.10
This license can also be found at this permalink: http://www.fontsquirrel.com/license/courier-prime
Copyright (c) 2013, Quote-Unquote Apps (http://quoteunquoteapps.com),
with Reserved Font Name Courier Prime.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
—————————————————————————————-
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
—————————————————————————————-
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
*/

View file

@ -0,0 +1,93 @@
Copyright 2006 The Inconsolata Project Authors
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

Binary file not shown.

View file

@ -1,9 +1,9 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>CourierPrime.sdff</file>
<file>InconsolataMedium.sdff</file>
<file>Roboto.sdff</file>
<file>Timeless.sdff</file>
<file>CourierPrime.arfont</file>
<file>InconsolataMedium.arfont</file>
<file>Roboto.arfont</file>
<file>Timeless.arfont</file>
</qresource>
</RCC>

View file

@ -1802,8 +1802,8 @@ public:
};
static void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uvec4& packed, const BlendshapeOffsetUnpacked& unpacked) {
float len = glm::compMax(glm::abs(unpacked.positionOffset));
glm::vec3 normalizedPos(unpacked.positionOffset);
float len = max(abs(unpacked.positionOffsetX), max(abs(unpacked.positionOffsetY), abs(unpacked.positionOffsetZ)));
glm::vec3 normalizedPos(unpacked.positionOffsetX, unpacked.positionOffsetY, unpacked.positionOffsetZ);
if (len > 0.0f) {
normalizedPos /= len;
} else {
@ -1813,8 +1813,8 @@ static void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uve
packed = glm::uvec4(
glm::floatBitsToUint(len),
glm_packSnorm3x10_1x2(glm::vec4(normalizedPos, 0.0f)),
glm_packSnorm3x10_1x2(glm::vec4(unpacked.normalOffset, 0.0f)),
glm_packSnorm3x10_1x2(glm::vec4(unpacked.tangentOffset, 0.0f))
glm_packSnorm3x10_1x2(glm::vec4(unpacked.normalOffsetX, unpacked.normalOffsetY, unpacked.normalOffsetZ, 0.0f)),
glm_packSnorm3x10_1x2(glm::vec4(unpacked.tangentOffsetX, unpacked.tangentOffsetY, unpacked.tangentOffsetZ, 0.0f))
);
}
@ -1922,10 +1922,19 @@ void Blender::run() {
int index = blendshape.indices.at(j);
auto& currentBlendshapeOffset = unpackedBlendshapeOffsets[index];
currentBlendshapeOffset.positionOffset += blendshape.vertices.at(j) * vertexCoefficient;
currentBlendshapeOffset.normalOffset += blendshape.normals.at(j) * normalCoefficient;
glm::vec3 blendshapePosition = blendshape.vertices.at(j) * vertexCoefficient;
currentBlendshapeOffset.positionOffsetX += blendshapePosition.x;
currentBlendshapeOffset.positionOffsetY += blendshapePosition.y;
currentBlendshapeOffset.positionOffsetZ += blendshapePosition.z;
glm::vec3 blendshapeNormal = blendshape.normals.at(j) * normalCoefficient;
currentBlendshapeOffset.normalOffsetX += blendshapeNormal.x;
currentBlendshapeOffset.normalOffsetY += blendshapeNormal.y;
currentBlendshapeOffset.normalOffsetZ += blendshapeNormal.z;
if (j < blendshape.tangents.size()) {
currentBlendshapeOffset.tangentOffset += blendshape.tangents.at(j) * normalCoefficient;
glm::vec3 blendshapeTangent = blendshape.tangents.at(j) * normalCoefficient;
currentBlendshapeOffset.tangentOffsetX += blendshapeTangent.x;
currentBlendshapeOffset.tangentOffsetY += blendshapeTangent.y;
currentBlendshapeOffset.tangentOffsetZ += blendshapeTangent.z;
}
}
}

View file

@ -32,6 +32,7 @@
<@include sdf_text3D.slh@>
<$declareEvalSDFSuperSampled()$>
layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec2 _positionMS;
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
<@endif@>
@ -42,7 +43,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reusing the fade texcoord locations here
void main() {
vec4 color = evalSDFSuperSampled(_texCoord0, _glyphBounds);
vec4 color = evalSDFSuperSampled(_texCoord0, _positionMS, _glyphBounds);
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
color.a *= params.color.a;

View file

@ -17,13 +17,16 @@
LAYOUT(binding=0) uniform sampler2D fontTexture;
struct TextParams {
vec4 bounds;
vec4 color;
vec3 effectColor;
float effectThickness;
vec2 unitRange;
int effect;
vec3 spare;
float effectThickness;
vec3 effectColor;
float spare;
};
LAYOUT(binding=0) uniform textParamsBuffer {
@ -37,51 +40,75 @@ LAYOUT(binding=0) uniform textParamsBuffer {
const float interiorCutoff = 0.5;
const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS);
vec4 evalSDF(vec2 texCoord, vec4 glyphBounds) {
// MSDF logic from: https://github.com/Chlumsky/msdfgen?tab=readme-ov-file#using-a-multi-channel-distance-field
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
float screenPxRange(vec2 texCoord) {
vec2 screenTexSize = vec2(1.0) / fwidth(texCoord);
return max(0.5 * dot(params.unitRange, screenTexSize), 1.0);
}
vec2 evalSDF(vec2 texCoord) {
vec4 msdf = textureLod(fontTexture, texCoord, TAA_TEXTURE_LOD_BIAS);
float sdf = median(msdf.r, msdf.g, msdf.b);
float screenPxDistance = screenPxRange(texCoord) * (sdf - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
return vec2(opacity, msdf.a);
}
vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds) {
vec3 color = params.color.rgb;
float sdf = textureLod(fontTexture, texCoord, TAA_TEXTURE_LOD_BIAS).g;
vec2 sdf = evalSDF(texCoord);
// Outline
if (params.effect == 1 || params.effect == 2) {
float outline = float(sdf < interiorCutoff);
float outline = float(sdf.x < interiorCutoff);
color = mix(color, params.effectColor, outline);
// with or without fill
sdf = mix(sdf, 0.0, float(params.effect == 1) * (1.0 - outline));
sdf.x = mix(sdf.y, 0.0, float(params.effect == 1) * (1.0 - outline));
const float EPSILON = 0.00001;
sdf += mix(0.0, params.effectThickness - EPSILON, outline);
sdf.x += mix(0.0, params.effectThickness - EPSILON, outline);
} else if (params.effect == 3) { // Shadow
// don't sample from outside of our glyph bounds
sdf *= mix(1.0, 0.0, float(clamp(texCoord, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != texCoord));
sdf.x *= mix(1.0, 0.0, float(clamp(texCoord, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != texCoord));
if (sdf < interiorCutoff) {
if (sdf.x < interiorCutoff) {
color = params.effectColor;
const float DOUBLE_MAX_OFFSET_PIXELS = 20.0; // must match value in Font.cpp
// FIXME: TAA_TEXTURE_LOD_BIAS doesn't have any effect because we're only generating one mip, so here we need to use 0, but it should
// really match the LOD that we use in the textureLod call below
vec2 textureOffset = vec2(params.effectThickness * DOUBLE_MAX_OFFSET_PIXELS) / vec2(textureSize(fontTexture, 0/*int(TAA_TEXTURE_LOD_BIAS)*/));
vec2 shadowTexCoords = texCoord - textureOffset;
sdf = textureLod(fontTexture, shadowTexCoords, TAA_TEXTURE_LOD_BIAS).g;
sdf.x = evalSDF(shadowTexCoords).x;
// don't sample from outside of our glyph bounds
sdf *= mix(1.0, 0.0, float(clamp(shadowTexCoords, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != shadowTexCoords));
sdf.x *= mix(1.0, 0.0, float(clamp(shadowTexCoords, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != shadowTexCoords));
}
}
return vec4(color, sdf);
return vec4(color, sdf.x);
}
vec4 evalSDFSuperSampled(vec2 texCoord, vec4 glyphBounds) {
vec4 evalSDFSuperSampled(vec2 texCoord, vec2 positionMS, vec4 glyphBounds) {
// Clip to edges. Note: We don't need to check the top edge.
if (positionMS.x < params.bounds.x || positionMS.x > (params.bounds.x + params.bounds.z) ||
positionMS.y < params.bounds.y - params.bounds.w) {
return vec4(0.0);
}
vec2 dxTexCoord = dFdx(texCoord) * 0.5 * taaBias;
vec2 dyTexCoord = dFdy(texCoord) * 0.5 * taaBias;
// Perform 4x supersampling for anisotropic filtering
vec4 color;
color = evalSDF(texCoord, glyphBounds);
color += evalSDF(texCoord + dxTexCoord, glyphBounds);
color += evalSDF(texCoord + dyTexCoord, glyphBounds);
color += evalSDF(texCoord + dxTexCoord + dyTexCoord, glyphBounds);
color = evalSDFColor(texCoord, glyphBounds);
color += evalSDFColor(texCoord + dxTexCoord, glyphBounds);
color += evalSDFColor(texCoord + dyTexCoord, glyphBounds);
color += evalSDFColor(texCoord + dxTexCoord + dyTexCoord, glyphBounds);
color *= 0.25;
return vec4(color.rgb, step(interiorCutoff, color.a));

View file

@ -19,6 +19,7 @@
<@include sdf_text3D.slh@>
layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec2 _positionMS;
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES;
<@endif@>
@ -27,6 +28,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
layout(location=RENDER_UTILS_ATTR_FADE1) flat out vec4 _glyphBounds; // we're reusing the fade texcoord locations here
void main() {
_positionMS = inPosition.xy;
_texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0);
_glyphBounds = inTexCoord1;

View file

@ -13,6 +13,10 @@
#include <QFile>
#include <QImage>
#include <QNetworkReply>
#include <QThreadStorage>
#include "artery-font/artery-font.h"
#include "artery-font/std-artery-font.h"
#include <ColorUtils.h>
@ -77,12 +81,150 @@ struct QuadBuilder {
}
};
Font::Pointer Font::load(QIODevice& fontFile) {
Pointer font = std::make_shared<Font>();
Font::Pointer Font::load(const QString& family, QIODevice& fontFile) {
Pointer font = std::make_shared<Font>(family);
font->read(fontFile);
return font;
}
void Font::handleFontNetworkReply() {
auto requestReply = qobject_cast<QNetworkReply*>(sender());
Q_ASSERT(requestReply != nullptr);
if (requestReply->error() == QNetworkReply::NoError) {
read(*requestReply);
} else {
qDebug() << "Error downloading " << requestReply->url() << " - " << requestReply->errorString();
}
}
QThreadStorage<size_t> _readOffset;
QThreadStorage<size_t> _readMax;
int readHelper(void* dst, int length, void* data) {
if (_readOffset.localData() + length > _readMax.localData()) {
return -1;
}
memcpy(dst, (char *)data + _readOffset.localData(), length);
_readOffset.setLocalData(_readOffset.localData() + length);
return length;
};
void Font::read(QIODevice& in) {
_loaded = false;
QByteArray data = in.readAll();
_readOffset.setLocalData(0);
_readMax.setLocalData(data.length());
artery_font::StdArteryFont<float> arteryFont;
bool success = artery_font::decode<&readHelper, float, artery_font::StdList, artery_font::StdByteArray, artery_font::StdString>(arteryFont, (void *)data.data());
if (!success) {
qDebug() << "Font" << _family << "failed to decode.";
return;
}
if (arteryFont.variants.length() == 0) {
qDebug() << "Font" << _family << "has 0 variants.";
return;
}
_distanceRange = glm::vec2(arteryFont.variants[0].metrics.distanceRange);
_fontSize = arteryFont.variants[0].metrics.ascender + fabs(arteryFont.variants[0].metrics.descender);
_leading = arteryFont.variants[0].metrics.lineHeight;
_spaceWidth = 0.5f * arteryFont.variants[0].metrics.emSize; // We use half the emSize as a first guess for _spaceWidth
if (arteryFont.variants[0].glyphs.length() == 0) {
qDebug() << "Font" << _family << "has 0 glyphs.";
return;
}
QVector<Glyph> glyphs;
glyphs.reserve(arteryFont.variants[0].glyphs.length());
for (int i = 0; i < arteryFont.variants[0].glyphs.length(); i++) {
auto& g = arteryFont.variants[0].glyphs[i];
Glyph glyph;
glyph.c = g.codepoint;
glyph.texOffset = glm::vec2(g.imageBounds.l, g.imageBounds.b);
glyph.texSize = glm::vec2(g.imageBounds.r, g.imageBounds.t) - glyph.texOffset;
glyph.offset = glm::vec2(g.planeBounds.l, g.planeBounds.b);
glyph.size = glm::vec2(g.planeBounds.r, g.planeBounds.t) - glyph.offset;
glyph.d = g.advance.h;
glyphs.push_back(glyph);
// If we find the space character, we save its size in _spaceWidth for later
if (glyph.c == ' ') {
_spaceWidth = glyph.d;
}
}
if (arteryFont.images.length() == 0) {
qDebug() << "Font" << _family << "has 0 images.";
return;
}
if (arteryFont.images[0].imageType != artery_font::ImageType::IMAGE_MTSDF) {
qDebug() << "Font" << _family << "has the wrong image type. Expected MTSDF (7), got" << arteryFont.images[0].imageType;
return;
}
if (arteryFont.images[0].encoding != artery_font::ImageEncoding::IMAGE_PNG) {
qDebug() << "Font" << _family << "has the wrong encoding. Expected PNG (8), got" << arteryFont.images[0].encoding;
return;
}
if (arteryFont.images[0].pixelFormat != artery_font::PixelFormat::PIXEL_UNSIGNED8) {
qDebug() << "Font" << _family << "has the wrong pixel format. Expected unsigned char (8), got" << arteryFont.images[0].pixelFormat;
return;
}
if (arteryFont.images[0].width == 0 || arteryFont.images[0].height == 0) {
qDebug() << "Font" << _family << "has image with width or height of 0. Width:" << arteryFont.images[0].width << ", height:"<< arteryFont.images[0].height;
return;
}
// read image data
QImage image;
if (!image.loadFromData((const unsigned char*)arteryFont.images[0].data, arteryFont.images[0].data.length(), "PNG")) {
qDebug() << "Failed to read image for font" << _family;
return;
}
_glyphs.clear();
glm::vec2 imageSize = toGlm(image.size());
_distanceRange /= imageSize;
foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates,
g.texSize /= imageSize;
g.texOffset /= imageSize;
// Y flip
g.texOffset.y = 1.0f - (g.texOffset.y + g.texSize.y);
g.offset.y = -(1.0f - (g.offset.y + g.size.y));
// store in the character to glyph hash
_glyphs[g.c] = g;
};
image = image.convertToFormat(QImage::Format_RGBA8888);
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
if (image.hasAlphaChannel()) {
formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::BGRA);
}
// FIXME: We're forcing this to use only one mip, and then manually doing anisotropic filtering in the shader,
// and also calling textureLod. Shouldn't this just use anisotropic filtering and auto-generate mips?
// We should also use smoothstep for anti-aliasing, as explained here: https://github.com/libgdx/libgdx/wiki/Distance-field-fonts
_texture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::SINGLE_MIP,
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR));
_texture->setStoredMipFormat(formatMip);
_texture->assignStoredMip(0, image.sizeInBytes(), image.constBits());
_texture->setImportant(true);
_loaded = true;
_needsParamsUpdate = true;
}
static QHash<QString, Font::Pointer> LOADED_FONTS;
Font::Pointer Font::load(const QString& family) {
@ -91,16 +233,15 @@ Font::Pointer Font::load(const QString& family) {
QString loadFilename;
if (family == ROBOTO_FONT_FAMILY) {
loadFilename = ":/Roboto.sdff";
loadFilename = ":/Roboto.arfont";
} else if (family == INCONSOLATA_FONT_FAMILY) {
loadFilename = ":/InconsolataMedium.sdff";
loadFilename = ":/InconsolataMedium.arfont";
} else if (family == COURIER_FONT_FAMILY) {
loadFilename = ":/CourierPrime.sdff";
loadFilename = ":/CourierPrime.arfont";
} else if (family == TIMELESS_FONT_FAMILY) {
loadFilename = ":/Timeless.sdff";
loadFilename = ":/Timeless.arfont";
} else if (family.startsWith("http")) {
auto loadingFont = std::make_shared<Font>();
loadingFont->setLoaded(false);
auto loadingFont = std::make_shared<Font>(family);
LOADED_FONTS[family] = loadingFont;
auto& networkAccessManager = NetworkAccessManager::getInstance();
@ -114,7 +255,7 @@ Font::Pointer Font::load(const QString& family) {
connect(networkReply, &QNetworkReply::finished, loadingFont.get(), &Font::handleFontNetworkReply);
} else if (!LOADED_FONTS.contains(ROBOTO_FONT_FAMILY)) {
// Unrecognized font and we haven't loaded Roboto yet
loadFilename = ":/Roboto.sdff";
loadFilename = ":/Roboto.arfont";
} else {
// Unrecognized font but we've already loaded Roboto
LOADED_FONTS[family] = LOADED_FONTS[ROBOTO_FONT_FAMILY];
@ -126,25 +267,13 @@ Font::Pointer Font::load(const QString& family) {
qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System.";
LOADED_FONTS[family] = load(fontFile);
LOADED_FONTS[family] = load(family, fontFile);
}
}
return LOADED_FONTS[family];
}
void Font::handleFontNetworkReply() {
auto requestReply = qobject_cast<QNetworkReply*>(sender());
Q_ASSERT(requestReply != nullptr);
if (requestReply->error() == QNetworkReply::NoError) {
setLoaded(true);
read(*requestReply);
} else {
qDebug() << "Error downloading " << requestReply->url() << " - " << requestReply->errorString();
}
}
Font::Font() {
Font::Font(const QString& family) : _family(family) {
static std::once_flag once;
std::call_once(once, []{
Q_INIT_RESOURCE(fonts);
@ -197,82 +326,6 @@ glm::vec2 Font::computeExtent(const QString& str) const {
return extent;
}
void Font::read(QIODevice& in) {
uint8_t header[4];
readStream(in, header);
if (memcmp(header, "SDFF", 4)) {
qDebug() << "Bad SDFF file";
_loaded = false;
return;
}
uint16_t version;
readStream(in, version);
// read font name
_family = "";
if (version > 0x0001) {
char c;
readStream(in, c);
while (c) {
_family += c;
readStream(in, c);
}
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) {
g.read(in);
});
// read image data
QImage image;
if (!image.loadFromData(in.readAll(), "PNG")) {
qDebug() << "Failed to read SDFF image";
_loaded = false;
return;
}
_glyphs.clear();
glm::vec2 imageSize = toGlm(image.size());
foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates,
g.texSize /= imageSize;
g.texOffset /= imageSize;
// store in the character to glyph hash
_glyphs[g.c] = g;
};
image = image.convertToFormat(QImage::Format_RGBA8888);
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
if (image.hasAlphaChannel()) {
formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::BGRA);
}
// FIXME: We're forcing this to use only one mip, and then manually doing anisotropic filtering in the shader,
// and also calling textureLod. Shouldn't this just use anisotropic filtering and auto-generate mips?
// We should also use smoothstep for anti-aliasing, as explained here: https://github.com/libgdx/libgdx/wiki/Distance-field-fonts
_texture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::SINGLE_MIP,
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR));
_texture->setStoredMipFormat(formatMip);
_texture->assignStoredMip(0, image.sizeInBytes(), image.constBits());
_texture->setImportant(true);
}
void Font::setupGPU() {
if (_pipelines.empty()) {
using namespace shader::render_utils::program;
@ -334,13 +387,21 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
drawInfo.bounds = bounds;
drawInfo.origin = origin;
float enlargedBoundsX = bounds.x - 0.5f * DOUBLE_MAX_OFFSET_PIXELS * float(enlargeForShadows);
float rightEdge = origin.x + enlargedBoundsX;
float rightEdge = origin.x + bounds.x;
// Top left of text
bool firstTokenOfLine = true;
glm::vec2 advance = origin;
std::vector<std::pair<Glyph, vec2>> glyphsAndCorners;
foreach(const QString& token, tokenizeForWrapping(str)) {
const QStringList tokens = tokenizeForWrapping(str);
for (int i = 0; i < tokens.length(); i++) {
const QString& token = tokens[i];
if ((bounds.y != -1) && (advance.y < origin.y - bounds.y)) {
// We are out of the y bound, stop drawing
break;
}
bool isNewLine = (token == QString('\n'));
bool forceNewLine = false;
@ -349,43 +410,50 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
// We are out of the x bound, force new line
forceNewLine = true;
}
if (isNewLine || forceNewLine) {
if (isNewLine || (forceNewLine && !firstTokenOfLine)) {
if (forceNewLine && !firstTokenOfLine) {
// We want to try this token again on the new line
i--;
}
// Character return, move the advance to a new line
advance = glm::vec2(origin.x, advance.y - _leading);
if (isNewLine) {
// No need to draw anything, go directly to next token
continue;
} else if (computeExtent(token).x > enlargedBoundsX) {
// token will never fit, stop drawing
break;
}
}
if ((bounds.y != -1) && (advance.y - _fontSize < origin.y - bounds.y)) {
// We are out of the y bound, stop drawing
break;
firstTokenOfLine = true;
// No need to draw anything, go directly to next token
continue;
}
// Draw the token
if (!isNewLine) {
for (auto c : token) {
auto glyph = _glyphs[c];
glyphsAndCorners.emplace_back(glyph, advance - glm::vec2(0.0f, _ascent));
// Advance by glyph size
advance.x += glyph.d;
for (const QChar& c : token) {
if (advance.x > rightEdge) {
break;
}
const Glyph& glyph = _glyphs[c];
glyphsAndCorners.emplace_back(glyph, advance);
// Advance by glyph size
advance.x += glyph.d;
}
if (forceNewLine && firstTokenOfLine) {
// If the first word of a line didn't fit, we draw as many characters as we could, now go to the next line
// Character return, move the advance to a new line
advance = glm::vec2(origin.x, advance.y - _leading);
firstTokenOfLine = true;
} else {
// Add space after all non return tokens
advance.x += _spaceWidth;
// Our token fits in the x direction! Any subsequent tokens won't be the first for this line.
firstTokenOfLine = false;
}
}
std::vector<QuadBuilder> quadBuilders;
quadBuilders.reserve(glyphsAndCorners.size());
{
int i = glyphsAndCorners.size() - 1;
int i = (int)glyphsAndCorners.size() - 1;
while (i >= 0) {
auto nextGlyphAndCorner = glyphsAndCorners[i];
float rightSpacing = rightEdge - (nextGlyphAndCorner.second.x + nextGlyphAndCorner.first.d);
@ -393,7 +461,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
alignment, rightSpacing));
i--;
while (i >= 0) {
auto prevGlyphAndCorner = glyphsAndCorners[i];
const auto& prevGlyphAndCorner = glyphsAndCorners[i];
// We're to the right of the last character we checked, which means we're on a previous line, so we need to
// recalculate the spacing, so we exit this loop
if (prevGlyphAndCorner.second.x >= nextGlyphAndCorner.second.x) {
@ -410,7 +478,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
}
// The quadBuilders is backwards now because we looped over the glyphs backwards to adjust their alignment
for (int i = quadBuilders.size() - 1; i >= 0; i--) {
for (int i = (int)quadBuilders.size() - 1; i >= 0; i--) {
quint16 verticesOffset = numVertices;
drawInfo.verticesBuffer->append(quadBuilders[i]);
numVertices += VERTICES_PER_QUAD;
@ -454,8 +522,10 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString
int textEffect = (int)effect;
const int SHADOW_EFFECT = (int)TextEffect::SHADOW_EFFECT;
const bool boundsChanged = bounds != drawInfo.bounds || origin != drawInfo.origin;
// If we're switching to or from shadow effect mode, we need to rebuild the vertices
if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin || alignment != _alignment ||
if (str != drawInfo.string || boundsChanged || alignment != _alignment ||
(drawInfo.params.effect != textEffect && (textEffect == SHADOW_EFFECT || drawInfo.params.effect == SHADOW_EFFECT)) ||
(textEffect == SHADOW_EFFECT && scale != _scale)) {
_scale = scale;
@ -465,8 +535,9 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString
setupGPU();
if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effectColor != effectColor ||
drawInfo.params.effectThickness != effectThickness || drawInfo.params.effect != textEffect) {
if (!drawInfo.paramsBuffer || boundsChanged || _needsParamsUpdate || drawInfo.params.color != color ||
drawInfo.params.effectColor != effectColor || drawInfo.params.effectThickness != effectThickness ||
drawInfo.params.effect != textEffect) {
drawInfo.params.color = color;
drawInfo.params.effectColor = effectColor;
drawInfo.params.effectThickness = effectThickness;
@ -474,14 +545,18 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString
// need the gamma corrected color here
DrawParams gpuDrawParams;
gpuDrawParams.bounds = glm::vec4(origin, bounds);
gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color);
gpuDrawParams.effectColor = ColorUtils::sRGBToLinearVec3(drawInfo.params.effectColor);
gpuDrawParams.effectThickness = drawInfo.params.effectThickness;
gpuDrawParams.unitRange = _distanceRange;
gpuDrawParams.effect = drawInfo.params.effect;
gpuDrawParams.effectThickness = drawInfo.params.effectThickness;
gpuDrawParams.effectColor = ColorUtils::sRGBToLinearVec3(drawInfo.params.effectColor);
if (!drawInfo.paramsBuffer) {
drawInfo.paramsBuffer = std::make_shared<gpu::Buffer>(sizeof(DrawParams), nullptr);
}
drawInfo.paramsBuffer->setSubData(0, sizeof(DrawParams), (const gpu::Byte*)&gpuDrawParams);
_needsParamsUpdate = false;
}
batch.setPipeline(_pipelines[std::make_tuple(color.a < 1.0f, unlit, forward)]);

View file

@ -24,22 +24,25 @@ class Font : public QObject {
public:
using Pointer = std::shared_ptr<Font>;
Font();
Font(const QString& family);
void read(QIODevice& path);
struct DrawParams {
vec4 color { 0 };
vec4 bounds { 0.0f };
vec4 color { 0.0f };
vec3 effectColor { 0 };
float effectThickness { 0 };
vec2 unitRange { 1.0f };
int effect { 0 };
float effectThickness { 0.0f };
vec3 effectColor { 0.0f };
#if defined(__clang__)
__attribute__((unused))
#endif
vec3 _spare;
float _spare;
};
struct DrawInfo {
@ -65,13 +68,12 @@ public:
static Pointer load(const QString& family);
bool isLoaded() const { return _loaded; }
void setLoaded(bool loaded) { _loaded = loaded; }
public slots:
void handleFontNetworkReply();
private:
static Pointer load(QIODevice& fontFile);
static Pointer load(const QString& family, QIODevice& fontFile);
QStringList tokenizeForWrapping(const QString& str) const;
QStringList splitLines(const QString& str) const;
glm::vec2 computeTokenExtent(const QString& str) const;
@ -91,16 +93,16 @@ private:
// Font characteristics
QString _family;
glm::vec2 _distanceRange { 1.0f };
float _fontSize { 0.0f };
float _leading { 0.0f };
float _ascent { 0.0f };
float _descent { 0.0f };
float _spaceWidth { 0.0f };
float _scale { 0.0f };
TextAlignment _alignment;
TextAlignment _alignment { TextAlignment::LEFT };
bool _loaded { true };
bool _loaded { false };
bool _needsParamsUpdate { false };
gpu::TexturePointer _texture;
gpu::BufferStreamPointer _stream;

View file

@ -9,15 +9,3 @@ QRectF Glyph::bounds() const {
QRectF Glyph::textureBounds() const {
return glmToRect(texOffset, texSize);
}
void Glyph::read(QIODevice& in) {
uint16_t charcode;
readStream(in, charcode);
c = charcode;
readStream(in, texOffset);
readStream(in, size);
readStream(in, offset);
readStream(in, d);
// texSize is divided by the image size later
texSize = size;
}

View file

@ -26,13 +26,10 @@ struct Glyph {
vec2 size;
vec2 offset;
float d; // xadvance - adjusts character positioning
size_t indexOffset;
// We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect
QRectF bounds() const;
QRectF textureBounds() const;
void read(QIODevice& in);
};
#endif

View file

@ -139,12 +139,12 @@ void IDsToBounds::run(const RenderContextPointer& renderContext, const ItemIDs&
for (auto id : inItems) {
auto& item = scene->getItem(id);
if (item.exist()) {
outItems.emplace_back(ItemBound{ id, item.getBound(renderContext->args) });
outItems.emplace_back(ItemBound(id, item.getBound(renderContext->args)));
}
}
} else {
for (auto id : inItems) {
outItems.emplace_back(ItemBound{ id });
outItems.emplace_back(ItemBound(id));
}
}
}

View file

@ -339,9 +339,9 @@ class ItemBound {
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
ItemID id;
ItemID id { 0 };
AABox bound;
uint32_t padding;
uint32_t padding { 0 };
};
// many Item Bounds in a vector

View file

@ -74,7 +74,7 @@ void AssetScriptingInterface::uploadData(QString data, const ScriptValue& callba
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray);
Promise deferred = makePromise(__FUNCTION__);
Q_ASSERT(engine);
Q_ASSERT(engine());
auto scriptEngine = engine();
deferred->ready([=](QString error, QVariantMap result) {
auto url = result.value("url").toString();
@ -98,7 +98,7 @@ void AssetScriptingInterface::setMapping(QString path, QString hash, const Scrip
auto handler = jsBindCallback(thisObject(), callback);
auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash);
Promise deferred = makePromise(__FUNCTION__);
Q_ASSERT(engine);
Q_ASSERT(engine());
auto scriptEngine = engine();
deferred->ready([=](QString error, QVariantMap result) {
jsCallback(handler, scriptEngine->newValue(error), result);
@ -136,7 +136,7 @@ void AssetScriptingInterface::downloadData(QString urlString, const ScriptValue&
auto assetRequest = assetClient->createRequest(hash);
Promise deferred = makePromise(__FUNCTION__);
Q_ASSERT(engine);
Q_ASSERT(engine());
auto scriptEngine = engine();
deferred->ready([=](QString error, QVariantMap result) {
// FIXME: to remain backwards-compatible the signature here is "callback(data, n/a)"
@ -200,7 +200,7 @@ void AssetScriptingInterface::getMapping(QString asset, const ScriptValue& callb
JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")");
JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function");
Promise promise = getAssetInfo(path);
Q_ASSERT(engine);
Q_ASSERT(engine());
auto scriptEngine = engine();
promise->ready([=](QString error, QVariantMap result) {
jsCallback(handler, scriptEngine->newValue(error), scriptEngine->newValue(result.value("hash").toString()));
@ -234,7 +234,7 @@ Promise AssetScriptingInterface::jsPromiseReady(Promise promise, const ScriptVal
if (!jsVerify(handler.isValid(), "jsPromiseReady -- invalid callback handler")) {
return nullptr;
}
Q_ASSERT(engine);
Q_ASSERT(engine());
auto scriptEngine = engine();
return promise->ready([this, handler, scriptEngine](QString error, QVariantMap result) {
jsCallback(handler, scriptEngine->newValue(error), result);
@ -244,7 +244,6 @@ Promise AssetScriptingInterface::jsPromiseReady(Promise promise, const ScriptVal
void AssetScriptingInterface::jsCallback(const ScriptValue& handler,
const ScriptValue& error, const ScriptValue& result) {
Q_ASSERT(thread() == QThread::currentThread());
Q_ASSERT(engine);
//V8TODO: which kind of script context guard needs to be used here?
ScriptContextGuard scriptContextGuard(_scriptManager->engine()->currentContext());
auto errorValue = !error.toBool() ? engine()->nullValue() : error;
@ -546,7 +545,7 @@ void AssetScriptingInterface::loadFromCache(const ScriptValue& options, const Sc
}
bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) {
Q_ASSERT(engine);
Q_ASSERT(engine());
auto scriptManager = engine()->manager();
if (!scriptManager) {
return false;

View file

@ -82,14 +82,14 @@ ScriptValue ConsoleScriptingInterface::exception(ScriptContext* context, ScriptE
void ConsoleScriptingInterface::time(QString labelName) {
_timerDetails.insert(labelName, QDateTime::currentDateTime().toUTC());
QString message = QString("%1: Timer started").arg(labelName);
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
scriptManager->scriptPrintedMessage(message, context()->currentFileName(), context()->currentLineNumber());
}
}
void ConsoleScriptingInterface::timeEnd(QString labelName) {
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
if (!_timerDetails.contains(labelName)) {
scriptManager->scriptErrorMessage("No such label found " + labelName, context()->currentFileName(), context()->currentLineNumber());
@ -138,7 +138,7 @@ ScriptValue ConsoleScriptingInterface::assertion(ScriptContext* context, ScriptE
}
void ConsoleScriptingInterface::trace() {
Q_ASSERT(engine);
Q_ASSERT(engine());
ScriptEnginePointer scriptEngine = engine();
if (ScriptManager* scriptManager = scriptEngine->manager()) {
scriptManager->scriptPrintedMessage
@ -148,7 +148,7 @@ void ConsoleScriptingInterface::trace() {
}
void ConsoleScriptingInterface::clear() {
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
scriptManager->clearDebugLogWindow();
}

View file

@ -90,7 +90,7 @@ void Mat4::print(const QString& label, const glm::mat4& m, bool transpose) const
QString message = QString("%1 %2").arg(qPrintable(label));
message = message.arg(glm::to_string(out).c_str());
qCDebug(scriptengine) << message;
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
scriptManager->print(message);
}

View file

@ -126,7 +126,7 @@ void Quat::print(const QString& label, const glm::quat& q, bool asDegrees) {
message = message.arg(glm::to_string(glm::dquat(q)).c_str());
}
qCDebug(scriptengine) << message;
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
scriptManager->print(message);
}

View file

@ -267,7 +267,7 @@ signals:
* Triggered when a client side entity script prints a message to the program log via {@link print}, {@link Script.print},
* {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or
* {@link console.timeEnd}.
* @function Script.printedMessage
* @function Script.printedEntityMessage
* @param {string} message - The message.
* @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.
* @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.
@ -280,7 +280,7 @@ signals:
/*@jsdoc
* Triggered when a client side entity script generates an error, {@link console.error} or {@link console.exception} is called, or
* {@link console.assert} is called and fails.
* @function Script.errorMessage
* @function Script.errorEntityMessage
* @param {string} message - The error message.
* @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.
* @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.
@ -292,7 +292,7 @@ signals:
/*@jsdoc
* Triggered when a client side entity script generates a warning or {@link console.warn} is called.
* @function Script.warningMessage
* @function Script.warningEntityMessage
* @param {string} message - The warning message.
* @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.
* @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.
@ -304,7 +304,7 @@ signals:
/*@jsdoc
* Triggered when a client side entity script generates an information message or {@link console.info} is called.
* @function Script.infoMessage
* @function Script.infoEntityMessage
* @param {string} message - The information message.
* @param {string} fileName - Name of the file in which message was generated. Empty string when no file name is available.
* @param {number} lineNumber - Number of the line on which message was generated. -1 if there line number is not available.

View file

@ -478,10 +478,10 @@ void ScriptManager::waitTillDoneRunning(bool shutdown) {
}
}
#else
auto startedWaiting = usecTimestampNow();
//auto startedWaiting = usecTimestampNow();
while (!_isDoneRunning) {
// If the final evaluation takes too long, then tell the script engine to stop running
auto elapsedUsecs = usecTimestampNow() - startedWaiting;
//auto elapsedUsecs = usecTimestampNow() - startedWaiting;
// TODO: This part was very unsafe and was causing crashes all the time.
// I disabled it for now until we find a safer solution.
// With it disabled now we get clean shutdowns and restarts.
@ -589,6 +589,10 @@ void ScriptManager::scriptErrorMessage(const QString& message, const QString& fi
emit errorMessage(message, getFilename());
if (!currentEntityIdentifier.isInvalidID()) {
emit errorEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
} else {
if (isEntityServerScript()) {
emit errorEntityMessage(message, fileName, lineNumber, EntityItemID(), isEntityServerScript());
}
}
}
@ -597,6 +601,10 @@ void ScriptManager::scriptWarningMessage(const QString& message, const QString&
emit warningMessage(message, getFilename());
if (!currentEntityIdentifier.isInvalidID()) {
emit warningEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
} else {
if (isEntityServerScript()) {
emit warningEntityMessage(message, fileName, lineNumber, EntityItemID(), isEntityServerScript());
}
}
}
@ -605,6 +613,10 @@ void ScriptManager::scriptInfoMessage(const QString& message, const QString& fil
emit infoMessage(message, getFilename());
if (!currentEntityIdentifier.isInvalidID()) {
emit infoEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
} else {
if (isEntityServerScript()) {
emit infoEntityMessage(message, fileName, lineNumber, EntityItemID(), isEntityServerScript());
}
}
}
@ -613,6 +625,12 @@ void ScriptManager::scriptPrintedMessage(const QString& message, const QString&
emit printedMessage(message, getFilename());
if (!currentEntityIdentifier.isInvalidID()) {
emit printedEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());
} else {
if (isEntityServerScript()) {
// TODO: Some callbacks like for example websockets one right now
// don't set currentEntityIdentifier and there doesn't seem to be easy way to add it currently
emit printedEntityMessage(message, fileName, lineNumber, EntityItemID(), isEntityServerScript());
}
}
}
@ -2036,7 +2054,7 @@ void ScriptManager::loadEntityScript(const EntityItemID& entityID, const QString
std::weak_ptr<ScriptManager> weakRef(shared_from_this());
scriptCache->getScriptContents(entityScript,
[this, weakRef, entityScript, entityID](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) {
std::shared_ptr<ScriptManager> strongRef(weakRef);
std::shared_ptr<ScriptManager> strongRef = weakRef.lock();
if (!strongRef) {
qCWarning(scriptengine) << "loadEntityScript.contentAvailable -- ScriptManager was deleted during getScriptContents!!";
return;

View file

@ -45,7 +45,7 @@ void ScriptUUID::print(const QString& label, const QUuid& id) {
QString message = QString("%1 %2").arg(qPrintable(label));
message = message.arg(id.toString());
qCDebug(scriptengine) << message;
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
scriptManager->print(message);
}

View file

@ -39,7 +39,7 @@ void Vec3::print(const QString& label, const glm::vec3& v) {
QString message = QString("%1 %2").arg(qPrintable(label));
message = message.arg(glm::to_string(glm::dvec3(v)).c_str());
qCDebug(scriptengine) << message;
Q_ASSERT(engine);
Q_ASSERT(engine());
if (ScriptManager* scriptManager = engine()->manager()) {
scriptManager->print(message);
}

View file

@ -33,8 +33,6 @@ class ScriptEngine;
* <p>Create using <code>new WebSocket(...)</code> in Interface, client entity, avatar, and server entity scripts, or the
* {@link WebSocketServer} class in server entity and assignment client scripts.
*
* <p><strong>Note:</strong> Does not support secure, <code>wss:</code> protocol.</p>
*
* @class WebSocket
* @param {string|WebSocket} urlOrWebSocket - The URL to connect to or an existing {@link WebSocket} to reuse the connection of.
*

View file

@ -56,7 +56,7 @@ bool qBytearrayFromScriptValue(const ScriptValue& object, QByteArray &qByteArray
return false;
}
v8::Local<v8::ArrayBuffer> arrayBuffer = v8::Local<v8::ArrayBuffer>::Cast(v8Value);
qByteArray.resize(arrayBuffer->ByteLength());
qByteArray.resize((int)arrayBuffer->ByteLength());
memcpy(qByteArray.data(), arrayBuffer->Data(), arrayBuffer->ByteLength());
return true;
}

View file

@ -539,7 +539,7 @@ void ScriptEngineV8::storeGlobalObjectContents() {
v8::Local<v8::Object> globalMemberObjects = v8::Object::New(_v8Isolate);
auto globalMemberNames = context->Global()->GetPropertyNames(context).ToLocalChecked();
for (size_t i = 0; i < globalMemberNames->Length(); i++) {
for (uint32_t i = 0; i < globalMemberNames->Length(); i++) {
auto name = globalMemberNames->Get(context, i).ToLocalChecked();
if(!globalMemberObjects->Set(context, name, context->Global()->Get(context, name).ToLocalChecked()).FromMaybe(false)) {
Q_ASSERT(false);
@ -645,7 +645,7 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure,
// Since V8 cannot use arbitrary object as global object, objects from main global need to be copied to closure's global object
auto globalObjectContents = _globalObjectContents.Get(_v8Isolate);
auto globalMemberNames = globalObjectContents->GetPropertyNames(globalObjectContents->CreationContext()).ToLocalChecked();
for (size_t i = 0; i < globalMemberNames->Length(); i++) {
for (uint32_t i = 0; i < globalMemberNames->Length(); i++) {
auto name = globalMemberNames->Get(closureContext, i).ToLocalChecked();
if(!closureContext->Global()->Set(closureContext, name, globalObjectContents->Get(globalObjectContents->CreationContext(), name).ToLocalChecked()).FromMaybe(false)) {
Q_ASSERT(false);
@ -656,7 +656,7 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure,
// Objects from closure need to be copied to global object too
// V8TODO: I'm not sure which context to use with Get
auto closureMemberNames = closureObject->GetPropertyNames(closureContext).ToLocalChecked();
for (size_t i = 0; i < closureMemberNames->Length(); i++) {
for (uint32_t i = 0; i < closureMemberNames->Length(); i++) {
auto name = closureMemberNames->Get(closureContext, i).ToLocalChecked();
if(!closureContext->Global()->Set(closureContext, name, closureObject->Get(closureContext, name).ToLocalChecked()).FromMaybe(false)) {
Q_ASSERT(false);
@ -717,7 +717,7 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure,
auto requireMemberNames =
oldRequireObject->GetPropertyNames(oldRequireObject->CreationContext()).ToLocalChecked();
for (size_t i = 0; i < requireMemberNames->Length(); i++) {
for (uint32_t i = 0; i < requireMemberNames->Length(); i++) {
auto name = requireMemberNames->Get(closureContext, i).ToLocalChecked();
v8::Local<v8::Value> oldObject;
if (!oldRequireObject->Get(oldRequireObject->CreationContext(), name).ToLocal(&oldObject)) {

View file

@ -1309,7 +1309,8 @@ int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** argu
}
v8::TryCatch tryCatch(isolate);
callback->Call(functionContext, v8This, numArgs, args);
auto maybeResult = callback->Call(functionContext, v8This, numArgs, args);
Q_UNUSED(maybeResult); // Signals don't have return values
if (tryCatch.HasCaught()) {
QString errorMessage(QString("Signal proxy ") + fullName() + " connection call failed: \""
+ _engine->formatErrorMessageFromTryCatch(tryCatch)

View file

@ -119,9 +119,9 @@ struct BlendshapeOffsetPacked {
};
struct BlendshapeOffsetUnpacked {
glm::vec3 positionOffset;
glm::vec3 normalOffset;
glm::vec3 tangentOffset;
float positionOffsetX, positionOffsetY, positionOffsetZ;
float normalOffsetX, normalOffsetY, normalOffsetZ;
float tangentOffsetX, tangentOffsetY, tangentOffsetZ;
};
using BlendshapeOffset = BlendshapeOffsetPacked;

View file

@ -25,7 +25,9 @@
#if defined(_MSC_VER) && _MSC_VER < 1900
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
// The following struct is not compliant with the HF coding standard, but uses underscores to match the classes

View file

@ -205,25 +205,33 @@ public:
* @typedef {object} PickRay
* @property {Vec3} origin - The starting position of the ray.
* @property {Vec3} direction - The direction that the ray travels.
* @property {Vec3} unmodifiedDirection - The direction that the ray would travel, if not for applied effects like delays.
*/
class PickRay : public MathPick {
public:
PickRay() : origin(NAN), direction(NAN) { }
PickRay(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), direction(vec3FromVariant(pickVariant["direction"])) {}
PickRay(const glm::vec3& origin, const glm::vec3 direction) : origin(origin), direction(direction) {}
PickRay() : origin(NAN), direction(NAN), unmodifiedDirection(NAN) { }
PickRay(const QVariantMap& pickVariant) :
origin(vec3FromVariant(pickVariant["origin"])), direction(vec3FromVariant(pickVariant["direction"])),
unmodifiedDirection(vec3FromVariant(pickVariant["unmodifiedDirection"])) {}
PickRay(const glm::vec3& origin, const glm::vec3& direction) :
origin(origin), direction(direction), unmodifiedDirection(direction) {}
PickRay(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& unmodifiedDirection) :
origin(origin), direction(direction), unmodifiedDirection(unmodifiedDirection) {}
glm::vec3 origin;
glm::vec3 direction;
glm::vec3 unmodifiedDirection;
operator bool() const override {
return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(direction)));
return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(direction)) || glm::any(glm::isnan(unmodifiedDirection)));
}
bool operator==(const PickRay& other) const {
return (origin == other.origin && direction == other.direction);
return (origin == other.origin && direction == other.direction && unmodifiedDirection == other.unmodifiedDirection);
}
QVariantMap toVariantMap() const override {
QVariantMap pickRay;
pickRay["origin"] = vec3toVariant(origin);
pickRay["direction"] = vec3toVariant(direction);
pickRay["unmodifiedDirection"] = vec3toVariant(unmodifiedDirection);
return pickRay;
}
};

View file

@ -28,7 +28,7 @@ const int NUM_FRUSTUM_CORNERS = 8;
const int NUM_FRUSTUM_PLANES = 6;
const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f;
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f;
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 55.0f;
const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
const float DEFAULT_NEAR_CLIP = 0.08f;
const float DEFAULT_FAR_CLIP = 16384.0f;

Some files were not shown because too many files have changed in this diff Show more