Merge branch 'master' into fix/interface-console

# Conflicts:
#	README.md
This commit is contained in:
David Rowe 2020-12-18 19:44:20 +13:00
commit 71cee734ec
119 changed files with 2185 additions and 2002 deletions

View file

@ -3,8 +3,7 @@ name: Master CI Build
on:
push:
branches:
- gha-master-ci
# FIXME: Change target branch to "master" before merging into "master" branch.
- master
env:
APP_NAME: interface
@ -48,50 +47,51 @@ jobs:
shell: bash
id: buildenv1
run: |
echo ::set-env name=UPLOAD_PREFIX::master
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
echo "UPLOAD_PREFIX=master" >> $GITHUB_ENV
echo ::set-output name=github_sha_short::`echo $GIT_COMMIT | cut -c1-7`
echo "JOB_NAME=build (${{matrix.os}}, ${{matrix.build_type}})" >> $GITHUB_ENV
# Linux build variables
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=INSTALLER_EXT::tgz
echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)"
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
echo "INSTALLER_EXT=tgz" >> $GITHUB_ENV
echo "CMAKE_BUILD_EXTRA=-- -j3" >> $GITHUB_ENV
echo "CMAKE_EXTRA=-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)" >> $GITHUB_ENV
fi
# Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=ZIP_COMMAND::zip
echo ::set-env name=ZIP_ARGS::-r
echo ::set-env name=INSTALLER_EXT::dmg
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
echo "ZIP_COMMAND=zip" >> $GITHUB_ENV
echo "ZIP_ARGS=-r" >> $GITHUB_ENV
echo "INSTALLER_EXT=dmg" >> $GITHUB_ENV
echo "CMAKE_EXTRA=-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode" >> $GITHUB_ENV
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-mac-symbols.zip"
fi
# Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then
echo ::set-env name=PYTHON_EXEC::python
echo ::set-env name=ZIP_COMMAND::7z
echo ::set-env name=ZIP_ARGS::a
echo ::set-env name=INSTALLER_EXT::exe
echo ::set-env name=CMAKE_EXTRA::"-A x64"
echo "::set-env name=SYMBOL_REGEX::\(exe\|dll\|pdb\)"
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-win-symbols.zip"
# echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}}
# echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx"
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=${BUILD_NUMBER}-${{ matrix.build_type }}-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
fi
# Configuration is broken into two steps because you can't set an env var and also reference it in the same step
- name: Configure build environment 2
shell: bash
run: |
echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo ::set-env name=ARTIFACT_PATTERN::Vircadia-Alpha-*.$INSTALLER_EXT
echo "ARTIFACT_PATTERN=Vircadia-Alpha-*.$INSTALLER_EXT" >> $GITHUB_ENV
# Build type variables
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
if [ "${{ matrix.build_type }}" = "full" ]; then
echo ::set-env name=CLIENT_ONLY::FALSE
echo ::set-env name=INSTALLER::Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo "CLIENT_ONLY=FALSE" >> $GITHUB_ENV
echo "INSTALLER=Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
else
echo ::set-env name=CLIENT_ONLY::TRUE
echo ::set-env name=INSTALLER::Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo "CLIENT_ONLY=TRUE" >> $GITHUB_ENV
echo "INSTALLER=Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
fi
- name: Clear working directory
if: startsWith(matrix.os, 'windows')

View file

@ -14,7 +14,9 @@ env:
RELEASE_TYPE: PR
RELEASE_NUMBER: ${{ github.event.number }}
VERSION_CODE: ${{ github.event.number }}
# Sentry Crash Reporting
CMAKE_BACKTRACE_URL: ${{ secrets.MINIDUMP_TOKEN }}
CMAKE_BACKTRACE_TOKEN: PR_${{ github.event.number }}_${{ github.sha }}
# OSX specific variables
DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer
@ -22,19 +24,24 @@ env:
# WIN32 specific variables
PreferredToolArchitecture: X64
GIT_COMMIT_SHORT: ${{ github.sha }}
jobs:
build:
name: "build (${{matrix.os}}, ${{matrix.build_type}})"
strategy:
matrix:
os: [windows-latest, macOS-latest, ubuntu-18.04]
build_type: [full]
include:
- os: windows-latest
build_type: full
- os: macOS-latest
build_type: full
- os: ubuntu-18.04
build_type: full
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
- os: ubuntu-18.04
build_type: android
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
fail-fast: false
runs-on: ${{matrix.os}}
if: github.event.action != 'labeled' || github.event.label.name == 'rebuild'
@ -43,36 +50,48 @@ jobs:
shell: bash
id: buildenv1
run: |
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
echo ::set-output name=github_sha_short::`echo $GIT_COMMIT | cut -c1-7`
echo "JOB_NAME=build (${{matrix.os}}, ${{matrix.build_type}})" >> $GITHUB_ENV
# Linux build variables
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=INSTALLER_EXT::*
echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)"
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
echo "INSTALLER_EXT=*" >> $GITHUB_ENV
echo "CMAKE_BUILD_EXTRA=-- -j3" >> $GITHUB_ENV
echo "CMAKE_EXTRA=-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)" >> $GITHUB_ENV
fi
# Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=INSTALLER_EXT::dmg
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
echo "INSTALLER_EXT=dmg" >> $GITHUB_ENV
echo "CMAKE_EXTRA=-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode" >> $GITHUB_ENV
fi
# Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then
echo ::set-env name=PYTHON_EXEC::python
echo ::set-env name=INSTALLER_EXT::exe
echo ::set-env name=CMAKE_EXTRA::"-A x64"
echo "PYTHON_EXEC=python" >> $GITHUB_ENV
echo "INSTALLER_EXT=exe" >> $GITHUB_ENV
echo "CMAKE_EXTRA=-A x64" >> $GITHUB_ENV
fi
# Android + Quest build variables
if [[ "${{ matrix.build_type }}" == "android" ]]; then
HIFI_ANDROID_PRECOMPILED="${{runner.workspace}}/dependencies"
echo "HIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED" >> $GITHUB_ENV
mkdir $HIFI_ANDROID_PRECOMPILED
echo "INSTALLER_EXT=apk" >> $GITHUB_ENV
fi
# Configuration is broken into two steps because you can't set an env var and also reference it in the same step
- name: Configure Build Environment 2
shell: bash
run: |
echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo ::set-env name=ARTIFACT_PATTERN::Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT
# Build type variables
echo ::set-env name=INSTALLER::Vircadia-Alpha-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
if [[ "${{ matrix.build_type }}" != "android" ]]; then
echo "ARTIFACT_PATTERN=Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV
# Build type variables
echo "INSTALLER=Vircadia-Alpha-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
else
echo "ARTIFACT_PATTERN=*.$INSTALLER_EXT" >> $GITHUB_ENV
fi
- name: Clear Working Directory
if: startsWith(matrix.os, 'windows')
shell: bash
@ -102,22 +121,27 @@ jobs:
shell: bash
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release $CMAKE_EXTRA
- name: Build Application
if: matrix.build_type == 'full' || matrix.build_type == 'client'
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
- name: Build Domain Server
if: matrix.build_type == 'full'
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
- name: Build Assignment Client
if: matrix.build_type == 'full'
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
- name: Build Console
if: matrix.build_type == 'full'
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
- name: Build Installer
if: matrix.build_type != 'android'
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
@ -144,6 +168,18 @@ jobs:
done
}
retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
- name: Build for Android + Quest
if: matrix.build_type == 'android'
shell: bash
working-directory: ${{runner.workspace}}/vircadia
run: |
echo "Pre-cache the vcpkg managed dependencies"
$PYTHON_EXEC prebuild.py --build-root ${{runner.workspace}}/build --android interface
cd android
# Pre-cache the gradle dependencies
./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
# Build!
./build_android.sh
- name: Output system stats
if: ${{ always() }}
working-directory: ${{runner.workspace}}/build
@ -161,4 +197,8 @@ jobs:
working-directory: ${{runner.workspace}}/build
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: $PYTHON_EXEC "$GITHUB_WORKSPACE/tools/ci-scripts/upload_to_publish_server.py"
run: |
if [[ "${{ matrix.build_type }}" == "android" ]]; then
cd $GITHUB_WORKSPACE/android
fi
$PYTHON_EXEC "$GITHUB_WORKSPACE/tools/ci-scripts/upload_to_publish_server.py"

View file

@ -66,7 +66,7 @@ The above code to suppress modules is not necessary, but will speed up the build
### Clone the repository
`git clone https://github.com/kasenvr/project-athena.git`
`git clone https://github.com/vircadia/vircadia.git`
## Building & Running
@ -119,4 +119,4 @@ Some things you can try if you want to do a clean build
* In Android Studio, click _File > Invalidate Caches / Restart_ and select _Invalidate and Restart_
If you see lots of "couldn't acquire lock" errors,
* Open Task Manager and close any running Clang / Gradle processes
* Open Task Manager and close any running Clang / Gradle processes

View file

@ -1,9 +1,11 @@
# Build Linux
*Last Updated on April 11, 2020*
*Last Updated on December 1, 2020*
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
You can use the [Vircadia Builder](https://github.com/vircadia/vircadia-builder) to build on Linux more easily. Alternatively, you can follow the manual steps below.
## Ubuntu 16.04/18.04 specific build guide
### Ubuntu 16.04 only
Add the following line to *.bash_profile*
@ -76,7 +78,7 @@ sudo apt-get install nodejs
Clone this repository:
```bash
git clone https://github.com/kasenvr/project-athena.git
git clone https://github.com/vircadia/vircadia.git
```
To compile a DEV version checkout the branch you need. To get a list of all tags:
@ -86,7 +88,7 @@ git fetch -a
Then checkout the main branch with:
```bash
git checkout kasen/core
git checkout master
```
### Using a custom Qt build
@ -103,7 +105,7 @@ Qt must be installed in `$HIFI_QT_BASE/$VIRCADIA_USE_QT_VERSION/qt5-install`.
Create the build directory:
```bash
cd project-athena
cd vircadia
mkdir build
cd build
```

View file

@ -107,7 +107,7 @@ Note: You can also run Interface by launching it from command line or File Explo
For any problems after Step #6, first try this:
* Delete your locally cloned copy of the Vircadia repository
* Restart your computer
* Redownload the [repository](https://github.com/kasenvr/project-athena)
* Redownload the [repository](https://github.com/vircadia/vircadia)
* Restart directions from Step #6
#### CMake gives you the same error message repeatedly after the build fails

View file

@ -41,6 +41,12 @@ endif()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" "${EXTERNAL_BUILD_ASSETS}")
MESSAGE(STATUS "EXTERNAL_BUILD_ASSETS: ${EXTERNAL_BUILD_ASSETS}")
set(GLES_OPTION "$ENV{USE_GLES}")
# Will affect VCPKG dependencies
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_env/USE_GLES.txt" "${GLES_OPTION}")
MESSAGE(STATUS "GLES_OPTION: ${GLES_OPTION}")
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake")
target_python()
@ -129,7 +135,6 @@ set(BUILD_TESTS_OPTION OFF)
set(BUILD_MANUAL_TESTS_OPTION ${BUILD_TESTS_OPTION})
set(BUILD_TOOLS_OPTION ON)
set(BUILD_INSTALLER_OPTION ON)
set(GLES_OPTION OFF)
set(DISABLE_QML_OPTION OFF)
set(DOWNLOAD_SERVERLESS_CONTENT_OPTION OFF)

View file

@ -1,3 +1,4 @@
The project embraces distributed development and if you'd like to help, it would be greatly appreciated. Just open a pull request with the revisions.
Contributing
@ -6,7 +7,7 @@ Contributing
2. Clone your fork of the repository locally
```
git clone git://github.com/USERNAME/project-athena.git
git clone git://github.com/USERNAME/vircadia.git
```
3. Create a new branch
@ -20,7 +21,7 @@ Contributing
6. Update your branch
```
git remote add upstream https://github.com/kasenvr/project-athena
git remote add upstream https://github.com/vircadia/vircadia
git pull upstream master
```
@ -35,13 +36,22 @@ Contributing
*You can follow [GitHub's guide](https://help.github.com/articles/creating-a-pull-request) to find out how to create a pull request.*
Tips for Pull Requests
===
To make the QA process go as smoothly as possible.
1. Have a basic description in your pull request.
2. Write a basic test plan if you are altering or adding features.
3. If a new API is added, try to make sure that some level of basic documentation on how you can utilize it is included.
4. If an added API or feature requires an external service, try to document or link to instructions on how to create a basic working setup.
Reporting Bugs
===
1. Always update to the latest code on master, we make many merges every day and it is possible the bug has already been fixed!
2. Search [issues](https://github.com/kasenvr/project-athena/issues) to make sure that somebody has not already reported the same bug.
3. [Add](https://github.com/kasenvr/project-athena/issues/new) your report to the issues list!
2. Search [issues](https://github.com/vircadia/vircadia/issues) to make sure that somebody has not already reported the same bug.
3. [Add](https://github.com/vircadia/vircadia/issues/new) your report to the issues list!
Requesting a Feature
===
1. Search [issues](https://github.com/kasenvr/project-athena/issues) to make sure that somebody has not already requested the same feature.
2. [Add](https://github.com/kasenvr/project-athena/issues/new) your request to the issues list!
1. Search [issues](https://github.com/vircadia/vircadia/issues) to make sure that somebody has not already requested the same feature.
2. [Add](https://github.com/vircadia/vircadia/issues/new) your request to the issues list!

View file

@ -8,17 +8,17 @@ Vircadia is a 3D social software project seeking to incrementally bring about a
### Releases
[View Releases here](https://github.com/kasenvr/project-athena/releases/)
[View Releases here](https://github.com/vircadia/vircadia/releases/)
### How to build the Interface
[For Windows](https://github.com/kasenvr/project-athena/blob/master/BUILD_WIN.md)
[For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md)
[For Mac](https://github.com/kasenvr/project-athena/blob/master/BUILD_OSX.md)
[For Mac](https://github.com/vircadia/vircadia/blob/master/BUILD_OSX.md)
[For Linux](https://github.com/kasenvr/project-athena/blob/master/BUILD_LINUX.md)
[For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md)
[For Linux - Vircadia Builder](https://github.com/kasenvr/vircadia-builder)
[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder)
### How to deploy a Server
@ -26,13 +26,13 @@ Vircadia is a 3D social software project seeking to incrementally bring about a
### How to build a Server
[For Linux - Vircadia Builder](https://github.com/kasenvr/vircadia-builder)
[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder)
### How to generate an Installer
[For Windows](https://github.com/kasenvr/project-athena/blob/master/INSTALLER.md)
[For Windows](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md)
[For Linux - AppImage - Vircadia Builder](https://github.com/kasenvr/vircadia-builder/blob/master/README.md#building-appimages)
[For Linux - AppImage - Vircadia Builder](https://github.com/vircadia/vircadia-builder/blob/master/README.md#building-appimages)
### Boot to Metaverse: The Goal

View file

@ -5,11 +5,11 @@ ANDROID_BUILD_TYPE=release
ANDROID_BUILD_TARGET=assembleRelease
if [[ "$RELEASE_TYPE" == "PR" ]]; then
ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ;
ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk ;
elif [[ "${STABLE_BUILD}" == "1" ]]; then
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}.apk ;
else
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ;
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk ;
fi
@ -17,7 +17,7 @@ fi
ANDROID_APP=interface
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
ANDROID_APK_NAME=HighFidelity-Beta-${ANDROID_APK_SUFFIX}
ANDROID_APK_NAME=Vircadia-Alpha-${ANDROID_APK_SUFFIX}
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET}
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
@ -25,9 +25,9 @@ cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
ANDROID_APP=questInterface
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
ANDROID_APK_NAME=HighFidelity-Quest-Beta-${ANDROID_APK_SUFFIX}
ANDROID_APK_NAME=Vircadia-Quest-Alpha-${ANDROID_APK_SUFFIX}
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} || true
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} || true
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}

View file

@ -1,9 +1,9 @@
#!/usr/bin/env bash
set -xeuo pipefail
DOCKER_IMAGE_NAME="hifi_androidbuild"
DOCKER_IMAGE_NAME="vircadia_androidbuild"
docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/Dockerfile docker
docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f ./android/docker/Dockerfile ./android/docker
# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION
# So make sure we use VERSION_CODE consistently
@ -17,7 +17,7 @@ test -z "$STABLE_BUILD" && export STABLE_BUILD=0
docker run \
--rm \
--security-opt seccomp:unconfined \
-v "${WORKSPACE}":/home/jenkins/hifi \
-v "${WORKSPACE}":/home/gha/vircadia \
-e RELEASE_NUMBER \
-e RELEASE_TYPE \
-e ANDROID_APP \
@ -33,7 +33,7 @@ docker run \
-e OAUTH_CLIENT_SECRET \
-e OAUTH_CLIENT_ID \
-e OAUTH_REDIRECT_URI \
-e SHA7 \
-e GIT_COMMIT_SHORT \
-e STABLE_BUILD \
-e VERSION_CODE \
"${DOCKER_IMAGE_NAME}" \

View file

@ -57,13 +57,13 @@ RUN apt-get -y install \
# --- Gradle
ARG BUILD_UID=1001
RUN useradd -ms /bin/bash -u $BUILD_UID jenkins
RUN echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER jenkins
WORKDIR /home/jenkins
RUN useradd -ms /bin/bash -u $BUILD_UID gha
RUN echo "gha ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER gha
WORKDIR /home/gha
# Hifi dependencies
ENV HIFI_BASE="/home/jenkins/hifi_android"
# Vircadia dependencies
ENV HIFI_BASE="/home/gha/vircadia_android"
ENV HIFI_ANDROID_PRECOMPILED="$HIFI_BASE/dependencies"
ENV HIFI_VCPKG_BASE="$HIFI_BASE/vcpkg"
@ -71,24 +71,18 @@ RUN mkdir "$HIFI_BASE" && \
mkdir "$HIFI_VCPKG_BASE" && \
mkdir "$HIFI_ANDROID_PRECOMPILED"
# Checkout a relatively recent commit from the main repository and use it to cache the
# gradle and vcpkg dependencies
# This commit ID should be updated whenever someone changes the dependency list
# in cmake/ports
RUN git clone https://github.com/highfidelity/hifi.git && \
cd ~/hifi && \
git checkout 796bfb5d6715ff14c2e60f3ee8fac1465b7578c6
# Download the repo
RUN git clone https://github.com/vircadia/vircadia.git
WORKDIR /home/jenkins/hifi
WORKDIR /home/gha/vircadia
RUN mkdir build
# Pre-cache the vcpkg managed dependencies
WORKDIR /home/jenkins/hifi/build
WORKDIR /home/gha/vircadia/build
RUN python3 ../prebuild.py --build-root `pwd` --android interface
# Pre-cache the gradle dependencies
WORKDIR /home/jenkins/hifi/android
WORKDIR /home/gha/vircadia/android
RUN ./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
#RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED

View file

@ -470,7 +470,9 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
scriptEngines->runScriptInitializers(newEngine);
newEngine->runInThread();
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngineSP);
// On the entity script server, these are the same
DependencyManager::get<EntityScriptingInterface>()->setPersistentEntitiesScriptEngine(newEngineSP);
DependencyManager::get<EntityScriptingInterface>()->setNonPersistentEntitiesScriptEngine(newEngineSP);
if (_entitiesScriptEngine) {
disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,

View file

@ -1,6 +1,6 @@
macro(target_oculus_mobile)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus_1.22/VrApi)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/ovr_sdk_mobile_1.37.0/VrApi)
# Mobile SDK
set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include)
@ -12,7 +12,7 @@ macro(target_oculus_mobile)
target_link_libraries(${TARGET_NAME} ${OVR_MOBILE_LIBRARIES})
# Platform SDK
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculusPlatform)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/ovr_platform_sdk_23.0.0)
set(OVR_PLATFORM_INCLUDE_DIRS ${INSTALL_DIR}/Include)
target_include_directories(${TARGET_NAME} PRIVATE ${OVR_PLATFORM_INCLUDE_DIRS})
set(OVR_PLATFORM_LIBRARIES ${INSTALL_DIR}/Android/libs/arm64-v8a/libovrplatformloader.so)

View file

@ -19,7 +19,7 @@ include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO kasenvr/etc2comp
REPO vircadia/etc2comp
REF 7f1843bf07825c21cab711360c1ddbad04641036
SHA512 d747076acda8537d39585858c793a35c3dcc9ef283d723619a47f8c81ec1454c95b3340ad35f0655a939eae5b8271c801c48a9a7568311a01903a344c44af25b
HEAD_REF master

View file

@ -2,13 +2,22 @@ include(vcpkg_common_functions)
vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
file(READ "${VCPKG_ROOT_DIR}/_env/USE_GLES.txt" USE_GLES)
# GitHub Actions Android builds fail with `FILENAME` set while desktop builds with GLES fail without a set `FILENAME`.
if (ANDROID)
vcpkg_download_distfile(
SOURCE_ARCHIVE
URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad32es.zip
SHA512 2e02ac633eed8f2ba2adbf96ea85d08998f48dd2e9ec9a88ec3c25f48eaf1405371d258066327c783772fcb3793bdb82bd7375fdabb2ba5e2ce0835468b17f65
)
elseif (USE_GLES)
vcpkg_download_distfile(
SOURCE_ARCHIVE
URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad32es.zip
SHA512 2e02ac633eed8f2ba2adbf96ea85d08998f48dd2e9ec9a88ec3c25f48eaf1405371d258066327c783772fcb3793bdb82bd7375fdabb2ba5e2ce0835468b17f65
FILENAME glad32es.zip
)
else()
# else Linux desktop
vcpkg_download_distfile(

View file

@ -3,7 +3,7 @@ include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO kasenvr/scribe
REPO vircadia/scribe
REF 1bd638a36ca771e5a68d01985b6389b71835cbd2
SHA512 dbe241d86df3912e544f6b9839873f9875df54efc93822b145e7b13243eaf2e3d690bc8a28b1e52d05bdcd7e68fca6b0b2f5c43ffd0f56a9b7a50d54dcf9e31e
HEAD_REF master

View file

@ -9,7 +9,7 @@ include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO kasenvr/nvidia-texture-tools
REPO vircadia/nvidia-texture-tools
REF 330c4d56274a0f602a5c70596e2eb670a4ed56c2
SHA512 4c0bc2f369120d696cc27710b6d33086b27eef55f537ec66b9a5c8b1839bc2426c0413670b0f65be52c5d353468f0126dfe024be1f0690611d4d7e33ac530127
HEAD_REF master

View file

@ -3,8 +3,8 @@ include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO oneapi-src/oneTBB
REF 4bdba61bafc6ba2d636f31564f1de5702d365cf7
SHA512 f2a8d7e0476f846039390f4a79af3fe13770e23b01bf4741e738136f7ddb401357a0e50f35212e8d0fa5fc4cf1563418337309227d7243fc3676edd406ae652d
REF eca91f16d7490a8abfdee652dadf457ec820cc37
SHA512 7144e1dc68304b5358e6ea330431b6f0c61fadb147efa353a5b242777d6fabf7b8cf99b79cffb51b49b911dd17a9f1879619d6eebdf319f23ec3235c89cffc25
HEAD_REF tbb_2019
PATCHES fix-static-build.patch
)

View file

@ -2,7 +2,7 @@
//
// index.html
//
// Created by kasenvr@gmail.com on 21 Jul 2020
// Created by somnilibertas@gmail.com on 21 Jul 2020
// Copyright 2020 Vircadia and contributors.
//
// Distributed under the Apache License, Version 2.0.

View file

@ -1,3 +1,3 @@
// Here you can put a script that will be run by an assignment-client (AC)
// For examples, please go to https://github.com/kasenvr/project-athena/tree/master/script-archive/acScripts
// For examples, please go to https://github.com/vircadia/vircadia/tree/master/script-archive/acScripts
// The directory named acScripts contains assignment-client specific scripts you can try.

View file

@ -1238,7 +1238,7 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString&
// Get data pertaining to "me", the user who generated the access token.
const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me";
const QString WORDPRESS_USER_QUERY = "_fields=username,roles";
const QString WORDPRESS_USER_QUERY = "_fields=username,email,roles";
QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE + (apiBase.contains("?") ? "&" : "?") + WORDPRESS_USER_QUERY;
QNetworkRequest request;
@ -1270,8 +1270,13 @@ void DomainGatekeeper::requestDomainUserFinished() {
if (200 <= httpStatus && httpStatus < 300) {
QString username = rootObject.value("username").toString().toLower();
if (_inFlightDomainUserIdentityRequests.contains(username)) {
QString email = rootObject.value("email").toString().toLower();
if (_inFlightDomainUserIdentityRequests.contains(username) || _inFlightDomainUserIdentityRequests.contains(email)) {
// Success! Verified user.
if (!_inFlightDomainUserIdentityRequests.contains(username)) {
username = email;
}
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));
_inFlightDomainUserIdentityRequests.remove(username);

View file

@ -39,15 +39,15 @@ ANDROID_PACKAGES = {
'sharedLibFolder': 'lib',
'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so']
},
'oculus_1.22': {
'file': 'ovr_sdk_mobile_1.22.zip',
'checksum': '1ac3c5b0521e5406f287f351015daff8',
'ovr_sdk_mobile_1.37.0': {
'file': 'ovr_sdk_mobile_1.37.0.zip',
'checksum': '6040e1966f335a3e5015295154cd7383',
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
'includeLibs': ['libvrapi.so']
},
'oculusPlatform': {
'file': 'OVRPlatformSDK_v1.34.0.zip',
'checksum': '16e4c5f39520f122bc49cb6d5bb88289',
'ovr_platform_sdk_23.0.0': {
'file': 'ovr_platform_sdk_23.0.0.zip',
'checksum': '29d02b560f60d0fa7b8a64cd965dd55b',
'sharedLibFolder': 'Android/libs/arm64-v8a',
'includeLibs': ['libovrplatformloader.so']
},

View file

@ -12,7 +12,7 @@ import functools
print = functools.partial(print, flush=True)
# Encapsulates the vcpkg system
# Encapsulates the vcpkg system
class QtDownloader:
CMAKE_TEMPLATE = """
# this file auto-generated by hifi_qt.py
@ -72,7 +72,7 @@ endif()
self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz'
elif u_major == 19 and u_minor == 10:
self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.6-ubuntu-19.10.tar.xz'
elif u_major > 18 and ( u_major != 19 and u_minor != 4):
elif u_major > 19:
print("We don't support " + distro.name(pretty=True) + " yet. Perhaps consider helping us out?")
raise Exception('LINUX DISTRO IS NOT SUPPORTED YET!!!')
else:
@ -81,7 +81,7 @@ endif()
else:
print("Sorry, " + distro.name(pretty=True) + " is not supported. Please consider helping us out.")
print("It's also possible to build Qt for your distribution, please see the documentation at:")
print("https://github.com/kasenvr/project-athena/tree/kasen/core/tools/qt-builder")
print("https://github.com/vircadia/vircadia/tree/master/tools/qt-builder")
raise Exception('UNKNOWN LINUX VERSION!!!')
else:
print("System : " + platform.system())

View file

@ -114,7 +114,7 @@ Item {
displayNameField.placeholderText = "Display Name (optional)";
var savedDisplayName = Settings.getValue("Avatar/displayName", "");
displayNameField.text = savedDisplayName;
emailField.placeholderText = (!isLoggingInToDomain) ? "Username or Email" : "Username";
emailField.placeholderText = "Username or Email";
if (!isLoggingInToDomain) {
var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";

View file

@ -309,8 +309,8 @@ FocusScope {
if (child.hasOwnProperty("modality")) {
var mappedPoint = mapToItem(child, point.x, point.y);
if (child.hasOwnProperty("frame")) {
var outLine = child.frame.children[2];
var framePoint = outLine.mapFromGlobal(point.x, point.y);
var outLine = child.frame.children[2]; // sizeOutline
var framePoint = mapToItem(outLine, point.x, point.y);
if (outLine.contains(framePoint)) {
return true;
}

View file

@ -54,10 +54,10 @@ Rectangle {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "<a href=\"https://github.com/kasenvr/project-athena\">Vircadia Github</a>."
text: "<a href=\"https://github.com/vircadia/vircadia\">Vircadia Github</a>."
size: 20
onLinkActivated: {
About.openUrl("https:/github.com/kasenvr/project-athena");
About.openUrl("https:/github.com/vircadia/vircadia");
}
}

View file

@ -2,8 +2,8 @@
// EntityScriptQMLWhitelist.qml
// interface/resources/qml/hifi/dialogs/security
//
// Created by Kasen IO on 2019.12.05 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO
// Created by Kalila L. on 2019.12.05 | realities.dev | somnilibertas@gmail.com
// Copyright 2019 Kalila L.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html

View file

@ -312,9 +312,9 @@ Rectangle {
parent.color = hifi.colors.blueHighlight;
}
onClicked: {
lightboxPopup.titleText = "Script Plugin Infrastructure by Kasen";
lightboxPopup.titleText = "Script Plugin Infrastructure";
lightboxPopup.bodyText = "Toggles the activation of scripting plugins in the 'plugins/scripting' folder. \n\n"
+ "Created by https://kasen.io/";
+ "Created by:\n humbletim@gmail.com\n somnilibertas@gmail.com";
lightboxPopup.button1text = "OK";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;

View file

@ -27,7 +27,6 @@ Item {
readonly property int frameMarginRight: frame.decoration ? frame.decoration.frameMarginRight : 0
readonly property int frameMarginTop: frame.decoration ? frame.decoration.frameMarginTop : 0
readonly property int frameMarginBottom: frame.decoration ? frame.decoration.frameMarginBottom : 0
readonly property int offsetCorrection: 20
// Frames always fill their parents, but their decorations may extend
// beyond the window via negative margin sizes
@ -76,7 +75,7 @@ Item {
Rectangle {
id: sizeOutline
x: -frameMarginLeft
y: -frameMarginTop - offsetCorrection
y: -frameMarginTop
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
color: hifi.colors.baseGrayHighlight15

View file

@ -355,6 +355,7 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
static const QString KEEP_ME_LOGGED_IN_SETTING_NAME = "keepMeLoggedIn";
static const QString CACHEBUST_SCRIPT_REQUIRE_SETTING_NAME = "cachebustScriptRequire";
static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f;
@ -1966,6 +1967,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
loadSettings();
updateVerboseLogging();
setCachebustRequire();
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -2600,6 +2603,16 @@ void Application::updateVerboseLogging() {
QLoggingCategory::setFilterRules(rules);
}
void Application::setCachebustRequire() {
auto menu = Menu::getInstance();
if (!menu) {
return;
}
bool enable = menu->isOptionChecked(MenuOption::CachebustRequire);
Setting::Handle<bool>{ CACHEBUST_SCRIPT_REQUIRE_SETTING_NAME, false }.set(enable);
}
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
DomainHandler::ConnectionRefusedReason reasonCode = static_cast<DomainHandler::ConnectionRefusedReason>(reasonCodeInt);
@ -8699,7 +8712,7 @@ void Application::notifyPacketVersionMismatch() {
}
void Application::checkSkeleton() const {
if (getMyAvatar()->getSkeletonModel()->isActive() && !getMyAvatar()->getSkeletonModel()->hasSkeleton()) {
if (getMyAvatar()->getSkeletonModel()->isLoaded() && !getMyAvatar()->getSkeletonModel()->hasSkeleton()) {
qCDebug(interfaceapp) << "MyAvatar model has no skeleton";
QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded...";

View file

@ -472,6 +472,8 @@ public slots:
void setIsInterstitialMode(bool interstitialMode);
void updateVerboseLogging();
void setCachebustRequire();
void changeViewAsNeeded(float boomLength);

View file

@ -4,14 +4,14 @@
//
// Created by Stephen Birarda on 8/12/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// For happ(ier) development of QML, use these two things:
// This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1
// Use this to live reload: DependencyManager::get<OffscreenUi>()->clearCache();
// For happ(ier) development of QML, use these two things:
// This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1
// Use this to live reload: DependencyManager::get<OffscreenUi>()->clearCache();
#include "Menu.h"
#include <QDesktopServices>
@ -365,6 +365,10 @@ Menu::Menu() {
// Developer > Scripting > Verbose Logging
addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false,
qApp, SLOT(updateVerboseLogging()));
// Developer > Scripting > Enable Cachebusting of Script.require
addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::CachebustRequire, 0, false,
qApp, SLOT(setCachebustRequire()));
// Developer > Scripting > Enable Speech Control API
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)

View file

@ -4,6 +4,7 @@
//
// Created by Stephen Birarda on 8/12/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -54,6 +55,7 @@ namespace MenuOption {
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
const QString BookmarkLocation = "Bookmark Location";
const QString CalibrateCamera = "Calibrate Camera";
const QString CachebustRequire = "Enable Cachebusting of Script.require";
const QString CenterPlayerInView = "Center Player In View";
const QString Chat = "Chat...";
const QString ClearDiskCaches = "Clear Disk Caches (requires restart)";

View file

@ -89,6 +89,7 @@ int main(int argc, const char* argv[]) {
QCommandLineOption displayNameOption("displayName", "set user display name <string>", "string");
QCommandLineOption setBookmarkOption("setBookmark", "set bookmark key=value pair", "string");
QCommandLineOption defaultScriptOverrideOption("defaultScriptsOverride", "override defaultsScripts.js", "string");
QCommandLineOption forceCrashReportingOption("forceCrashReporting", "Force crash reporting to initialize");
parser.addOption(urlOption);
parser.addOption(noLauncherOption);
@ -103,6 +104,7 @@ int main(int argc, const char* argv[]) {
parser.addOption(displayNameOption);
parser.addOption(setBookmarkOption);
parser.addOption(defaultScriptOverrideOption);
parser.addOption(forceCrashReportingOption);
if (!parser.parse(arguments)) {
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
@ -218,8 +220,9 @@ int main(int argc, const char* argv[]) {
}
qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled();
qDebug() << "Crash handler logger is enabled:" << ual.isCrashMonitorEnabled();
if (ual.isCrashMonitorEnabled()) {
bool isCrashHandlerEnabled = ual.isCrashMonitorEnabled() || parser.isSet(forceCrashReportingOption);
qDebug() << "Crash handler logger is enabled:" << isCrashHandlerEnabled;
if (isCrashHandlerEnabled) {
auto crashHandlerStarted = startCrashHandler(argv[0]);
qDebug() << "Crash handler started:" << crashHandlerStarted;
}

View file

@ -23,7 +23,7 @@ OverlayConductor::OverlayConductor() {
OverlayConductor::~OverlayConductor() {
}
bool OverlayConductor::headOutsideOverlay() const {
bool OverlayConductor::headNotCenteredInOverlay() const {
glm::mat4 hmdMat = qApp->getHMDSensorPose();
glm::vec3 hmdPos = extractTranslation(hmdMat);
glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f));
@ -32,8 +32,8 @@ bool OverlayConductor::headOutsideOverlay() const {
glm::vec3 uiPos = uiTransform.getTranslation();
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
const float MAX_COMPOSITOR_DISTANCE = 0.33f;
const float MAX_COMPOSITOR_ANGLE = 90.0f;
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
return true;
@ -70,6 +70,8 @@ bool OverlayConductor::updateAvatarIsAtRest() {
void OverlayConductor::centerUI() {
// place the overlay at the current hmd position in sensor space
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
// Set its radius.
camMat = glm::scale(camMat, glm::vec3(HUD_RADIUS));
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
}
@ -83,7 +85,6 @@ void OverlayConductor::update(float dt) {
if (!desktop) {
return;
}
bool currentVisible = !desktop->property("pinned").toBool();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// centerUI when hmd mode is first enabled and mounted
@ -96,24 +97,24 @@ void OverlayConductor::update(float dt) {
_hmdMode = false;
}
bool shouldRecenter = false;
if (_suppressedByHead) {
if (updateAvatarIsAtRest()) {
_suppressedByHead = false;
shouldRecenter = true;
}
} else {
if (_hmdMode && headOutsideOverlay()) {
_suppressedByHead = true;
}
bool initiateRecenter = false;
if (_hmdMode && headNotCenteredInOverlay()) {
initiateRecenter = true;
}
bool shouldRecenter = false;
if (initiateRecenter || _suppressedByHead) {
_suppressedByHead = !updateAvatarIsAtRest();
shouldRecenter = !_suppressedByHead;
}
bool currentVisible = !desktop->property("pinned").toBool();
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && !_suppressedByHead;
if (targetVisible != currentVisible) {
offscreenUi->setPinned(!targetVisible);
}
if (shouldRecenter && !_suppressedByHead) {
if (shouldRecenter) {
centerUI();
}
#endif

View file

@ -22,7 +22,7 @@ public:
void centerUI();
private:
bool headOutsideOverlay() const;
bool headNotCenteredInOverlay() const;
bool updateAvatarIsAtRest();
#if !defined(DISABLE_QML)

View file

@ -1246,7 +1246,7 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
}
case CAMERA_MATRIX_INDEX: {
glm::quat rotation;
if (_skeletonModel && _skeletonModel->isActive()) {
if (_skeletonModel && _skeletonModel->isLoaded()) {
int headJointIndex = getJointIndex("Head");
if (headJointIndex >= 0) {
_skeletonModel->getAbsoluteJointRotationInRigFrame(headJointIndex, rotation);
@ -1298,7 +1298,7 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
}
case CAMERA_MATRIX_INDEX: {
glm::vec3 translation;
if (_skeletonModel && _skeletonModel->isActive()) {
if (_skeletonModel && _skeletonModel->isLoaded()) {
int headJointIndex = getJointIndex("Head");
if (headJointIndex >= 0) {
_skeletonModel->getAbsoluteJointTranslationInRigFrame(headJointIndex, translation);
@ -1427,7 +1427,7 @@ void Avatar::withValidJointIndicesCache(std::function<void()> const& worker) con
QWriteLocker writeLock(&_modelJointIndicesCacheLock);
if (!_modelJointsCached) {
_modelJointIndicesCache.clear();
if (_skeletonModel && _skeletonModel->isActive()) {
if (_skeletonModel && _skeletonModel->isLoaded()) {
_modelJointIndicesCache = _skeletonModel->getHFMModel().jointIndices;
_modelJointsCached = true;
}

View file

@ -176,7 +176,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
updateRenderItems();
}
if (!isActive() || !_owningAvatar->isMyAvatar()) {
if (!isLoaded() || !_owningAvatar->isMyAvatar()) {
return; // only simulate for own avatar
}
@ -255,19 +255,19 @@ bool SkeletonModel::getRightHandPosition(glm::vec3& position) const {
}
bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const {
return isActive() && getJointPositionInWorldFrame(_rig.indexOfJoint("Head"), headPosition);
return isLoaded() && getJointPositionInWorldFrame(_rig.indexOfJoint("Head"), headPosition);
}
bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const {
return isActive() && getJointPositionInWorldFrame(_rig.indexOfJoint("Neck"), neckPosition);
return isLoaded() && getJointPositionInWorldFrame(_rig.indexOfJoint("Neck"), neckPosition);
}
bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const {
return isActive() && getJointPosition(_rig.indexOfJoint("Neck"), neckPosition);
return isLoaded() && getJointPosition(_rig.indexOfJoint("Neck"), neckPosition);
}
bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (!isActive()) {
if (!isLoaded()) {
return false;
}
@ -361,7 +361,7 @@ void SkeletonModel::renderBoundingCollisionShapes(RenderArgs* args, gpu::Batch&
}
bool SkeletonModel::hasSkeleton() {
return isActive() ? _rig.indexOfJoint("Hips") != -1 : false;
return isLoaded() ? _rig.indexOfJoint("Hips") != -1 : false;
}
void SkeletonModel::onInvalidate() {

View file

@ -44,10 +44,10 @@ public:
bool getIsJointOverridden(int jointIndex) const;
/// Returns the index of the left hand joint, or -1 if not found.
int getLeftHandJointIndex() const { return isActive() ? _rig.indexOfJoint("LeftHand") : -1; }
int getLeftHandJointIndex() const { return isLoaded() ? _rig.indexOfJoint("LeftHand") : -1; }
/// Returns the index of the right hand joint, or -1 if not found.
int getRightHandJointIndex() const { return isActive() ? _rig.indexOfJoint("RightHand") : -1; }
int getRightHandJointIndex() const { return isLoaded() ? _rig.indexOfJoint("RightHand") : -1; }
bool getLeftGrabPosition(glm::vec3& position) const;
bool getRightGrabPosition(glm::vec3& position) const;

View file

@ -41,10 +41,10 @@ static const float reticleSize = TWO_PI / 100.0f;
//EntityItemID CompositorHelper::_noItemId;
static QString _tooltipId;
const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(3960, 1188); // ~10% more pixel density than old version, 72dx240d FOV
const QRect CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT = QRect(956, 0, 2048, 1188); // don't include entire width only center 2048
const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(2640, 1188);
const QRect CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT = QRect(296, 0, 2048, 1188); // Center 2048 pixels.
const float CompositorHelper::VIRTUAL_UI_ASPECT_RATIO = (float)VIRTUAL_SCREEN_SIZE.x / (float)VIRTUAL_SCREEN_SIZE.y;
const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI * 3.0f / 2.0f, PI * 3.0f / 2.0f / VIRTUAL_UI_ASPECT_RATIO);
const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI, PI / VIRTUAL_UI_ASPECT_RATIO);
const vec2 CompositorHelper::MOUSE_EXTENTS_ANGULAR_SIZE = vec2(PI * 2.0f, PI * 0.95f); // horizontal: full sphere, vertical: ~5deg from poles
const vec2 CompositorHelper::MOUSE_EXTENTS_PIXELS = vec2(VIRTUAL_SCREEN_SIZE) * (MOUSE_EXTENTS_ANGULAR_SIZE / VIRTUAL_UI_TARGET_FOV);
@ -384,9 +384,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
glm::vec3 localPosition = transformPoint(worldToUi, position);
glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction));
const float UI_RADIUS = 1.0f;
const float UNIT_RADIUS = 1.0f;
float intersectionDistance;
if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &intersectionDistance)) {
if (raySphereIntersect(localDirection, localPosition, UNIT_RADIUS, &intersectionDistance)) {
result = transformPoint(uiToWorld, localPosition + localDirection * intersectionDistance);
#ifdef WANT_DEBUG
DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
@ -407,9 +407,8 @@ bool CompositorHelper::calculateParabolaUICollisionPoint(const glm::vec3& origin
glm::vec3 localVelocity = glm::normalize(transformVectorFast(worldToUi, velocity));
glm::vec3 localAcceleration = glm::normalize(transformVectorFast(worldToUi, acceleration));
const float UI_RADIUS = 1.0f;
float intersectionDistance;
if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), UI_RADIUS, intersectionDistance)) {
if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), HUD_RADIUS, intersectionDistance)) {
result = origin + velocity * intersectionDistance + 0.5f * acceleration * intersectionDistance * intersectionDistance;
parabolicDistance = intersectionDistance;
return true;

View file

@ -27,7 +27,8 @@
class ReticleInterface;
const float DEFAULT_RETICLE_DEPTH = 1.0f; // FIXME - probably should be based on UI radius
const float HUD_RADIUS = 1.5f;
const float DEFAULT_RETICLE_DEPTH = HUD_RADIUS;
const float MAGNIFY_WIDTH = 220.0f;
const float MAGNIFY_HEIGHT = 100.0f;
@ -154,7 +155,7 @@ private:
std::unique_ptr<QPropertyAnimation> _alphaPropertyAnimation;
std::atomic<bool> _reticleVisible { true };
std::atomic<float> _reticleDepth { 1.0f };
std::atomic<float> _reticleDepth { DEFAULT_RETICLE_DEPTH };
// NOTE: when the compositor is running in HMD mode, it will control the reticle position as a custom
// application specific position, when it's in desktop mode, the reticle position will simply move

View file

@ -158,79 +158,98 @@ render::ItemID EntityTreeRenderer::renderableIdForEntityId(const EntityItemID& i
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
void EntityTreeRenderer::resetEntitiesScriptEngine() {
_entitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_entitiesScriptEngine);
_entitiesScriptEngine->runInThread();
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_entitiesScriptEngine);
void EntityTreeRenderer::setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->setEntitiesScriptEngine(entitiesScriptEngineProvider);
// Connect mouse events to entity script callbacks
if (!_mouseAndPreloadSignalHandlersConnected) {
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mousePressOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseDoublePressOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseDoublePressOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseMoveOnEntity", event);
// FIXME: this is a duplicate of mouseMoveOnEntity, but it seems like some scripts might use this naming
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseMoveEvent", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseReleaseOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "mousePressOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseDoublePressOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "mouseDoublePressOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "mouseMoveOnEntity", event);
// FIXME: this is a duplicate of mouseMoveOnEntity, but it seems like some scripts might use this naming
scriptEngine->callEntityScriptMethod(entityID, "mouseMoveEvent", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "mouseReleaseOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "clickDownOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::holdingClickOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "holdingClickOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickReleaseOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "clickReleaseOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "clickDownOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::holdingClickOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "holdingClickOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickReleaseOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "clickReleaseOnEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverEnterEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverOverEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverLeaveEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "hoverEnterEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "hoverOverEntity", event);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
scriptEngine->callEntityScriptMethod(entityID, "hoverLeaveEntity", event);
});
connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptPreloadFinished, [&](const EntityItemID& entityID) {
EntityItemPointer entity = getTree()->findEntityByID(entityID);
if (entity) {
entity->setScriptHasFinishedPreload(true);
}
});
connect(scriptEngine.data(), &ScriptEngine::entityScriptPreloadFinished, [&](const EntityItemID& entityID) {
EntityItemPointer entity = getTree()->findEntityByID(entityID);
if (entity) {
entity->setScriptHasFinishedPreload(true);
}
});
}
_mouseAndPreloadSignalHandlersConnected = true;
void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() {
if (_persistentEntitiesScriptEngine) {
_persistentEntitiesScriptEngine->unloadAllEntityScripts(true);
_persistentEntitiesScriptEngine->stop();
_persistentEntitiesScriptEngine->waitTillDoneRunning();
_persistentEntitiesScriptEngine->disconnectNonEssentialSignals();
}
_persistentEntitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_persistentEntitiesScriptEngine);
_persistentEntitiesScriptEngine->runInThread();
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_persistentEntitiesScriptEngine);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->setPersistentEntitiesScriptEngine(entitiesScriptEngineProvider);
setupEntityScriptEngineSignals(_persistentEntitiesScriptEngine);
}
void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() {
if (_nonPersistentEntitiesScriptEngine) {
_nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(true);
_nonPersistentEntitiesScriptEngine->stop();
_nonPersistentEntitiesScriptEngine->waitTillDoneRunning();
_nonPersistentEntitiesScriptEngine->disconnectNonEssentialSignals();
}
_nonPersistentEntitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_nonPersistentEntitiesScriptEngine);
_nonPersistentEntitiesScriptEngine->runInThread();
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_nonPersistentEntitiesScriptEngine);
DependencyManager::get<EntityScriptingInterface>()->setNonPersistentEntitiesScriptEngine(entitiesScriptEngineProvider);
setupEntityScriptEngineSignals(_nonPersistentEntitiesScriptEngine);
}
void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
leaveDomainAndNonOwnedEntities();
// unload and stop the engine
if (_entitiesScriptEngine) {
QList<EntityItemID> entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs();
if (_nonPersistentEntitiesScriptEngine) {
QList<EntityItemID> entitiesWithEntityScripts = _nonPersistentEntitiesScriptEngine->getListOfEntityScriptIDs();
foreach (const EntityItemID& entityID, entitiesWithEntityScripts) {
foreach (const EntityItemID& entityID, entitiesWithEntityScripts) {
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
if (entityItem && !entityItem->getScript().isEmpty()) {
if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) {
if (_currentEntitiesInside.contains(entityID)) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
}
_entitiesScriptEngine->unloadEntityScript(entityID, true);
_nonPersistentEntitiesScriptEngine->unloadEntityScript(entityID, true);
}
}
}
@ -240,6 +259,10 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
stopDomainAndNonOwnedEntities();
if (!_shuttingDown && _wantScripts) {
resetNonPersistentEntitiesScriptEngine();
}
std::unordered_map<EntityItemID, EntityRendererPointer> savedEntities;
std::unordered_set<EntityRendererPointer> savedRenderables;
// remove all entities from the scene
@ -269,16 +292,22 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
void EntityTreeRenderer::clear() {
leaveAllEntities();
// unload and stop the engine
if (_entitiesScriptEngine) {
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
_entitiesScriptEngine->unloadAllEntityScripts(true);
_entitiesScriptEngine->stop();
}
// reset the engine
auto scene = _viewState->getMain3DScene();
if (_shuttingDown) {
// unload and stop the engines
if (_nonPersistentEntitiesScriptEngine) {
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
_nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(true);
_nonPersistentEntitiesScriptEngine->stop();
}
if (_persistentEntitiesScriptEngine) {
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
_persistentEntitiesScriptEngine->unloadAllEntityScripts(true);
_persistentEntitiesScriptEngine->stop();
}
if (scene) {
render::Transaction transaction;
for (const auto& entry : _entitiesInScene) {
@ -289,7 +318,8 @@ void EntityTreeRenderer::clear() {
}
} else {
if (_wantScripts) {
resetEntitiesScriptEngine();
resetPersistentEntitiesScriptEngine();
resetNonPersistentEntitiesScriptEngine();
}
if (scene) {
for (const auto& entry : _entitiesInScene) {
@ -313,13 +343,17 @@ void EntityTreeRenderer::clear() {
}
void EntityTreeRenderer::reloadEntityScripts() {
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->resetModuleCache();
_persistentEntitiesScriptEngine->unloadAllEntityScripts();
_persistentEntitiesScriptEngine->resetModuleCache();
_nonPersistentEntitiesScriptEngine->unloadAllEntityScripts();
_nonPersistentEntitiesScriptEngine->resetModuleCache();
for (const auto& entry : _entitiesInScene) {
const auto& renderer = entry.second;
const auto& entity = renderer->getEntity();
if (!entity->getScript().isEmpty()) {
_entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true);
if (entity && !entity->getScript().isEmpty()) {
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
scriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true);
}
}
}
@ -329,7 +363,8 @@ void EntityTreeRenderer::init() {
EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_tree);
if (_wantScripts) {
resetEntitiesScriptEngine();
resetPersistentEntitiesScriptEngine();
resetNonPersistentEntitiesScriptEngine();
}
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities
@ -341,8 +376,11 @@ void EntityTreeRenderer::init() {
}
void EntityTreeRenderer::shutdown() {
if (_entitiesScriptEngine) {
_entitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
if (_persistentEntitiesScriptEngine) {
_persistentEntitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
}
if (_nonPersistentEntitiesScriptEngine) {
_nonPersistentEntitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
}
_shuttingDown = true;
@ -658,12 +696,16 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
// for entity IDs that no longer exist.
if (_entitiesScriptEngine) {
if (_persistentEntitiesScriptEngine && _nonPersistentEntitiesScriptEngine) {
// for all of our previous containing entities, if they are no longer containing then send them a leave event
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
if (!entitiesContainingAvatar.contains(entityID)) {
emit leaveEntity(entityID);
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
auto entity = getTree()->findEntityByEntityItemID(entityID);
if (entity) {
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
}
}
}
@ -671,7 +713,11 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
if (!_currentEntitiesInside.contains(entityID)) {
emit enterEntity(entityID);
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
auto entity = getTree()->findEntityByEntityItemID(entityID);
if (entity) {
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
scriptEngine->callEntityScriptMethod(entityID, "enterEntity");
}
}
}
_currentEntitiesInside = entitiesContainingAvatar;
@ -687,8 +733,8 @@ void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() {
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
if (entityItem && !(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) {
emit leaveEntity(entityID);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
if (_nonPersistentEntitiesScriptEngine) {
_nonPersistentEntitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
}
} else {
currentEntitiesInsideToSave.insert(entityID);
@ -706,8 +752,12 @@ void EntityTreeRenderer::leaveAllEntities() {
// for all of our previous containing entities, if they are no longer containing then send them a leave event
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
emit leaveEntity(entityID);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
if (entityItem) {
auto& scriptEngine = (entityItem->isLocalEntity() || entityItem->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
if (scriptEngine) {
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
}
}
}
_currentEntitiesInside.clear();
@ -1003,11 +1053,12 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
return;
}
if (_tree && !_shuttingDown && _entitiesScriptEngine && !itr->second->getEntity()->getScript().isEmpty()) {
auto& scriptEngine = (itr->second->getEntity()->isLocalEntity() || itr->second->getEntity()->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
if (_tree && !_shuttingDown && scriptEngine && !itr->second->getEntity()->getScript().isEmpty()) {
if (_currentEntitiesInside.contains(entityID)) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
}
_entitiesScriptEngine->unloadEntityScript(entityID, true);
scriptEngine->unloadEntityScript(entityID, true);
}
auto scene = _viewState->getMain3DScene();
@ -1052,20 +1103,21 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool
if (!entity) {
return;
}
bool shouldLoad = entity->shouldPreloadScript() && _entitiesScriptEngine;
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
bool shouldLoad = entity->shouldPreloadScript() && scriptEngine;
QString scriptUrl = entity->getScript();
if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) {
if (_entitiesScriptEngine) {
if (scriptEngine) {
if (_currentEntitiesInside.contains(entityID)) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
}
_entitiesScriptEngine->unloadEntityScript(entityID);
scriptEngine->unloadEntityScript(entityID);
}
entity->scriptHasUnloaded();
}
if (shouldLoad) {
entity->setScriptHasFinishedPreload(false);
_entitiesScriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload);
scriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload);
entity->scriptHasPreloaded();
}
}
@ -1172,8 +1224,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
if ((myNodeID == entityASimulatorID && entityAIsDynamic) || (myNodeID == entityBSimulatorID && (!entityAIsDynamic || entityASimulatorID.isNull()))) {
playEntityCollisionSound(entityA, collision);
emit collisionWithEntity(idA, idB, collision);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
auto& scriptEngine = (entityA->isLocalEntity() || entityA->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
if (scriptEngine) {
scriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
}
}
@ -1183,8 +1236,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
Collision invertedCollision(collision);
invertedCollision.invert();
emit collisionWithEntity(idB, idA, invertedCollision);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision);
auto& scriptEngine = (entityB->isLocalEntity() || entityB->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
if (scriptEngine) {
scriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision);
}
}
}

View file

@ -173,7 +173,9 @@ private:
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
void resetEntitiesScriptEngine();
void resetPersistentEntitiesScriptEngine();
void resetNonPersistentEntitiesScriptEngine();
void setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine);
void findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
@ -196,7 +198,8 @@ private:
QSet<EntityItemID> _currentEntitiesInside;
bool _wantScripts;
ScriptEnginePointer _entitiesScriptEngine;
ScriptEnginePointer _nonPersistentEntitiesScriptEngine; // used for domain + non-owned avatar entities, cleared on domain switch
ScriptEnginePointer _persistentEntitiesScriptEngine; // used for local + owned avatar entities, persists on domain switch, cleared on reload content
void playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision);
@ -214,8 +217,6 @@ private:
std::function<RayToEntityIntersectionResult(unsigned int)> _getPrevRayPickResultOperator;
std::function<void(unsigned int, bool)> _setPrecisionPickingOperator;
bool _mouseAndPreloadSignalHandlersConnected { false };
class LayeredZone {
public:
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : zone(zone), id(zone->getID()), volume(zone->getVolumeEstimate()) {}

View file

@ -431,13 +431,13 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa
_visible = entity->getVisible();
setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera());
setRenderLayer(entity->getRenderLayer());
setPrimitiveMode(entity->getPrimitiveMode());
_primitiveMode = entity->getPrimitiveMode();
_canCastShadow = entity->getCanCastShadow();
setCullWithParent(entity->getCullWithParent());
_cauterized = entity->getCauterized();
if (entity->needsZoneOcclusionUpdate()) {
entity->resetNeedsZoneOcclusionUpdate();
setRenderWithZones(entity->getRenderWithZones());
_renderWithZones = entity->getRenderWithZones();
}
entity->setNeedsRenderUpdate(false);
});
@ -469,11 +469,13 @@ void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) {
void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material);
emit requestRenderUpdate();
}
void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
emit requestRenderUpdate();
}
glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start) {

View file

@ -108,18 +108,7 @@ protected:
virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; }
virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; }
virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; }
virtual void setCullWithParent(bool value) { _cullWithParent = value; }
virtual void setRenderWithZones(const QVector<QUuid>& renderWithZones) { _renderWithZones = renderWithZones; }
template <typename F, typename T>
T withReadLockResult(const std::function<T()>& f) {
T result;
withReadLock([&] {
result = f();
});
return result;
}
signals:
void requestRenderUpdate();

View file

@ -52,14 +52,11 @@ void GizmoEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
void GizmoEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
bool dirty = false;
RingGizmoPropertyGroup ringProperties = entity->getRingProperties();
withWriteLock([&] {
_gizmoType = entity->getGizmoType();
if (_ringProperties != ringProperties) {
_ringProperties = ringProperties;
dirty = true;
}
});
_gizmoType = entity->getGizmoType();
if (_ringProperties != ringProperties) {
_ringProperties = ringProperties;
dirty = true;
}
if (dirty || _prevPrimitiveMode != _primitiveMode || !_ringGeometryID || !_majorTicksGeometryID || !_minorTicksGeometryID) {
_prevPrimitiveMode = _primitiveMode;
@ -242,19 +239,20 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) {
if (_gizmoType == GizmoType::RING) {
Transform transform;
bool hasTickMarks;
glm::vec4 tickProperties;
bool hasTickMarks = _ringProperties.getHasTickMarks();
glm::vec4 tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(),
_ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength());
bool forward;
bool wireframe;
bool transparent;
withReadLock([&] {
transform = _renderTransform;
hasTickMarks = _ringProperties.getHasTickMarks();
tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(),
_ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength());
transparent = isTransparent();
wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD;
});
bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
geometryCache->bindSimpleProgram(batch, false, isTransparent(), wireframe, true, true, forward, graphics::MaterialKey::CULL_NONE);
geometryCache->bindSimpleProgram(batch, false, transparent, wireframe, true, true, forward, graphics::MaterialKey::CULL_NONE);
batch.setModelTransform(transform);

View file

@ -30,16 +30,6 @@ bool GridEntityRenderer::isTransparent() const {
}
void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
withWriteLock([&] {
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
_followCamera = entity->getFollowCamera();
_majorGridEvery = entity->getMajorGridEvery();
_minorGridEvery = entity->getMinorGridEvery();
});
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] {
withWriteLock([&] {
@ -49,6 +39,16 @@ void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
});
}
void GridEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
_followCamera = entity->getFollowCamera();
_majorGridEvery = entity->getMajorGridEvery();
_minorGridEvery = entity->getMinorGridEvery();
}
Item::Bound GridEntityRenderer::getBound() {
if (_followCamera) {
// This is a UI element that should always be in view, lie to the octree to avoid culling
@ -73,13 +73,12 @@ ShapeKey GridEntityRenderer::getShapeKey() {
}
void GridEntityRenderer::doRender(RenderArgs* args) {
glm::vec4 color;
glm::vec4 color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
glm::vec3 dimensions;
Transform renderTransform;
bool forward;
withReadLock([&] {
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
dimensions = _dimensions;
renderTransform = _renderTransform;
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD;

View file

@ -30,10 +30,11 @@ protected:
private:
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;
glm::u8vec3 _color;
float _alpha;
float _alpha { NAN };
PulsePropertyGroup _pulseProperties;
bool _followCamera;

View file

@ -29,44 +29,7 @@ bool ImageEntityRenderer::isTransparent() const {
return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
bool ImageEntityRenderer::needsRenderUpdate() const {
if (resultWithReadLock<bool>([&] {
return !_textureIsLoaded;
})) {
return true;
}
return Parent::needsRenderUpdate();
}
void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
withWriteLock([&] {
auto imageURL = entity->getImageURL();
if (_imageURL != imageURL) {
_imageURL = imageURL;
if (imageURL.isEmpty()) {
_texture.reset();
} else {
_texture = DependencyManager::get<TextureCache>()->getTexture(_imageURL);
}
_textureIsLoaded = false;
}
_emissive = entity->getEmissive();
_keepAspectRatio = entity->getKeepAspectRatio();
_subImage = entity->getSubImage();
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
_billboardMode = entity->getBillboardMode();
if (!_textureIsLoaded) {
emit requestRenderUpdate();
}
_textureIsLoaded = _texture && (_texture->isLoaded() || _texture->isFailed());
});
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] {
withWriteLock([&] {
@ -76,6 +39,33 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
});
}
void ImageEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
auto imageURL = entity->getImageURL();
if (_imageURL != imageURL) {
_imageURL = imageURL;
if (imageURL.isEmpty()) {
_texture.reset();
} else {
_texture = DependencyManager::get<TextureCache>()->getTexture(_imageURL);
}
_textureIsLoaded = false;
}
_emissive = entity->getEmissive();
_keepAspectRatio = entity->getKeepAspectRatio();
_subImage = entity->getSubImage();
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
_billboardMode = entity->getBillboardMode();
if (!_textureIsLoaded) {
emit requestRenderUpdate();
}
_textureIsLoaded = _texture && (_texture->isLoaded() || _texture->isFailed());
}
Item::Bound ImageEntityRenderer::getBound() {
auto bound = Parent::getBound();
if (_billboardMode != BillboardMode::NONE) {
@ -93,33 +83,26 @@ ShapeKey ImageEntityRenderer::getShapeKey() {
builder.withTranslucent();
}
withReadLock([&] {
if (_emissive) {
builder.withUnlit();
}
if (_emissive) {
builder.withUnlit();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
});
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}
void ImageEntityRenderer::doRender(RenderArgs* args) {
NetworkTexturePointer texture;
QRect subImage;
glm::vec4 color;
glm::vec4 color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
Transform transform;
withReadLock([&] {
texture = _texture;
subImage = _subImage;
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
transform = _renderTransform;
});
if (!_visible || !texture || !texture->isLoaded() || color.a == 0.0f) {
if (!_visible || !_texture || !_texture->isLoaded() || color.a == 0.0f) {
return;
}
@ -129,28 +112,28 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->getViewFrustum().getPosition()));
batch->setModelTransform(transform);
batch->setResourceTexture(0, texture->getGPUTexture());
batch->setResourceTexture(0, _texture->getGPUTexture());
float imageWidth = texture->getWidth();
float imageHeight = texture->getHeight();
float imageWidth = _texture->getWidth();
float imageHeight = _texture->getHeight();
QRect fromImage;
if (subImage.width() <= 0) {
if (_subImage.width() <= 0) {
fromImage.setX(0);
fromImage.setWidth(imageWidth);
} else {
float scaleX = imageWidth / texture->getOriginalWidth();
fromImage.setX(scaleX * subImage.x());
fromImage.setWidth(scaleX * subImage.width());
float scaleX = imageWidth / _texture->getOriginalWidth();
fromImage.setX(scaleX * _subImage.x());
fromImage.setWidth(scaleX * _subImage.width());
}
if (subImage.height() <= 0) {
if (_subImage.height() <= 0) {
fromImage.setY(0);
fromImage.setHeight(imageHeight);
} else {
float scaleY = imageHeight / texture->getOriginalHeight();
fromImage.setY(scaleY * subImage.y());
fromImage.setHeight(scaleY * subImage.height());
float scaleY = imageHeight / _texture->getOriginalHeight();
fromImage.setY(scaleY * _subImage.y());
fromImage.setHeight(scaleY * _subImage.height());
}
float maxSize = glm::max(fromImage.width(), fromImage.height());

View file

@ -29,8 +29,8 @@ protected:
bool isTransparent() const override;
private:
virtual bool needsRenderUpdate() const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;
QString _imageURL;

View file

@ -16,197 +16,177 @@
using namespace render;
using namespace render::entities;
bool MaterialEntityRenderer::needsRenderUpdate() const {
if (_retryApply) {
return true;
}
if (!_texturesLoaded) {
return true;
}
return Parent::needsRenderUpdate();
}
bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (resultWithReadLock<bool>([&] {
if (entity->getTransform() != _transform) {
return true;
}
if (entity->getUnscaledDimensions() != _dimensions) {
return true;
}
if (entity->getParentID() != _parentID) {
return true;
}
return false;
})) {
return true;
}
return false;
void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] {
withWriteLock([&] {
_renderTransform = getModelTransform();
// Material entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
const float MATERIAL_ENTITY_SCALE = 0.5f;
_renderTransform.postScale(MATERIAL_ENTITY_SCALE);
_renderTransform.postScale(ENTITY_ITEM_DEFAULT_DIMENSIONS);
});
});
}
void MaterialEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
withWriteLock([&] {
bool deleteNeeded = false;
bool addNeeded = _retryApply;
bool transformChanged = false;
{
MaterialMappingMode mode = entity->getMaterialMappingMode();
if (mode != _materialMappingMode) {
_materialMappingMode = mode;
transformChanged = true;
}
bool deleteNeeded = false;
bool addNeeded = _retryApply;
bool transformChanged = false;
{
MaterialMappingMode mode = entity->getMaterialMappingMode();
if (mode != _materialMappingMode) {
_materialMappingMode = mode;
transformChanged = true;
}
{
bool repeat = entity->getMaterialRepeat();
if (repeat != _materialRepeat) {
_materialRepeat = repeat;
transformChanged = true;
}
}
{
bool repeat = entity->getMaterialRepeat();
if (repeat != _materialRepeat) {
_materialRepeat = repeat;
transformChanged = true;
}
{
glm::vec2 mappingPos = entity->getMaterialMappingPos();
glm::vec2 mappingScale = entity->getMaterialMappingScale();
float mappingRot = entity->getMaterialMappingRot();
if (mappingPos != _materialMappingPos || mappingScale != _materialMappingScale || mappingRot != _materialMappingRot) {
_materialMappingPos = mappingPos;
_materialMappingScale = mappingScale;
_materialMappingRot = mappingRot;
transformChanged |= _materialMappingMode == MaterialMappingMode::UV;
}
}
{
glm::vec2 mappingPos = entity->getMaterialMappingPos();
glm::vec2 mappingScale = entity->getMaterialMappingScale();
float mappingRot = entity->getMaterialMappingRot();
if (mappingPos != _materialMappingPos || mappingScale != _materialMappingScale || mappingRot != _materialMappingRot) {
_materialMappingPos = mappingPos;
_materialMappingScale = mappingScale;
_materialMappingRot = mappingRot;
transformChanged |= _materialMappingMode == MaterialMappingMode::UV;
}
{
Transform transform = entity->getTransform();
glm::vec3 dimensions = entity->getUnscaledDimensions();
if (transform != _transform || dimensions != _dimensions) {
_transform = transform;
_dimensions = dimensions;
transformChanged |= _materialMappingMode == MaterialMappingMode::PROJECTED;
}
}
{
Transform transform = entity->getTransform();
glm::vec3 dimensions = entity->getUnscaledDimensions();
if (transform != _transform || dimensions != _dimensions) {
_transform = transform;
_dimensions = dimensions;
transformChanged |= _materialMappingMode == MaterialMappingMode::PROJECTED;
}
}
{
auto material = getMaterial();
// Update the old material regardless of if it's going to change
if (transformChanged && material && !_parentID.isNull()) {
deleteNeeded = true;
addNeeded = true;
applyTextureTransform(material);
}
{
auto material = getMaterial();
// Update the old material regardless of if it's going to change
if (transformChanged && material && !_parentID.isNull()) {
deleteNeeded = true;
addNeeded = true;
applyTextureTransform(material);
}
}
bool urlChanged = false;
std::string newCurrentMaterialName = _currentMaterialName;
{
QString materialURL = entity->getMaterialURL();
if (materialURL != _materialURL) {
_materialURL = materialURL;
if (_materialURL.contains("#")) {
auto split = _materialURL.split("#");
newCurrentMaterialName = split.last().toStdString();
} else if (_materialURL.contains("?")) {
qDebug() << "DEPRECATED: Use # instead of ? for material URLS:" << _materialURL;
auto split = _materialURL.split("?");
newCurrentMaterialName = split.last().toStdString();
}
urlChanged = true;
bool urlChanged = false;
std::string newCurrentMaterialName = _currentMaterialName;
{
QString materialURL = entity->getMaterialURL();
if (materialURL != _materialURL) {
_materialURL = materialURL;
if (_materialURL.contains("#")) {
auto split = _materialURL.split("#");
newCurrentMaterialName = split.last().toStdString();
} else if (_materialURL.contains("?")) {
qDebug() << "DEPRECATED: Use # instead of ? for material URLS:" << _materialURL;
auto split = _materialURL.split("?");
newCurrentMaterialName = split.last().toStdString();
}
urlChanged = true;
}
}
bool usingMaterialData = _materialURL.startsWith("materialData");
bool materialDataChanged = false;
QUuid oldParentID = _parentID;
QString oldParentMaterialName = _parentMaterialName;
{
QString materialData = entity->getMaterialData();
if (materialData != _materialData) {
_materialData = materialData;
if (usingMaterialData) {
materialDataChanged = true;
}
bool usingMaterialData = _materialURL.startsWith("materialData");
bool materialDataChanged = false;
QUuid oldParentID = _parentID;
QString oldParentMaterialName = _parentMaterialName;
{
QString materialData = entity->getMaterialData();
if (materialData != _materialData) {
_materialData = materialData;
if (usingMaterialData) {
materialDataChanged = true;
}
}
{
QString parentMaterialName = entity->getParentMaterialName();
if (parentMaterialName != _parentMaterialName) {
_parentMaterialName = parentMaterialName;
deleteNeeded = true;
addNeeded = true;
}
}
{
QString parentMaterialName = entity->getParentMaterialName();
if (parentMaterialName != _parentMaterialName) {
_parentMaterialName = parentMaterialName;
deleteNeeded = true;
addNeeded = true;
}
{
QUuid parentID = entity->getParentID();
if (parentID != _parentID) {
_parentID = parentID;
deleteNeeded = true;
addNeeded = true;
}
}
{
QUuid parentID = entity->getParentID();
if (parentID != _parentID) {
_parentID = parentID;
deleteNeeded = true;
addNeeded = true;
}
{
quint16 priority = entity->getPriority();
if (priority != _priority) {
_priority = priority;
deleteNeeded = true;
addNeeded = true;
}
}
{
quint16 priority = entity->getPriority();
if (priority != _priority) {
_priority = priority;
deleteNeeded = true;
addNeeded = true;
}
}
if (urlChanged && !usingMaterialData) {
_networkMaterial = DependencyManager::get<MaterialCache>()->getMaterial(_materialURL);
auto onMaterialRequestFinished = [this, entity, oldParentID, oldParentMaterialName, newCurrentMaterialName](bool success) {
if (success) {
deleteMaterial(oldParentID, oldParentMaterialName);
_texturesLoaded = false;
_parsedMaterials = _networkMaterial->parsedMaterials;
setCurrentMaterialName(newCurrentMaterialName);
applyMaterial(entity);
} else {
deleteMaterial(oldParentID, oldParentMaterialName);
_retryApply = false;
_texturesLoaded = true;
}
};
if (_networkMaterial) {
if (_networkMaterial->isLoaded()) {
onMaterialRequestFinished(!_networkMaterial->isFailed());
} else {
connect(_networkMaterial.data(), &Resource::finished, this, [this, onMaterialRequestFinished](bool success) {
withWriteLock([&] {
onMaterialRequestFinished(success);
});
});
}
}
} else if (materialDataChanged && usingMaterialData) {
if (urlChanged && !usingMaterialData) {
_networkMaterial = DependencyManager::get<MaterialCache>()->getMaterial(_materialURL);
auto onMaterialRequestFinished = [this, entity, oldParentID, oldParentMaterialName, newCurrentMaterialName](bool success) {
deleteMaterial(oldParentID, oldParentMaterialName);
_texturesLoaded = false;
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(_materialData.toUtf8()), _materialURL);
// Since our material changed, the current name might not be valid anymore, so we need to update
setCurrentMaterialName(newCurrentMaterialName);
applyMaterial(entity);
} else {
if (deleteNeeded) {
deleteMaterial(oldParentID, oldParentMaterialName);
}
if (addNeeded) {
if (success) {
_texturesLoaded = false;
_parsedMaterials = _networkMaterial->parsedMaterials;
setCurrentMaterialName(newCurrentMaterialName);
applyMaterial(entity);
emit requestRenderUpdate();
} else {
_retryApply = false;
_texturesLoaded = true;
}
};
if (_networkMaterial) {
if (_networkMaterial->isLoaded()) {
onMaterialRequestFinished(!_networkMaterial->isFailed());
} else {
connect(_networkMaterial.data(), &Resource::finished, this, [this, onMaterialRequestFinished](bool success) {
onMaterialRequestFinished(success);
});
}
}
{
auto material = getMaterial();
bool newTexturesLoaded = material ? !material->isMissingTexture() : false;
if (!_texturesLoaded && newTexturesLoaded) {
material->checkResetOpacityMap();
}
_texturesLoaded = newTexturesLoaded;
} else if (materialDataChanged && usingMaterialData) {
deleteMaterial(oldParentID, oldParentMaterialName);
_texturesLoaded = false;
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(_materialData.toUtf8()), _materialURL);
// Since our material changed, the current name might not be valid anymore, so we need to update
setCurrentMaterialName(newCurrentMaterialName);
applyMaterial(entity);
} else {
if (deleteNeeded) {
deleteMaterial(oldParentID, oldParentMaterialName);
}
if (addNeeded) {
applyMaterial(entity);
}
}
_renderTransform = getModelTransform();
const float MATERIAL_ENTITY_SCALE = 0.5f;
_renderTransform.postScale(MATERIAL_ENTITY_SCALE);
_renderTransform.postScale(ENTITY_ITEM_DEFAULT_DIMENSIONS);
});
{
auto material = getMaterial();
bool newTexturesLoaded = material ? !material->isMissingTexture() : false;
if (!_texturesLoaded && newTexturesLoaded) {
material->checkResetOpacityMap();
}
_texturesLoaded = newTexturesLoaded;
}
if (!_texturesLoaded || _retryApply) {
emit requestRenderUpdate();
}
}
ItemKey MaterialEntityRenderer::getKey() {
@ -268,33 +248,30 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
gpu::Batch& batch = *args->_batch;
// Don't render if our parent is set or our material is null
QUuid parentID;
withReadLock([&] {
parentID = _parentID;
});
if (!parentID.isNull()) {
if (!_parentID.isNull()) {
return;
}
Transform renderTransform;
graphics::MaterialPointer drawMaterial;
graphics::MaterialPointer drawMaterial = getMaterial();
bool proceduralRender = false;
Transform textureTransform;
textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0));
textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot)));
textureTransform.setScale(glm::vec3(_materialMappingScale, 1));
Transform renderTransform;
withReadLock([&] {
renderTransform = _renderTransform;
drawMaterial = getMaterial();
textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0));
textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot)));
textureTransform.setScale(glm::vec3(_materialMappingScale, 1));
if (drawMaterial && drawMaterial->isProcedural() && drawMaterial->isReady()) {
proceduralRender = true;
}
});
if (!drawMaterial) {
return;
}
if (drawMaterial->isProcedural() && drawMaterial->isReady()) {
proceduralRender = true;
}
batch.setModelTransform(renderTransform);
if (!proceduralRender) {

View file

@ -27,8 +27,7 @@ public:
~MaterialEntityRenderer() { deleteMaterial(_parentID, _parentMaterialName); }
private:
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;

View file

@ -51,12 +51,6 @@ ModelPointer ModelEntityWrapper::getModel() const {
});
}
bool ModelEntityWrapper::isModelLoaded() const {
return resultWithReadLock<bool>([&] {
return _model.operator bool() && _model->isLoaded();
});
}
EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity(new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()),
[](EntityItem* ptr) { ptr->deleteLater(); });
@ -69,8 +63,7 @@ EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityI
RenderableModelEntityItem::RenderableModelEntityItem(const EntityItemID& entityItemID, bool dimensionsInitialized) :
ModelEntityWrapper(entityItemID),
_dimensionsInitialized(dimensionsInitialized) {
}
RenderableModelEntityItem::~RenderableModelEntityItem() { }
@ -83,34 +76,6 @@ void RenderableModelEntityItem::setUnscaledDimensions(const glm::vec3& value) {
}
}
void RenderableModelEntityItem::doInitialModelSimulation() {
DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__);
ModelPointer model = getModel();
if (!model) {
return;
}
// The machinery for updateModelBounds will give existing models the opportunity to fix their
// translation/rotation/scale/registration. The first two are straightforward, but the latter two have guards to
// make sure they don't happen after they've already been set. Here we reset those guards. This doesn't cause the
// entity values to change -- it just allows the model to match once it comes in.
model->setScaleToFit(false, getScaledDimensions());
model->setSnapModelToRegistrationPoint(false, getRegistrationPoint());
// now recalculate the bounds and registration
model->setScaleToFit(true, getScaledDimensions());
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
model->setRotation(getWorldOrientation());
model->setTranslation(getWorldPosition());
glm::vec3 scale = model->getScale();
model->setUseDualQuaternionSkinning(!isNonUniformScale(scale));
if (_needsInitialSimulation) {
model->simulate(0.0f);
_needsInitialSimulation = false;
}
}
void RenderableModelEntityItem::autoResizeJointArrays() {
ModelPointer model = getModel();
if (model && model->isLoaded() && !_needsInitialSimulation) {
@ -125,7 +90,7 @@ bool RenderableModelEntityItem::needsUpdateModelBounds() const {
return false;
}
if (!_dimensionsInitialized || !model->isActive()) {
if (!_dimensionsInitialized || !model->isLoaded()) {
return false;
}
@ -182,22 +147,25 @@ void RenderableModelEntityItem::updateModelBounds() {
}
bool overridingModelTransform = model->isOverridingModelTransformAndOffset();
glm::vec3 scaledDimensions = getScaledDimensions();
glm::vec3 registrationPoint = getRegistrationPoint();
if (!overridingModelTransform &&
(model->getScaleToFitDimensions() != getScaledDimensions() ||
model->getRegistrationPoint() != getRegistrationPoint() ||
!model->getIsScaledToFit())) {
(model->getScaleToFitDimensions() != scaledDimensions ||
model->getRegistrationPoint() != registrationPoint ||
!model->getIsScaledToFit() || _needsToRescaleModel)) {
// The machinery for updateModelBounds will give existing models the opportunity to fix their
// translation/rotation/scale/registration. The first two are straightforward, but the latter two
// have guards to make sure they don't happen after they've already been set. Here we reset those guards.
// This doesn't cause the entity values to change -- it just allows the model to match once it comes in.
model->setScaleToFit(false, getScaledDimensions());
model->setSnapModelToRegistrationPoint(false, getRegistrationPoint());
model->setScaleToFit(false, scaledDimensions);
model->setSnapModelToRegistrationPoint(false, registrationPoint);
// now recalculate the bounds and registration
model->setScaleToFit(true, getScaledDimensions());
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
model->setScaleToFit(true, scaledDimensions);
model->setSnapModelToRegistrationPoint(true, registrationPoint);
updateRenderItems = true;
model->scaleToFit();
_needsToRescaleModel = false;
}
bool success;
@ -221,6 +189,9 @@ void RenderableModelEntityItem::updateModelBounds() {
glm::vec3 scale = model->getScale();
model->setUseDualQuaternionSkinning(!isNonUniformScale(scale));
model->updateRenderItems();
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
locationChanged();
}
}
@ -248,8 +219,6 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper
}
}
return properties;
}
@ -261,7 +230,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
OctreeElementPointer& element, float& distance, BoxFace& face,
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
auto model = getModel();
if (!model || !isModelLoaded()) {
if (!model || !model->isLoaded()) {
return false;
}
@ -273,7 +242,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face,
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
auto model = getModel();
if (!model || !isModelLoaded()) {
if (!model || !model->isLoaded()) {
return false;
}
@ -281,8 +250,23 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3
face, surfaceNormal, extraInfo, precisionPicking, false);
}
QString RenderableModelEntityItem::getCollisionShapeURL() const {
return getShapeType() == SHAPE_TYPE_COMPOUND ? getCompoundShapeURL() : getModelURL();
}
void RenderableModelEntityItem::fetchCollisionGeometryResource() {
_collisionGeometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(getCollisionShapeURL());
if (_collisionGeometryResource) {
if (_collisionGeometryResource->isLoaded()) {
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
locationChanged();
} else {
connect(_collisionGeometryResource.get(), &GeometryResource::finished, this, [&] {
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
locationChanged();
});
}
}
}
bool RenderableModelEntityItem::unableToLoadCollisionShape() {
@ -299,7 +283,7 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
if (!_collisionGeometryResource && !getCollisionShapeURL().isEmpty()) {
fetchCollisionGeometryResource();
}
} else if (_collisionGeometryResource && !getCompoundShapeURL().isEmpty()) {
} else if (_collisionGeometryResource) {
// the compoundURL has been set but the shapeType does not agree
_collisionGeometryResource.reset();
}
@ -308,49 +292,43 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
auto currentCompoundShapeURL = getCompoundShapeURL();
ModelEntityItem::setCompoundShapeURL(url);
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
if (url != currentCompoundShapeURL && !url.isEmpty()) {
auto shapeType = getShapeType();
if (shapeType == SHAPE_TYPE_COMPOUND) {
fetchCollisionGeometryResource();
}
}
}
void RenderableModelEntityItem::setModelURL(const QString& url) {
auto currentModelURL = getModelURL();
ModelEntityItem::setModelURL(url);
if (url != currentModelURL && !url.isEmpty()) {
auto shapeType = getShapeType();
if (shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
fetchCollisionGeometryResource();
}
}
}
bool RenderableModelEntityItem::isReadyToComputeShape() const {
ShapeType type = getShapeType();
auto model = getModel();
auto shapeType = getShapeType();
if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
auto shapeURL = getCollisionShapeURL();
if (!model || shapeURL.isEmpty()) {
return false;
}
if (model->getURL().isEmpty() || !_dimensionsInitialized) {
// we need a render geometry with a scale to proceed, so give up.
return false;
}
if (model->isLoaded()) {
if (!shapeURL.isEmpty() && !_collisionGeometryResource) {
// we need a render geometry with a scale to proceed
if (model && !model->getURL().isEmpty() && !shapeURL.isEmpty() && _dimensionsInitialized && model->isLoaded()) {
if (!_collisionGeometryResource) {
const_cast<RenderableModelEntityItem*>(this)->fetchCollisionGeometryResource();
}
if (_collisionGeometryResource && _collisionGeometryResource->isLoaded()) {
// we have both URLs AND both geometries AND they are both fully loaded.
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
DETAILED_PERFORMANCE_TIMER("_model->simulate");
const_cast<RenderableModelEntityItem*>(this)->doInitialModelSimulation();
}
return true;
}
// do we have both URLs AND both geometries AND they are both fully loaded?
return _collisionGeometryResource && _collisionGeometryResource->isLoaded() && _collisionGeometryResource->isHFMModelLoaded();
}
// the model is still being downloaded.
return false;
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
return isModelLoaded();
} else if (shapeType >= SHAPE_TYPE_SIMPLE_HULL && shapeType <= SHAPE_TYPE_STATIC_MESH) {
return model && model->isLoaded() && _dimensionsInitialized;
}
return true;
}
@ -362,20 +340,31 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
ShapeType type = getShapeType();
auto model = getModel();
if (!model || !model->isLoaded()) {
type = SHAPE_TYPE_NONE;
if (type >= SHAPE_TYPE_COMPOUND && type <= SHAPE_TYPE_STATIC_MESH) {
if (!model) {
type = SHAPE_TYPE_NONE;
} else if (!model->isLoaded()) {
type = SHAPE_TYPE_NONE;
if (!model->didVisualGeometryRequestFail()) {
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
locationChanged();
}
}
}
if (type == SHAPE_TYPE_COMPOUND || type == SHAPE_TYPE_SIMPLE_COMPOUND) {
if (!_collisionGeometryResource || !_collisionGeometryResource->isLoaded() || !_collisionGeometryResource->isHFMModelLoaded()) {
type = SHAPE_TYPE_NONE;
if (!_collisionGeometryResource->isFailed()) {
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
locationChanged();
}
}
}
if (type == SHAPE_TYPE_COMPOUND) {
if (!_collisionGeometryResource || !_collisionGeometryResource->isLoaded()) {
return;
}
updateModelBounds();
// should never fall in here when collision model not fully loaded
// TODO: assert that all geometries exist and are loaded
//assert(_model && _model->isLoaded() && _collisionGeometryResource && _collisionGeometryResource->isLoaded());
const HFMModel& collisionGeometry = _collisionGeometryResource->getHFMModel();
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
@ -452,7 +441,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
// collision model's extents).
glm::vec3 dimensions = getScaledDimensions();
glm::vec3 scaleToFit = dimensions / model->getHFMModel().getUnscaledMeshExtents().size();
glm::vec3 extents = model->getHFMModel().getUnscaledMeshExtents().size();
glm::vec3 scaleToFit = dimensions / extents;
// multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model.
glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
@ -462,12 +452,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset;
}
}
shapeInfo.setParams(type, dimensions, getCompoundShapeURL());
shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL());
adjustShapeInfoByRegistration(shapeInfo);
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
updateModelBounds();
// assert we never fall in here when model not fully loaded
assert(model && model->isLoaded());
model->updateGeometry();
// compute meshPart local transforms
@ -476,16 +464,16 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
int numHFMMeshes = hfmModel.meshes.size();
int totalNumVertices = 0;
glm::vec3 dimensions = getScaledDimensions();
glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
glm::mat4 invRegistrationOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
for (int i = 0; i < numHFMMeshes; i++) {
const HFMMesh& mesh = hfmModel.meshes.at(i);
if (mesh.clusters.size() > 0) {
const HFMCluster& cluster = mesh.clusters.at(0);
auto jointMatrix = model->getRig().getJointTransform(cluster.jointIndex);
// we backtranslate by the registration offset so we can apply that offset to the shapeInfo later
localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix);
localTransforms.push_back(invRegistrationOffset * jointMatrix * cluster.inverseBindMatrix);
} else {
localTransforms.push_back(invRegistraionOffset);
localTransforms.push_back(invRegistrationOffset);
}
totalNumVertices += mesh.vertices.size();
}
@ -697,7 +685,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
}
}
shapeInfo.setParams(type, 0.5f * dimensions, getModelURL());
shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL());
adjustShapeInfoByRegistration(shapeInfo);
} else {
EntityItem::computeShapeInfo(shapeInfo);
@ -716,8 +704,8 @@ void RenderableModelEntityItem::setJointMap(std::vector<int> jointMap) {
int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) {
int result = -1;
int mapSize = (int) _jointMap.size();
if (modelJointIndex >=0 && modelJointIndex < mapSize) {
int mapSize = (int)_jointMap.size();
if (modelJointIndex >= 0 && modelJointIndex < mapSize) {
result = _jointMap[modelJointIndex];
}
@ -726,28 +714,44 @@ int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) {
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
auto model = getModel();
if (EntityItem::contains(point) && model && _collisionGeometryResource && _collisionGeometryResource->isLoaded()) {
glm::mat4 worldToHFMMatrix = model->getWorldToHFMMatrix();
glm::vec3 hfmPoint = worldToHFMMatrix * glm::vec4(point, 1.0f);
return _collisionGeometryResource->getHFMModel().convexHullContains(hfmPoint);
if (model && model->isLoaded()) {
auto shapeType = getShapeType();
if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
if (_collisionGeometryResource && _collisionGeometryResource->isLoaded() && _collisionGeometryResource->isHFMModelLoaded() && EntityItem::contains(point)) {
glm::mat4 worldToHFMMatrix = model->getWorldToHFMMatrix();
glm::vec3 hfmPoint = worldToHFMMatrix * glm::vec4(point, 1.0f);
return _collisionGeometryResource->getHFMModel().convexHullContains(hfmPoint);
}
} else if (shapeType >= SHAPE_TYPE_SIMPLE_HULL && shapeType <= SHAPE_TYPE_STATIC_MESH) {
if (EntityItem::contains(point)) {
glm::mat4 worldToHFMMatrix = model->getWorldToHFMMatrix();
glm::vec3 hfmPoint = worldToHFMMatrix * glm::vec4(point, 1.0f);
return model->getHFMModel().convexHullContains(hfmPoint);
}
} else {
return EntityItem::contains(point);
}
}
return false;
}
bool RenderableModelEntityItem::shouldBePhysical() const {
auto model = getModel();
// If we have a model, make sure it hasn't failed to download.
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
bool physicalModelLoaded = false;
ShapeType shapeType = getShapeType();
if (model) {
if ((shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) && model->didCollisionGeometryRequestFail()) {
return false;
} else if (shapeType != SHAPE_TYPE_NONE && model->didVisualGeometryRequestFail()) {
return false;
if (shapeType >= SHAPE_TYPE_COMPOUND && shapeType <= SHAPE_TYPE_STATIC_MESH) {
auto model = getModel();
// If we have a model, make sure it hasn't failed to download.
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
physicalModelLoaded = model && !model->didVisualGeometryRequestFail();
if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
physicalModelLoaded &= _collisionGeometryResource && !_collisionGeometryResource->isFailed();
}
} else if (shapeType != SHAPE_TYPE_NONE) {
physicalModelLoaded = true;
}
return !isDead() && shapeType != SHAPE_TYPE_NONE && !isLocalEntity() && QUrl(_modelURL).isValid();
return physicalModelLoaded && !isDead() && !isLocalEntity() && QUrl(getModelURL()).isValid();
}
int RenderableModelEntityItem::getJointParent(int index) const {
@ -943,13 +947,13 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics, bool tellChild
int RenderableModelEntityItem::getJointIndex(const QString& name) const {
auto model = getModel();
return (model && model->isActive()) ? model->getRig().indexOfJoint(name) : -1;
return (model && model->isLoaded()) ? model->getRig().indexOfJoint(name) : -1;
}
QStringList RenderableModelEntityItem::getJointNames() const {
QStringList result;
auto model = getModel();
if (model && model->isActive()) {
if (model && model->isLoaded()) {
const Rig& rig = model->getRig();
int jointCount = rig.getJointStateCount();
for (int jointIndex = 0; jointIndex < jointCount; jointIndex++) {
@ -960,13 +964,13 @@ QStringList RenderableModelEntityItem::getJointNames() const {
}
scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() {
auto model = resultWithReadLock<ModelPointer>([this]{ return _model; });
auto model = resultWithReadLock<ModelPointer>([&] { return _model; });
if (!model || !model->isLoaded()) {
return scriptable::ScriptableModelBase();
}
auto result = _model->getScriptableModel();
auto result = model->getScriptableModel();
result.objectID = getEntity()->getID();
{
std::lock_guard<std::mutex> lock(_materialsLock);
@ -1036,7 +1040,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() {
});
if (changed) {
locationChanged(true, true);
locationChanged();
}
}
@ -1054,10 +1058,14 @@ ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Pare
}
void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) {
void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed, const ModelPointer& model) {
auto builder = ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
if (!_cullWithParent && _model && _model->isGroupCulled()) {
if (!_visible) {
builder.withInvisible();
}
if (!_cullWithParent && model && model->isGroupCulled()) {
builder.withMetaCullGroup();
} else if (_cullWithParent) {
builder.withSubMetaCulled();
@ -1075,8 +1083,9 @@ ItemKey ModelEntityRenderer::getKey() {
}
uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) const {
if (_model) {
auto metaSubItems = _model->fetchRenderItemIDs();
auto model = resultWithReadLock<ModelPointer>([&] { return _model; });
if (model) {
auto metaSubItems = model->fetchRenderItemIDs();
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
return (uint32_t)metaSubItems.size();
}
@ -1089,8 +1098,9 @@ void ModelEntityRenderer::handleBlendedVertices(int blendshapeNumber, const QVec
}
void ModelEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& transaction) {
if (_model) {
_model->removeFromScene(scene, transaction);
auto model = resultWithReadLock<ModelPointer>([&] { return _model; });
if (model) {
model->removeFromScene(scene, transaction);
}
Parent::removeFromScene(scene, transaction);
}
@ -1099,14 +1109,14 @@ void ModelEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entit
entity->setModel({});
}
void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
void ModelEntityRenderer::animate(const TypedEntityPointer& entity, const ModelPointer& model) {
if (!_animation || !_animation->isLoaded()) {
return;
}
QVector<EntityJointData> jointsData;
const QVector<HFMAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
const QVector<HFMAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
int frameCount = frames.size();
if (frameCount <= 0) {
return;
@ -1124,17 +1134,17 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
_lastKnownCurrentFrame = currentIntegerFrame;
}
if (_jointMapping.size() != _model->getJointStateCount()) {
if (_jointMapping.size() != model->getJointStateCount()) {
qCWarning(entitiesrenderer) << "RenderableModelEntityItem::getAnimationFrame -- joint count mismatch"
<< _jointMapping.size() << _model->getJointStateCount();
<< _jointMapping.size() << model->getJointStateCount();
return;
}
QStringList animationJointNames = _animation->getHFMModel().getJointNames();
auto& hfmJoints = _animation->getHFMModel().joints;
auto& originalHFMJoints = _model->getHFMModel().joints;
auto& originalHFMIndices = _model->getHFMModel().jointIndices;
auto& originalHFMJoints = model->getHFMModel().joints;
auto& originalHFMIndices = model->getHFMModel().jointIndices;
bool allowTranslation = entity->getAnimationAllowTranslation();
@ -1182,146 +1192,49 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
entity->copyAnimationJointDataToModel();
}
bool ModelEntityRenderer::needsRenderUpdate() const {
if (resultWithReadLock<bool>([&] {
if (_moving || _animating) {
return true;
}
if (!_texturesLoaded) {
return true;
}
if (!_prevModelLoaded) {
return true;
}
return false;
})) {
bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (entity->blendshapesChanged()) {
return true;
}
ModelPointer model;
QUrl parsedModelURL;
withReadLock([&] {
model = _model;
parsedModelURL = _parsedModelURL;
});
if (model) {
// When the individual mesh parts of a model finish fading, they will mark their Model as needing updating
// we will watch for that and ask the model to update it's render items
if (parsedModelURL != model->getURL()) {
return true;
}
if (model->needsReload()) {
return true;
}
if (model->needsFixupInScene()) {
return true;
}
if (model->getRenderItemsNeedUpdate()) {
return true;
}
}
return Parent::needsRenderUpdate();
}
bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (resultWithReadLock<bool>([&] {
if (entity->hasModel() != _hasModel) {
return true;
}
// No model to render, early exit
if (!_hasModel) {
return false;
}
if (_animating != entity->isAnimatingSomething()) {
return true;
}
return false;
})) { return true; }
ModelPointer model;
withReadLock([&] {
model = _model;
});
if (model && model->isLoaded()) {
if (!entity->_dimensionsInitialized || entity->_needsInitialSimulation || !entity->_originalTexturesRead) {
return true;
}
if (entity->blendshapesChanged()) {
return true;
}
// Check to see if we need to update the model bounds
if (entity->needsUpdateModelBounds()) {
return true;
}
// Check to see if we need to update the model bounds
auto transform = entity->getTransform();
if (model->getTranslation() != transform.getTranslation() ||
model->getRotation() != transform.getRotation()) {
return true;
}
if (model->getScaleToFitDimensions() != entity->getScaledDimensions() ||
model->getRegistrationPoint() != entity->getRegistrationPoint()) {
return true;
}
// Check to see if we need to update the model bounds
if (entity->needsUpdateModelBounds()) {
return true;
}
return false;
return Parent::needsRenderUpdateFromTypedEntity(entity);
}
void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__);
if (_hasModel != entity->hasModel()) {
withWriteLock([&] {
_hasModel = entity->hasModel();
});
_hasModel = entity->hasModel();
QUrl modelURL = QUrl(entity->getModelURL());
if (_parsedModelURL != modelURL) {
_parsedModelURL = modelURL;
}
withWriteLock([&] {
_animating = entity->isAnimatingSomething();
if (_parsedModelURL != entity->getModelURL()) {
_parsedModelURL = QUrl(entity->getModelURL());
}
ModelPointer model = resultWithReadLock<ModelPointer>([&] {
return _model;
});
ModelPointer model;
withReadLock([&] { model = _model; });
bool visuallyReady = model && model->isLoaded() && _didLastVisualGeometryRequestSucceed && _texturesLoaded;
entity->setVisuallyReady(visuallyReady);
withWriteLock([&] {
bool visuallyReady = true;
if (_hasModel) {
if (model && _didLastVisualGeometryRequestSucceed) {
visuallyReady = (_prevModelLoaded && _texturesLoaded);
}
}
entity->setVisuallyReady(visuallyReady);
});
const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene();
render::Transaction transaction;
// Check for removal
if (!_hasModel) {
if (model) {
model->removeFromScene(scene, transaction);
entity->bumpAncestorChainRenderableVersion();
withWriteLock([&] { _model.reset(); });
emit DependencyManager::get<scriptable::ModelProviderFactory>()->
modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model);
modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, model);
withWriteLock([&] { _model.reset(); });
}
setKey(false);
_didLastVisualGeometryRequestSucceed = false;
setKey(_didLastVisualGeometryRequestSucceed, model);
return;
}
@ -1330,48 +1243,52 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
model = std::make_shared<Model>(nullptr, entity.get(), _created);
connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate);
connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) {
setKey(didVisualGeometryRequestSucceed);
_model->setTagMask(getTagMask());
_model->setHifiRenderLayer(getHifiRenderLayer());
_model->setPrimitiveMode(_primitiveMode);
_model->setCullWithParent(_cullWithParent);
_model->setRenderWithZones(_renderWithZones);
emit requestRenderUpdate();
if (didVisualGeometryRequestSucceed) {
emit DependencyManager::get<scriptable::ModelProviderFactory>()->
modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model);
}
_didLastVisualGeometryRequestSucceed = didVisualGeometryRequestSucceed;
const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene();
render::Transaction transaction;
transaction.updateItem<PayloadProxyInterface>(_renderItemID, [&](PayloadProxyInterface& self) {
const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene();
withWriteLock([&] {
setKey(didVisualGeometryRequestSucceed, _model);
_model->setVisibleInScene(_visible, scene);
_model->setCauterized(_cauterized, scene);
_model->setCanCastShadow(_canCastShadow, scene);
_model->setGroupCulled(entity->getGroupCulled(), scene);
_model->setTagMask(getTagMask(), scene);
_model->setHifiRenderLayer(getHifiRenderLayer(), scene);
_model->setPrimitiveMode(_primitiveMode, scene);
_model->setCullWithParent(_cullWithParent, scene);
_model->setRenderWithZones(_renderWithZones, scene);
});
if (didVisualGeometryRequestSucceed) {
emit DependencyManager::get<scriptable::ModelProviderFactory>()->
modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, model);
}
entity->_originalTexturesRead = false;
entity->_needsJointSimulation = true;
entity->_needsToRescaleModel = true;
emit requestRenderUpdate();
});
scene->enqueueTransaction(transaction);
});
model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity));
entity->setModel(model);
withWriteLock([&] { _model = model; });
}
// From here on, we are guaranteed a populated model
if (_parsedModelURL != model->getURL()) {
withWriteLock([&] {
_texturesLoaded = false;
_jointMappingCompleted = false;
model->setURL(_parsedModelURL);
});
_texturesLoaded = false;
_jointMappingCompleted = false;
model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity));
model->setURL(_parsedModelURL);
}
// Nothing else to do unless the model is loaded
if (!model->isLoaded()) {
withWriteLock([&] {
_prevModelLoaded = false;
});
emit requestRenderUpdate();
return;
} else if (!_prevModelLoaded) {
withWriteLock([&] {
_prevModelLoaded = true;
});
}
// Check for initializing the model
// FIXME: There are several places below here where we are modifying the entity, which we should not be doing from the renderable
if (!entity->_dimensionsInitialized) {
EntityItemProperties properties;
properties.setLastEdited(usecTimestampNow()); // we must set the edit time since we're editing it
@ -1391,53 +1308,39 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
entity->_originalTexturesRead = true;
}
if (_textures != entity->getTextures()) {
auto textures = entity->getTextures();
if (_textures != textures) {
QVariantMap newTextures;
withWriteLock([&] {
_texturesLoaded = false;
_textures = entity->getTextures();
newTextures = parseTexturesToMap(_textures, entity->_originalTextures);
});
_texturesLoaded = false;
_textures = textures;
newTextures = parseTexturesToMap(_textures, entity->_originalTextures);
model->setTextures(newTextures);
}
if (entity->_needsJointSimulation) {
entity->copyAnimationJointDataToModel();
}
entity->updateModelBounds();
entity->stopModelOverrideIfNoParent();
if (model->isVisible() != _visible) {
withWriteLock([&] {
setKey(_didLastVisualGeometryRequestSucceed, model);
model->setVisibleInScene(_visible, scene);
}
if (model->isCauterized() != _cauterized) {
model->setCauterized(_cauterized, scene);
}
render::hifi::Tag tagMask = getTagMask();
if (model->getTagMask() != tagMask) {
model->setTagMask(tagMask, scene);
}
model->setCanCastShadow(_canCastShadow, scene);
model->setGroupCulled(entity->getGroupCulled(), scene);
model->setTagMask(getTagMask(), scene);
model->setHifiRenderLayer(getHifiRenderLayer(), scene);
model->setPrimitiveMode(_primitiveMode, scene);
model->setCullWithParent(_cullWithParent, scene);
model->setRenderWithZones(_renderWithZones, scene);
});
if (entity->blendshapesChanged()) {
model->setBlendshapeCoefficients(entity->getBlendshapeCoefficientVector());
model->updateBlendshapes();
}
// TODO? early exit here when not visible?
if (model->canCastShadow() != _canCastShadow) {
model->setCanCastShadow(_canCastShadow, scene);
}
{
bool groupCulled = entity->getGroupCulled();
if (model->isGroupCulled() != groupCulled) {
model->setGroupCulled(groupCulled);
setKey(_didLastVisualGeometryRequestSucceed);
}
}
{
DETAILED_PROFILE_RANGE(simulation_physics, "Fixup");
if (model->needsFixupInScene()) {
@ -1451,11 +1354,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
}
}
bool needsUpdate = false;
if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) {
withWriteLock([&] {
_texturesLoaded = true;
});
model->updateRenderItems();
_texturesLoaded = true;
needsUpdate = true;
} else if (!_texturesLoaded) {
emit requestRenderUpdate();
}
@ -1481,19 +1383,23 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
emit requestRenderUpdate();
} else {
_allProceduralMaterialsLoaded = true;
model->setRenderItemsNeedUpdate();
needsUpdate = true;
}
}
// When the individual mesh parts of a model finish fading, they will mark their Model as needing updating
// we will watch for that and ask the model to update it's render items
if (model->getRenderItemsNeedUpdate()) {
if (needsUpdate || model->getRenderItemsNeedUpdate()) {
model->updateRenderItems();
}
scene->enqueueTransaction(transaction);
}
void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
// The code to deal with the change of properties is now in ModelEntityItem.cpp
// That is where _currentFrame and _lastAnimated were updated.
if (_animating) {
if (entity->isAnimatingSomething()) {
DETAILED_PROFILE_RANGE(simulation_physics, "Animate");
auto animationURL = entity->getAnimationURL();
@ -1502,18 +1408,22 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_animationURL = animationURL;
if (_animation) {
//(_animation->getURL().toString() != entity->getAnimationURL())) { // bad check
// the joints have been mapped before but we have a new animation to load
_animation.reset();
_jointMappingCompleted = false;
}
}
if (!_jointMappingCompleted) {
mapJoints(entity, model);
}
if (entity->readyToAnimate()) {
animate(entity);
ModelPointer model = resultWithReadLock<ModelPointer>([&] {
return _model;
});
if (model && model->isLoaded()) {
if (!_jointMappingCompleted) {
mapJoints(entity, model);
}
if (entity->readyToAnimate()) {
animate(entity, model);
}
}
emit requestRenderUpdate();
}
@ -1521,40 +1431,20 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) {
Parent::setIsVisibleInSecondaryCamera(value);
setKey(_didLastVisualGeometryRequestSucceed);
if (_model) {
_model->setTagMask(getTagMask());
}
// called within a lock so no need to lock for _model
setKey(_didLastVisualGeometryRequestSucceed, _model);
}
void ModelEntityRenderer::setRenderLayer(RenderLayer value) {
Parent::setRenderLayer(value);
setKey(_didLastVisualGeometryRequestSucceed);
if (_model) {
_model->setHifiRenderLayer(getHifiRenderLayer());
}
}
void ModelEntityRenderer::setPrimitiveMode(PrimitiveMode value) {
Parent::setPrimitiveMode(value);
if (_model) {
_model->setPrimitiveMode(_primitiveMode);
}
// called within a lock so no need to lock for _model
setKey(_didLastVisualGeometryRequestSucceed, _model);
}
void ModelEntityRenderer::setCullWithParent(bool value) {
Parent::setCullWithParent(value);
setKey(_didLastVisualGeometryRequestSucceed);
if (_model) {
_model->setCullWithParent(_cullWithParent);
}
}
void ModelEntityRenderer::setRenderWithZones(const QVector<QUuid>& renderWithZones) {
Parent::setRenderWithZones(renderWithZones);
if (_model) {
_model->setRenderWithZones(renderWithZones);
}
// called within a lock so no need to lock for _model
setKey(_didLastVisualGeometryRequestSucceed, _model);
}
// NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items
@ -1570,9 +1460,8 @@ void ModelEntityRenderer::doRender(RenderArgs* args) {
geometryCache->renderWireCubeInstance(args, batch, greenColor, geometryCache->getShapePipelinePointer(false, false, args->_renderMethod == Args::RenderMethod::FORWARD));
#if WANT_EXTRA_DEBUGGING
ModelPointer model;
withReadLock([&] {
model = _model;
ModelPointer model = resultWithReadLock<ModelPointer>([&] {
return _model;
});
if (model) {
model->renderDebugMeshBoxes(batch, args->_renderMethod == Args::RenderMethod::FORWARD);

View file

@ -41,7 +41,6 @@ protected:
ModelEntityWrapper(const EntityItemID& entityItemID) : Parent(entityItemID) {}
void setModel(const ModelPointer& model);
ModelPointer getModel() const;
bool isModelLoaded() const;
bool _needsInitialSimulation{ true };
private:
@ -62,7 +61,6 @@ public:
virtual void setUnscaledDimensions(const glm::vec3& value) override;
virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override;
void doInitialModelSimulation();
void updateModelBounds();
virtual bool supportsDetailedIntersection() const override;
@ -77,6 +75,7 @@ public:
virtual void setShapeType(ShapeType type) override;
virtual void setCompoundShapeURL(const QString& url) override;
virtual void setModelURL(const QString& url) override;
virtual bool isReadyToComputeShape() const override;
virtual void computeShapeInfo(ShapeInfo& shapeInfo) override;
@ -121,6 +120,8 @@ private:
bool readyToAnimate() const;
void fetchCollisionGeometryResource();
QString getCollisionShapeURL() const;
GeometryResource::Pointer _collisionGeometryResource;
std::vector<int> _jointMap;
QVariantMap _originalTextures;
@ -128,6 +129,7 @@ private:
bool _originalTexturesRead { false };
bool _dimensionsInitialized { true };
bool _needsJointSimulation { false };
bool _needsToRescaleModel { false };
};
namespace render { namespace entities {
@ -153,25 +155,23 @@ protected:
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
void setKey(bool didVisualGeometryRequestSucceed);
void setKey(bool didVisualGeometryRequestSucceed, const ModelPointer& model);
virtual ItemKey getKey() override;
virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override;
virtual void handleBlendedVertices(int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs) override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual bool needsRenderUpdate() const override;
virtual void doRender(RenderArgs* args) override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;
void setIsVisibleInSecondaryCamera(bool value) override;
void setRenderLayer(RenderLayer value) override;
void setPrimitiveMode(PrimitiveMode value) override;
void setCullWithParent(bool value) override;
void setRenderWithZones(const QVector<QUuid>& renderWithZones) override;
private:
void animate(const TypedEntityPointer& entity);
void animate(const TypedEntityPointer& entity, const ModelPointer& model);
void mapJoints(const TypedEntityPointer& entity, const ModelPointer& model);
// Transparency is handled in ModelMeshPartPayload
@ -186,20 +186,16 @@ private:
bool _hasTransitioned{ false };
#endif
const void* _collisionMeshKey { nullptr };
QUrl _parsedModelURL;
bool _jointMappingCompleted { false };
QVector<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
AnimationPointer _animation;
bool _animating { false };
QString _animationURL;
uint64_t _lastAnimated { 0 };
render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() };
bool _didLastVisualGeometryRequestSucceed { true };
bool _prevModelLoaded { false };
void processMaterials();
bool _allProceduralMaterialsLoaded { false };

View file

@ -64,84 +64,7 @@ ParticleEffectEntityRenderer::ParticleEffectEntityRenderer(const EntityItemPoint
});
}
bool ParticleEffectEntityRenderer::needsRenderUpdate() const {
if (resultWithReadLock<bool>([&] {
return !_textureLoaded;
})) {
return true;
}
return Parent::needsRenderUpdate();
}
void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
auto newParticleProperties = entity->getParticleProperties();
if (!newParticleProperties.valid()) {
qCWarning(entitiesrenderer) << "Bad particle properties";
}
if (resultWithReadLock<bool>([&] { return _particleProperties != newParticleProperties; })) {
_timeUntilNextEmit = 0;
withWriteLock([&] {
_particleProperties = newParticleProperties;
if (!_prevEmitterShouldTrailInitialized) {
_prevEmitterShouldTrailInitialized = true;
_prevEmitterShouldTrail = _particleProperties.emission.shouldTrail;
}
});
}
withWriteLock([&] {
_pulseProperties = entity->getPulseProperties();
_shapeType = entity->getShapeType();
QString compoundShapeURL = entity->getCompoundShapeURL();
if (_compoundShapeURL != compoundShapeURL) {
_compoundShapeURL = compoundShapeURL;
_hasComputedTriangles = false;
fetchGeometryResource();
}
});
_emitting = entity->getIsEmitting();
bool textureEmpty = resultWithReadLock<bool>([&] { return _particleProperties.textures.isEmpty(); });
if (textureEmpty) {
if (_networkTexture) {
withWriteLock([&] {
_networkTexture.reset();
});
}
withWriteLock([&] {
_textureLoaded = true;
entity->setVisuallyReady(true);
});
} else {
bool textureNeedsUpdate = resultWithReadLock<bool>([&] {
return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures);
});
if (textureNeedsUpdate) {
withWriteLock([&] {
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
_textureLoaded = false;
entity->setVisuallyReady(false);
});
}
if (!_textureLoaded) {
emit requestRenderUpdate();
}
bool textureLoaded = resultWithReadLock<bool>([&] {
return _networkTexture && (_networkTexture->isLoaded() || _networkTexture->isFailed());
});
if (textureLoaded) {
withWriteLock([&] {
entity->setVisuallyReady(true);
_textureLoaded = true;
});
}
}
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] {
withWriteLock([&] {
@ -151,20 +74,66 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
}
void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
auto newParticleProperties = entity->getParticleProperties();
if (!newParticleProperties.valid()) {
qCWarning(entitiesrenderer) << "Bad particle properties";
}
if (_particleProperties != newParticleProperties) {
_timeUntilNextEmit = 0;
_particleProperties = newParticleProperties;
if (!_prevEmitterShouldTrailInitialized) {
_prevEmitterShouldTrailInitialized = true;
_prevEmitterShouldTrail = _particleProperties.emission.shouldTrail;
}
}
_pulseProperties = entity->getPulseProperties();
_shapeType = entity->getShapeType();
QString compoundShapeURL = entity->getCompoundShapeURL();
if (_compoundShapeURL != compoundShapeURL) {
_compoundShapeURL = compoundShapeURL;
_hasComputedTriangles = false;
fetchGeometryResource();
}
_emitting = entity->getIsEmitting();
if (_particleProperties.textures.isEmpty()) {
if (_networkTexture) {
_networkTexture.reset();
}
_textureLoaded = true;
entity->setVisuallyReady(true);
} else {
if (!_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures)) {
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
_textureLoaded = false;
entity->setVisuallyReady(false);
}
if (!_textureLoaded) {
emit requestRenderUpdate();
}
if (_networkTexture && (_networkTexture->isLoaded() || _networkTexture->isFailed())) {
entity->setVisuallyReady(true);
_textureLoaded = true;
}
}
// Fill in Uniforms structure
ParticleUniforms particleUniforms;
withReadLock([&] {
particleUniforms.radius.start = _particleProperties.radius.range.start;
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
particleUniforms.radius.spread = _particleProperties.radius.gradient.spread;
particleUniforms.spin.start = _particleProperties.spin.range.start;
particleUniforms.spin.middle = _particleProperties.spin.gradient.target;
particleUniforms.spin.finish = _particleProperties.spin.range.finish;
particleUniforms.spin.spread = _particleProperties.spin.gradient.spread;
particleUniforms.lifespan = _particleProperties.lifespan;
particleUniforms.rotateWithEntity = _particleProperties.rotateWithEntity ? 1 : 0;
});
particleUniforms.radius.start = _particleProperties.radius.range.start;
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
particleUniforms.radius.spread = _particleProperties.radius.gradient.spread;
particleUniforms.spin.start = _particleProperties.spin.range.start;
particleUniforms.spin.middle = _particleProperties.spin.gradient.target;
particleUniforms.spin.finish = _particleProperties.spin.range.finish;
particleUniforms.spin.spread = _particleProperties.spin.gradient.spread;
particleUniforms.lifespan = _particleProperties.lifespan;
particleUniforms.rotateWithEntity = _particleProperties.rotateWithEntity ? 1 : 0;
// Update particle uniforms
_uniformBuffer.edit<ParticleUniforms>() = particleUniforms;
}
@ -403,27 +372,18 @@ void ParticleEffectEntityRenderer::stepSimulation() {
const auto interval = std::min<uint64_t>(USECS_PER_SECOND / 60, now - _lastSimulated);
_lastSimulated = now;
particle::Properties particleProperties;
ShapeType shapeType;
GeometryResource::Pointer geometryResource;
withReadLock([&] {
particleProperties = _particleProperties;
shapeType = _shapeType;
geometryResource = _geometryResource;
});
const auto& modelTransform = getModelTransform();
if (_emitting && particleProperties.emitting() &&
(shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) {
uint64_t emitInterval = particleProperties.emitIntervalUsecs();
if (_emitting && _particleProperties.emitting() &&
(_shapeType != SHAPE_TYPE_COMPOUND || (_geometryResource && _geometryResource->isLoaded()))) {
uint64_t emitInterval = _particleProperties.emitIntervalUsecs();
if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
auto timeRemaining = interval;
while (timeRemaining > _timeUntilNextEmit) {
if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) {
computeTriangles(geometryResource->getHFMModel());
computeTriangles(_geometryResource->getHFMModel());
}
// emit particle
_cpuParticles.push_back(createParticle(modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo));
_cpuParticles.push_back(createParticle(modelTransform, _particleProperties, _shapeType, _geometryResource, _triangleInfo));
_timeUntilNextEmit = emitInterval;
if (emitInterval < timeRemaining) {
timeRemaining -= emitInterval;
@ -435,14 +395,14 @@ void ParticleEffectEntityRenderer::stepSimulation() {
}
// Kill any particles that have expired or are over the max size
while (_cpuParticles.size() > particleProperties.maxParticles || (!_cpuParticles.empty() && _cpuParticles.front().expiration == 0)) {
while (_cpuParticles.size() > _particleProperties.maxParticles || (!_cpuParticles.empty() && _cpuParticles.front().expiration == 0)) {
_cpuParticles.pop_front();
}
const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
// update the particles
for (auto& particle : _cpuParticles) {
if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
if (_prevEmitterShouldTrail != _particleProperties.emission.shouldTrail) {
if (_prevEmitterShouldTrail) {
particle.relativePosition = particle.relativePosition + particle.basePosition - modelTransform.getTranslation();
}
@ -451,14 +411,14 @@ void ParticleEffectEntityRenderer::stepSimulation() {
particle.expiration = particle.expiration >= interval ? particle.expiration - interval : 0;
particle.integrate(deltaTime);
}
_prevEmitterShouldTrail = particleProperties.emission.shouldTrail;
_prevEmitterShouldTrail = _particleProperties.emission.shouldTrail;
// Build particle primitives
static GpuParticles gpuParticles;
gpuParticles.clear();
gpuParticles.reserve(_cpuParticles.size()); // Reserve space
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) {
glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [this, &modelTransform] (const CpuParticle& particle) {
glm::vec3 position = particle.relativePosition + (_particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
});
@ -487,13 +447,14 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
// if the particles are marked rotateWithEntity
withReadLock([&] {
transform.setRotation(_renderTransform.getRotation());
auto& color = _uniformBuffer.edit<ParticleUniforms>().color;
color.start = EntityRenderer::calculatePulseColor(_particleProperties.getColorStart(), _pulseProperties, _created);
color.middle = EntityRenderer::calculatePulseColor(_particleProperties.getColorMiddle(), _pulseProperties, _created);
color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created);
color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created);
});
auto& color = _uniformBuffer.edit<ParticleUniforms>().color;
color.start = EntityRenderer::calculatePulseColor(_particleProperties.getColorStart(), _pulseProperties, _created);
color.middle = EntityRenderer::calculatePulseColor(_particleProperties.getColorMiddle(), _pulseProperties, _created);
color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created);
color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created);
batch.setModelTransform(transform);
batch.setUniformBuffer(0, _uniformBuffer);
batch.setInputFormat(_vertexFormat);

View file

@ -25,7 +25,6 @@ public:
ParticleEffectEntityRenderer(const EntityItemPointer& entity);
protected:
virtual bool needsRenderUpdate() const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;

View file

@ -121,16 +121,6 @@ ShapeKey PolyLineEntityRenderer::getShapeKey() {
return builder.build();
}
bool PolyLineEntityRenderer::needsRenderUpdate() const {
if (resultWithReadLock<bool>([&] {
return (!_textureLoaded && _texture && _texture->isLoaded());
})) {
return true;
}
return Parent::needsRenderUpdate();
}
bool PolyLineEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (entity->pointsChanged() || entity->widthsChanged() || entity->normalsChanged() || entity->texturesChanged() || entity->colorsChanged()) {
return true;
@ -140,6 +130,15 @@ bool PolyLineEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityP
}
void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] {
withWriteLock([&] {
_renderTransform = getModelTransform();
});
});
}
void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
auto pointsChanged = entity->pointsChanged();
auto widthsChanged = entity->widthsChanged();
auto normalsChanged = entity->normalsChanged();
@ -159,13 +158,15 @@ void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer&
if (!textures.isEmpty()) {
entityTextures = QUrl(textures);
}
withWriteLock([&] {
_texture = DependencyManager::get<TextureCache>()->getTexture(entityTextures);
});
_texture = DependencyManager::get<TextureCache>()->getTexture(entityTextures);
_textureAspectRatio = 1.0f;
_textureLoaded = false;
}
if (!_textureLoaded) {
emit requestRenderUpdate();
}
bool textureChanged = false;
if (!_textureLoaded && _texture && _texture->isLoaded()) {
textureChanged = true;
@ -175,13 +176,11 @@ void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer&
// Data
bool faceCameraChanged = faceCamera != _faceCamera;
withWriteLock([&] {
if (faceCameraChanged || glow != _glow) {
_faceCamera = faceCamera;
_glow = glow;
updateData();
}
});
if (faceCameraChanged || glow != _glow) {
_faceCamera = faceCamera;
_glow = glow;
updateData();
}
// Geometry
if (pointsChanged) {
@ -200,19 +199,10 @@ void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer&
bool uvModeStretchChanged = _isUVModeStretch != isUVModeStretch;
_isUVModeStretch = isUVModeStretch;
bool geometryChanged = uvModeStretchChanged || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged || faceCameraChanged;
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, geometryChanged] {
withWriteLock([&] {
_renderTransform = getModelTransform();
if (geometryChanged) {
updateGeometry();
}
});
});
if (uvModeStretchChanged || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged || faceCameraChanged) {
updateGeometry();
}
}
void PolyLineEntityRenderer::updateGeometry() {
@ -318,19 +308,16 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) {
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
size_t numVertices;
Transform transform;
gpu::TexturePointer texture;
gpu::TexturePointer texture = _textureLoaded ? _texture->getGPUTexture() : DependencyManager::get<TextureCache>()->getWhiteTexture();
withReadLock([&] {
numVertices = _numVertices;
transform = _renderTransform;
texture = _textureLoaded ? _texture->getGPUTexture() : DependencyManager::get<TextureCache>()->getWhiteTexture();
batch.setResourceBuffer(0, _polylineGeometryBuffer);
batch.setUniformBuffer(0, _polylineDataBuffer);
});
if (numVertices < 2) {
batch.setResourceBuffer(0, _polylineGeometryBuffer);
batch.setUniformBuffer(0, _polylineDataBuffer);
if (_numVertices < 2) {
return;
}
@ -341,5 +328,5 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) {
batch.setPipeline(_pipelines[{args->_renderMethod, isTransparent()}]);
batch.setModelTransform(transform);
batch.setResourceTexture(0, texture);
batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * numVertices), 0);
batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * _numVertices), 0);
}

View file

@ -30,9 +30,9 @@ public:
virtual bool isTransparent() const override;
protected:
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual ItemKey getKey() override;
virtual ShapeKey getShapeKey() override;

View file

@ -1802,25 +1802,44 @@ ShapeKey PolyVoxEntityRenderer::getShapeKey() {
}
bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (entity->voxelToWorldMatrix() != _lastVoxelToWorldMatrix) {
if (resultWithReadLock<bool>([&] {
if (entity->voxelToWorldMatrix() != _lastVoxelToWorldMatrix) {
return true;
}
if (entity->_mesh != _mesh) {
return true;
}
return false;
})) {
return true;
}
if (entity->_mesh != _mesh) {
return true;
}
return false;
return Parent::needsRenderUpdateFromTypedEntity(entity);
}
void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT
if (!_hasTransitioned) {
transaction.resetTransitionOnItem(_renderItemID, render::Transition::ELEMENT_ENTER_DOMAIN);
_hasTransitioned = true;
}
#endif
}
void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
_lastVoxelToWorldMatrix = entity->voxelToWorldMatrix();
_lastVoxelVolumeSize = entity->getVoxelVolumeSize();
_params->setSubData(0, vec4(_lastVoxelVolumeSize, 0.0));
graphics::MeshPointer newMesh;
entity->withReadLock([&] {
newMesh = entity->_mesh;
});
if (newMesh && newMesh->getIndexBuffer()._buffer) {
_mesh = newMesh;
}
std::array<QString, 3> xyzTextureURLs{ {
entity->getXTextureURL(),
@ -1838,20 +1857,6 @@ void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& s
}
}
void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
_lastVoxelToWorldMatrix = entity->voxelToWorldMatrix();
_lastVoxelVolumeSize = entity->getVoxelVolumeSize();
_params->setSubData(0, vec4(_lastVoxelVolumeSize, 0.0));
graphics::MeshPointer newMesh;
entity->withReadLock([&] {
newMesh = entity->_mesh;
});
if (newMesh && newMesh->getIndexBuffer()._buffer) {
_mesh = newMesh;
}
}
void PolyVoxEntityRenderer::doRender(RenderArgs* args) {
if (!_mesh || !_mesh->getIndexBuffer()._buffer) {
return;

View file

@ -51,24 +51,7 @@ bool ShapeEntityRenderer::needsRenderUpdate() const {
return Parent::needsRenderUpdate();
}
bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (_dimensions != entity->getScaledDimensions()) {
return true;
}
if (_proceduralData != entity->getUserData()) {
return true;
}
return false;
}
void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
withWriteLock([&] {
_shape = entity->getShape();
_pulseProperties = entity->getPulseProperties();
});
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] {
withWriteLock([&] {
@ -86,47 +69,53 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
}
void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
_shape = entity->getShape();
_pulseProperties = entity->getPulseProperties();
bool materialChanged = false;
glm::vec3 color = toGlm(entity->getColor());
if (_color != color) {
_color = color;
_material->setAlbedo(color);
materialChanged = true;
}
float alpha = entity->getAlpha();
if (_alpha != alpha) {
_alpha = alpha;
_material->setOpacity(alpha);
materialChanged = true;
}
auto userData = entity->getUserData();
if (_proceduralData != userData) {
_proceduralData = userData;
_material->setProceduralData(_proceduralData);
materialChanged = true;
}
withReadLock([&] {
auto mat = _materials.find("0");
if (mat != _materials.end() && mat->second.top().material && mat->second.top().material->isProcedural() && mat->second.top().material->isReady()) {
auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(mat->second.top().material);
if (procedural->isFading()) {
procedural->setIsFading(Interpolate::calculateFadeRatio(procedural->getFadeStartTime()) < 1.0f);
}
}
});
withWriteLock([&] {
bool materialChanged = false;
glm::vec3 color = toGlm(entity->getColor());
if (_color != color) {
_color = color;
_material->setAlbedo(color);
materialChanged = true;
}
float alpha = entity->getAlpha();
if (_alpha != alpha) {
_alpha = alpha;
_material->setOpacity(alpha);
materialChanged = true;
}
auto userData = entity->getUserData();
if (_proceduralData != userData) {
_proceduralData = userData;
_material->setProceduralData(_proceduralData);
materialChanged = true;
}
auto materials = _materials.find("0");
if (materials != _materials.end()) {
if (materialChanged) {
materials->second.setNeedsUpdate(true);
}
bool requestUpdate = false;
if (materials->second.top().material && materials->second.top().material->isProcedural() && materials->second.top().material->isReady()) {
auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(materials->second.top().material);
if (procedural->isFading()) {
procedural->setIsFading(Interpolate::calculateFadeRatio(procedural->getFadeStartTime()) < 1.0f);
requestUpdate = true;
}
}
if (materials->second.shouldUpdate()) {
RenderPipelines::updateMultiMaterial(materials->second);
requestUpdate = true;
}
if (requestUpdate) {
emit requestRenderUpdate();
}
}
@ -231,13 +220,12 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
graphics::MultiMaterial materials;
auto geometryCache = DependencyManager::get<GeometryCache>();
GeometryCache::Shape geometryShape;
GeometryCache::Shape geometryShape = geometryCache->getShapeForEntityShape(_shape);
PrimitiveMode primitiveMode;
RenderLayer renderLayer;
glm::vec4 outColor;
Pipeline pipelineType;
withReadLock([&] {
geometryShape = geometryCache->getShapeForEntityShape(_shape);
primitiveMode = _primitiveMode;
renderLayer = _renderLayer;
batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation
@ -245,9 +233,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
pipelineType = getPipelineType(materials);
auto& schema = materials.getSchemaBuffer().get<graphics::MultiMaterial::Schema>();
outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity);
outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created);
});
outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created);
if (outColor.a == 0.0f) {
return;
}
@ -256,7 +245,9 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(materials.top().material);
outColor = procedural->getColor(outColor);
outColor.a *= procedural->isFading() ? Interpolate::calculateFadeRatio(procedural->getFadeStartTime()) : 1.0f;
procedural->prepare(batch, _position, _dimensions, _orientation, _created, ProceduralProgramKey(outColor.a < 1.0f));
withReadLock([&] {
procedural->prepare(batch, _position, _dimensions, _orientation, _created, ProceduralProgramKey(outColor.a < 1.0f));
});
if (render::ShapeKey(args->_globalShapeKey).isWireframe() || primitiveMode == PrimitiveMode::LINES) {
geometryCache->renderWireShape(batch, geometryShape, outColor);

View file

@ -30,7 +30,6 @@ protected:
private:
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;

View file

@ -92,14 +92,6 @@ uint32_t TextEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) const {
return parentSubs;
}
bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (_dimensions != entity->getScaledDimensions()) {
return true;
}
return false;
}
void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] {
@ -112,26 +104,24 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
}
void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
withWriteLock([&] {
_pulseProperties = entity->getPulseProperties();
_text = entity->getText();
_lineHeight = entity->getLineHeight();
_textColor = toGlm(entity->getTextColor());
_textAlpha = entity->getTextAlpha();
_backgroundColor = toGlm(entity->getBackgroundColor());
_backgroundAlpha = entity->getBackgroundAlpha();
_billboardMode = entity->getBillboardMode();
_leftMargin = entity->getLeftMargin();
_rightMargin = entity->getRightMargin();
_topMargin = entity->getTopMargin();
_bottomMargin = entity->getBottomMargin();
_unlit = entity->getUnlit();
_font = entity->getFont();
_effect = entity->getTextEffect();
_effectColor = toGlm(entity->getTextEffectColor());
_effectThickness = entity->getTextEffectThickness();
updateTextRenderItem();
});
_pulseProperties = entity->getPulseProperties();
_text = entity->getText();
_lineHeight = entity->getLineHeight();
_textColor = toGlm(entity->getTextColor());
_textAlpha = entity->getTextAlpha();
_backgroundColor = toGlm(entity->getBackgroundColor());
_backgroundAlpha = entity->getBackgroundAlpha();
_billboardMode = entity->getBillboardMode();
_leftMargin = entity->getLeftMargin();
_rightMargin = entity->getRightMargin();
_topMargin = entity->getTopMargin();
_bottomMargin = entity->getBottomMargin();
_unlit = entity->getUnlit();
_font = entity->getFont();
_effect = entity->getTextEffect();
_effectColor = toGlm(entity->getTextEffectColor());
_effectThickness = entity->getTextEffectThickness();
updateTextRenderItem();
}
void TextEntityRenderer::doRender(RenderArgs* args) {
@ -141,25 +131,23 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
glm::vec4 backgroundColor;
Transform modelTransform;
BillboardMode billboardMode;
PrimitiveMode primitiveMode;
RenderLayer renderLayer;
withReadLock([&] {
modelTransform = _renderTransform;
billboardMode = _billboardMode;
primitiveMode = _primitiveMode;
renderLayer = _renderLayer;
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
});
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
if (backgroundColor.a <= 0.0f) {
return;
}
modelTransform.setRotation(EntityItem::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), billboardMode, args->getViewFrustum().getPosition()));
modelTransform.setRotation(EntityItem::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), _billboardMode, args->getViewFrustum().getPosition()));
batch.setModelTransform(modelTransform);
auto geometryCache = DependencyManager::get<GeometryCache>();
@ -323,53 +311,36 @@ void entities::TextPayload::render(RenderArgs* args) {
glm::vec3 dimensions;
BillboardMode billboardMode;
QString text;
glm::vec4 textColor;
QString font;
TextEffect effect;
glm::vec3 effectColor;
float effectThickness;
float lineHeight, leftMargin, rightMargin, topMargin, bottomMargin;
bool forward;
textRenderable->withReadLock([&] {
modelTransform = textRenderable->_renderTransform;
dimensions = textRenderable->_dimensions;
billboardMode = textRenderable->_billboardMode;
text = textRenderable->_text;
font = textRenderable->_font;
effect = textRenderable->_effect;
effectThickness = textRenderable->_effectThickness;
lineHeight = textRenderable->_lineHeight;
leftMargin = textRenderable->_leftMargin;
rightMargin = textRenderable->_rightMargin;
topMargin = textRenderable->_topMargin;
bottomMargin = textRenderable->_bottomMargin;
float fadeRatio = textRenderable->_isFading ? Interpolate::calculateFadeRatio(textRenderable->_fadeStartTime) : 1.0f;
textColor = glm::vec4(textRenderable->_textColor, fadeRatio * textRenderable->_textAlpha);
textColor = EntityRenderer::calculatePulseColor(textColor, textRenderable->_pulseProperties, textRenderable->_created);
effectColor = EntityRenderer::calculatePulseColor(textRenderable->_effectColor, textRenderable->_pulseProperties, textRenderable->_created);
forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD;
});
textColor = EntityRenderer::calculatePulseColor(textColor, textRenderable->_pulseProperties, textRenderable->_created);
glm::vec3 effectColor = EntityRenderer::calculatePulseColor(textRenderable->_effectColor, textRenderable->_pulseProperties, textRenderable->_created);
if (textColor.a <= 0.0f) {
return;
}
modelTransform.setRotation(EntityItem::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), billboardMode, args->getViewFrustum().getPosition()));
float scale = lineHeight / textRenderer->getFontSize();
float scale = textRenderable->_lineHeight / textRenderer->getFontSize();
modelTransform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z));
modelTransform.setScale(scale);
batch.setModelTransform(modelTransform);
glm::vec2 bounds = glm::vec2(dimensions.x - (leftMargin + rightMargin), dimensions.y - (topMargin + bottomMargin));
textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, bounds / scale, scale,
text, font, textColor, effectColor, effectThickness, effect,
glm::vec2 bounds = glm::vec2(dimensions.x - (textRenderable->_leftMargin + textRenderable->_rightMargin), dimensions.y - (textRenderable->_topMargin + textRenderable->_bottomMargin));
textRenderer->draw(batch, textRenderable->_leftMargin / scale, -textRenderable->_topMargin / scale, bounds / scale, scale,
textRenderable->_text, textRenderable->_font, textColor, effectColor, textRenderable->_effectThickness, textRenderable->_effect,
textRenderable->_unlit, forward);
}

View file

@ -42,7 +42,6 @@ protected:
void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
private:
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;

View file

@ -125,16 +125,6 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe
return false;
}
bool WebEntityRenderer::needsRenderUpdate() const {
if (resultWithReadLock<bool>([this] {
return !_webSurface;
})) {
return true;
}
return Parent::needsRenderUpdate();
}
void WebEntityRenderer::onTimeout() {
uint64_t lastRenderTime;
if (!resultWithReadLock<bool>([&] {
@ -261,6 +251,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
_renderTransform.postScale(entity->getScaledDimensions());
});
});
} else {
emit requestRenderUpdate();
}
});
}
@ -339,8 +331,11 @@ void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const Q
return;
}
++_currentWebCount;
WebEntityRenderer::acquireWebSurface(newSourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface);
bool isHTML = _contentType == ContentType::HtmlContent;
if (isHTML) {
++_currentWebCount;
}
WebEntityRenderer::acquireWebSurface(newSourceURL, isHTML, _webSurface, _cachedWebSurface);
_fadeStartTime = usecTimestampNow();
_webSurface->resume();
@ -358,12 +353,15 @@ void WebEntityRenderer::destroyWebSurface() {
QSharedPointer<OffscreenQmlSurface> webSurface;
withWriteLock([&] {
webSurface.swap(_webSurface);
_contentType = ContentType::NoContent;
if (webSurface) {
--_currentWebCount;
if (_contentType == ContentType::HtmlContent) {
--_currentWebCount;
}
WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections);
}
_contentType = ContentType::NoContent;
});
}

View file

@ -55,7 +55,6 @@ public:
virtual QObject* getEventHandler() override;
protected:
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;

View file

@ -189,7 +189,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
CullTest::_containingZones.insert(_entityID);
}
void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
auto position = entity->getWorldPosition();
auto rotation = entity->getWorldOrientation();
auto dimensions = entity->getScaledDimensions();
@ -199,7 +199,11 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
auto visible = entity->getVisible();
if (transformChanged || visible != _lastVisible) {
_lastVisible = visible;
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
void* key = (void*)this;
EntityItemID id = entity->getID();
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [id] {
DependencyManager::get<EntityTreeRenderer>()->updateZone(id);
});
}
auto proceduralUserData = entity->getUserData();
@ -267,10 +271,6 @@ ItemKey ZoneEntityRenderer::getKey() {
}
bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (entity->getVisible() != _lastVisible) {
return true;
}
if (entity->keyLightPropertiesChanged() ||
entity->ambientLightPropertiesChanged() ||
entity->hazePropertiesChanged() ||
@ -280,29 +280,11 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
if (_skyboxTextureURL != entity->getSkyboxProperties().getURL()) {
return true;
}
if (entity->getWorldPosition() != _lastPosition) {
return true;
}
if (entity->getScaledDimensions() != _lastDimensions) {
return true;
}
if (entity->getWorldOrientation() != _lastRotation) {
return true;
}
if (entity->getUserData() != _proceduralUserData) {
return true;
}
return false;
}
void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity) {
setKeyLightMode((ComponentMode)entity->getKeyLightMode());
_keyLightMode = (ComponentMode)entity->getKeyLightMode();
const auto& sunLight = editSunLight();
sunLight->setType(graphics::Light::SUN);
@ -319,7 +301,7 @@ void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity
}
void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& entity) {
setAmbientLightMode((ComponentMode)entity->getAmbientLightMode());
_ambientLightMode = (ComponentMode)entity->getAmbientLightMode();
const auto& ambientLight = editAmbientLight();
ambientLight->setType(graphics::Light::AMBIENT);
@ -339,11 +321,12 @@ void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer&
}
void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity) {
setHazeMode((ComponentMode)entity->getHazeMode());
const uint32_t hazeMode = entity->getHazeMode();
_hazeMode = (ComponentMode)hazeMode;
const auto& haze = editHaze();
const uint32_t hazeMode = entity->getHazeMode();
haze->setHazeActive(hazeMode == COMPONENT_MODE_ENABLED);
haze->setAltitudeBased(_hazeProperties.getHazeAltitudeEffect());
@ -367,7 +350,7 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity)
}
void ZoneEntityRenderer::updateBloomFromEntity(const TypedEntityPointer& entity) {
setBloomMode((ComponentMode)entity->getBloomMode());
_bloomMode = (ComponentMode)entity->getBloomMode();
const auto& bloom = editBloom();
@ -377,7 +360,7 @@ void ZoneEntityRenderer::updateBloomFromEntity(const TypedEntityPointer& entity)
}
void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& entity) {
setSkyboxMode((ComponentMode)entity->getSkyboxMode());
_skyboxMode = (ComponentMode)entity->getSkyboxMode();
editBackground();
setSkyboxColor(toGlm(_skyboxProperties.getColor()));
@ -478,26 +461,6 @@ void ZoneEntityRenderer::updateSkyboxMap() {
}
}
void ZoneEntityRenderer::setHazeMode(ComponentMode mode) {
_hazeMode = mode;
}
void ZoneEntityRenderer::setKeyLightMode(ComponentMode mode) {
_keyLightMode = mode;
}
void ZoneEntityRenderer::setAmbientLightMode(ComponentMode mode) {
_ambientLightMode = mode;
}
void ZoneEntityRenderer::setSkyboxMode(ComponentMode mode) {
_skyboxMode = mode;
}
void ZoneEntityRenderer::setBloomMode(ComponentMode mode) {
_bloomMode = mode;
}
void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) {
editSkybox()->setColor(color);
}

View file

@ -38,7 +38,7 @@ protected:
virtual ItemKey getKey() override;
virtual void doRender(RenderArgs* args) override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
private:
void updateKeyZoneItemFromEntity(const TypedEntityPointer& entity);
@ -52,12 +52,6 @@ private:
void setAmbientURL(const QString& ambientUrl);
void setSkyboxURL(const QString& skyboxUrl);
void setHazeMode(ComponentMode mode);
void setKeyLightMode(ComponentMode mode);
void setAmbientLightMode(ComponentMode mode);
void setSkyboxMode(ComponentMode mode);
void setBloomMode(ComponentMode mode);
void setSkyboxColor(const glm::vec3& color);
void setProceduralUserData(const QString& userData);

View file

@ -1911,13 +1911,11 @@ void EntityItem::setUnscaledDimensions(const glm::vec3& value) {
if (glm::length2(getUnscaledDimensions() - newDimensions) > MIN_SCALE_CHANGE_SQUARED) {
withWriteLock([&] {
_unscaledDimensions = newDimensions;
});
locationChanged();
dimensionsChanged();
withWriteLock([&] {
_flags |= (Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
_queryAACubeSet = false;
});
locationChanged();
dimensionsChanged();
}
}

View file

@ -1046,18 +1046,26 @@ QSizeF EntityScriptingInterface::textSize(const QUuid& id, const QString& text)
return EntityTree::textSize(id, text);
}
void EntityScriptingInterface::setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine) {
void EntityScriptingInterface::setPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
_entitiesScriptEngine = engine;
_persistentEntitiesScriptEngine = engine;
}
void EntityScriptingInterface::setNonPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
_nonPersistentEntitiesScriptEngine = engine;
}
void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) {
PROFILE_RANGE(script_entities, __FUNCTION__);
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
if (_entitiesScriptEngine) {
EntityItemID entityID{ id };
_entitiesScriptEngine->callEntityScriptMethod(entityID, method, params);
auto entity = getEntityTree()->findEntityByEntityItemID(id);
if (entity) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
if (scriptEngine) {
scriptEngine->callEntityScriptMethod(id, method, params);
}
}
}
@ -1099,9 +1107,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer
params << paramString;
}
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID());
auto entity = getEntityTree()->findEntityByEntityItemID(entityID);
if (entity) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
if (scriptEngine) {
scriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID());
}
}
}
}
@ -1332,7 +1344,7 @@ bool EntityPropertyMetadataRequest::script(EntityItemID entityID, QScriptValue h
if (entitiesScriptEngine) {
request->setFuture(entitiesScriptEngine->getLocalEntityScriptDetails(entityID));
}
});
}, entityID);
if (!request->isStarted()) {
request->deleteLater();
callScopedHandlerObject(handler, _engine->makeError("Entities Scripting Provider unavailable", "InternalError"), QScriptValue());

View file

@ -181,7 +181,8 @@ public:
void setEntityTree(EntityTreePointer modelTree);
EntityTreePointer getEntityTree() { return _entityTree; }
void setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
void setPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
void setNonPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
void resetActivityTracking();
ActivityTracking getActivityTracking() const { return _activityTracking; }
@ -2510,9 +2511,12 @@ signals:
void webEventReceived(const EntityItemID& entityItemID, const QVariant& message);
protected:
void withEntitiesScriptEngine(std::function<void(QSharedPointer<EntitiesScriptEngineProvider>)> function) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
function(_entitiesScriptEngine);
void withEntitiesScriptEngine(std::function<void(QSharedPointer<EntitiesScriptEngineProvider>)> function, const EntityItemID& id) {
auto entity = getEntityTree()->findEntityByEntityItemID(id);
if (entity) {
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
function((entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine);
}
};
private slots:
@ -2542,7 +2546,8 @@ private:
EntityTreePointer _entityTree;
std::recursive_mutex _entitiesScriptEngineLock;
QSharedPointer<EntitiesScriptEngineProvider> _entitiesScriptEngine;
QSharedPointer<EntitiesScriptEngineProvider> _persistentEntitiesScriptEngine;
QSharedPointer<EntitiesScriptEngineProvider> _nonPersistentEntitiesScriptEngine;
bool _bidOnSimulationOwnership { false };

View file

@ -78,7 +78,14 @@ void LightEntityItem::setFalloffRadius(float value) {
}
void LightEntityItem::setIsSpotlight(bool value) {
if (value == getIsSpotlight()) {
bool needsRenderUpdate;
withWriteLock([&] {
needsRenderUpdate = value != _isSpotlight;
_needsRenderUpdate |= needsRenderUpdate;
_isSpotlight = value;
});
if (!needsRenderUpdate) {
return;
}
@ -92,25 +99,25 @@ void LightEntityItem::setIsSpotlight(bool value) {
newDimensions = glm::vec3(glm::compMax(dimensions));
}
withWriteLock([&] {
_needsRenderUpdate = true;
_isSpotlight = value;
});
setScaledDimensions(newDimensions);
}
void LightEntityItem::setCutoff(float value) {
value = glm::clamp(value, MIN_CUTOFF, MAX_CUTOFF);
if (value == getCutoff()) {
bool needsRenderUpdate;
bool spotlight;
withWriteLock([&] {
needsRenderUpdate = value != _cutoff;
_needsRenderUpdate |= needsRenderUpdate;
_cutoff = value;
spotlight = _isSpotlight;
});
if (!needsRenderUpdate) {
return;
}
withWriteLock([&] {
_needsRenderUpdate = true;
_cutoff = value;
});
if (getIsSpotlight()) {
if (spotlight) {
// If we are a spotlight, adjusting the cutoff will affect the area we encapsulate,
// so update the dimensions to reflect this.
const float length = getScaledDimensions().z;

View file

@ -254,26 +254,30 @@ void ModelEntityItem::debugDump() const {
}
void ModelEntityItem::setShapeType(ShapeType type) {
bool changed = false;
uint32_t flags = 0;
withWriteLock([&] {
if (type != _shapeType) {
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
// dynamic and STATIC_MESH are incompatible
// since the shape is being set here we clear the dynamic bit
_dynamic = false;
_flags |= Simulation::DIRTY_MOTION_TYPE;
flags = Simulation::DIRTY_MOTION_TYPE;
}
_shapeType = type;
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
changed = true;
}
});
if (changed) {
markDirtyFlags(flags);
locationChanged();
}
}
ShapeType ModelEntityItem::getShapeType() const {
return computeTrueShapeType();
}
ShapeType ModelEntityItem::computeTrueShapeType() const {
ShapeType type = _shapeType;
ShapeType type = resultWithReadLock<ShapeType>([&] { return _shapeType; });
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
// dynamic is incompatible with STATIC_MESH
// shouldn't fall in here but just in case --> fall back to COMPOUND
@ -290,7 +294,6 @@ void ModelEntityItem::setModelURL(const QString& url) {
withWriteLock([&] {
if (_modelURL != url) {
_modelURL = url;
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
_needsRenderUpdate = true;
}
});
@ -325,18 +328,10 @@ void ModelEntityItem::setCompoundShapeURL(const QString& url) {
withWriteLock([&] {
if (_compoundShapeURL.get() != url) {
_compoundShapeURL.set(url);
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
});
}
void ModelEntityItem::setAnimationURL(const QString& url) {
_flags |= Simulation::DIRTY_UPDATEABLE;
withWriteLock([&] {
_animationProperties.setURL(url);
});
}
void ModelEntityItem::setAnimationSettings(const QString& value) {
// NOTE: this method only called for old bitstream format
@ -399,20 +394,6 @@ void ModelEntityItem::setAnimationSettings(const QString& value) {
});
}
void ModelEntityItem::setAnimationIsPlaying(bool value) {
_flags |= Simulation::DIRTY_UPDATEABLE;
withWriteLock([&] {
_animationProperties.setRunning(value);
});
}
void ModelEntityItem::setAnimationFPS(float value) {
_flags |= Simulation::DIRTY_UPDATEABLE;
withWriteLock([&] {
_animationProperties.setFPS(value);
});
}
void ModelEntityItem::resizeJointArrays(int newSize) {
if (newSize < 0) {
return;
@ -588,10 +569,6 @@ QString ModelEntityItem::getCompoundShapeURL() const {
return _compoundShapeURL.get();
}
QString ModelEntityItem::getCollisionShapeURL() const {
return getShapeType() == SHAPE_TYPE_COMPOUND ? getCompoundShapeURL() : getModelURL();
}
void ModelEntityItem::setColor(const glm::u8vec3& value) {
withWriteLock([&] {
_color = value;
@ -629,61 +606,18 @@ void ModelEntityItem::setAnimationCurrentFrame(float value) {
});
}
void ModelEntityItem::setAnimationAllowTranslation(bool value) {
withWriteLock([&] {
_animationProperties.setAllowTranslation(value);
});
}
bool ModelEntityItem::getAnimationAllowTranslation() const {
return resultWithReadLock<bool>([&] {
return _animationProperties.getAllowTranslation();
});
}
void ModelEntityItem::setAnimationLoop(bool loop) {
withWriteLock([&] {
_animationProperties.setLoop(loop);
});
}
bool ModelEntityItem::getAnimationLoop() const {
return resultWithReadLock<bool>([&] {
return _animationProperties.getLoop();
});
}
void ModelEntityItem::setAnimationHold(bool hold) {
withWriteLock([&] {
_animationProperties.setHold(hold);
});
}
bool ModelEntityItem::getAnimationHold() const {
return resultWithReadLock<bool>([&] {
return _animationProperties.getHold();
});
}
bool ModelEntityItem::getAnimationIsPlaying() const {
return resultWithReadLock<bool>([&] {
return _animationProperties.getRunning();
});
}
float ModelEntityItem::getAnimationCurrentFrame() const {
return resultWithReadLock<float>([&] {
return _animationProperties.getCurrentFrame();
});
}
float ModelEntityItem::getAnimationFPS() const {
return resultWithReadLock<float>([&] {
return _animationProperties.getFPS();
});
}
bool ModelEntityItem::isAnimatingSomething() const {
return resultWithReadLock<bool>([&] {
return _animationProperties.isValidAndRunning();
@ -722,6 +656,7 @@ bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProp
bool somethingChanged = newProperties != _animationProperties;
if (somethingChanged) {
_animationProperties = newProperties;
_needsRenderUpdate = true;
_flags |= Simulation::DIRTY_UPDATEABLE;
}
return somethingChanged;

View file

@ -76,33 +76,18 @@ public:
static const QString DEFAULT_COMPOUND_SHAPE_URL;
QString getCompoundShapeURL() const;
// Returns the URL used for the collision shape
QString getCollisionShapeURL() const;
// model related properties
virtual void setModelURL(const QString& url);
virtual void setCompoundShapeURL(const QString& url);
// Animation related items...
AnimationPropertyGroup getAnimationProperties() const;
// TODO: audit and remove unused Animation accessors
bool hasAnimation() const;
QString getAnimationURL() const;
virtual void setAnimationURL(const QString& url);
void setAnimationCurrentFrame(float value);
void setAnimationIsPlaying(bool value);
void setAnimationFPS(float value);
void setAnimationAllowTranslation(bool value);
float getAnimationCurrentFrame() const;
bool getAnimationAllowTranslation() const;
void setAnimationLoop(bool loop);
bool getAnimationLoop() const;
void setAnimationHold(bool hold);
bool getAnimationHold() const;
bool isAnimatingSomething() const;
void setRelayParentJoints(bool relayJoints);
bool getRelayParentJoints() const;
@ -110,11 +95,6 @@ public:
void setGroupCulled(bool value);
bool getGroupCulled() const;
bool getAnimationIsPlaying() const;
float getAnimationCurrentFrame() const;
float getAnimationFPS() const;
bool isAnimatingSomething() const;
static const QString DEFAULT_TEXTURES;
const QString getTextures() const;
void setTextures(const QString& textures);
@ -142,7 +122,6 @@ public:
private:
void setAnimationSettings(const QString& value); // only called for old bitstream format
bool applyNewAnimationProperties(AnimationPropertyGroup newProperties);
ShapeType computeTrueShapeType() const;
protected:
void resizeJointArrays(int newSize);

View file

@ -266,21 +266,21 @@ glm::u8vec3 PolyLineEntityItem::getColor() const {
void PolyLineEntityItem::setIsUVModeStretch(bool isUVModeStretch) {
withWriteLock([&] {
_needsRenderUpdate = _isUVModeStretch != isUVModeStretch;
_needsRenderUpdate |= _isUVModeStretch != isUVModeStretch;
_isUVModeStretch = isUVModeStretch;
});
}
void PolyLineEntityItem::setGlow(bool glow) {
withWriteLock([&] {
_needsRenderUpdate = _glow != glow;
_needsRenderUpdate |= _glow != glow;
_glow = glow;
});
}
void PolyLineEntityItem::setFaceCamera(bool faceCamera) {
withWriteLock([&] {
_needsRenderUpdate = _faceCamera != faceCamera;
_needsRenderUpdate |= _faceCamera != faceCamera;
_faceCamera = faceCamera;
});
}

View file

@ -442,4 +442,11 @@ PulsePropertyGroup ShapeEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}
void ShapeEntityItem::setUserData(const QString& value) {
withWriteLock([&] {
_needsRenderUpdate |= _userData != value;
_userData = value;
});
}

View file

@ -101,6 +101,8 @@ public:
PulsePropertyGroup getPulseProperties() const;
void setUserData(const QString& value) override;
protected:
glm::u8vec3 _color;
float _alpha { 1.0f };

View file

@ -84,12 +84,12 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie
// Contains a QString property, must be synchronized
withWriteLock([&] {
_keyLightPropertiesChanged = _keyLightProperties.setProperties(properties);
_ambientLightPropertiesChanged = _ambientLightProperties.setProperties(properties);
_skyboxPropertiesChanged = _skyboxProperties.setProperties(properties);
_keyLightPropertiesChanged |= _keyLightProperties.setProperties(properties);
_ambientLightPropertiesChanged |= _ambientLightProperties.setProperties(properties);
_skyboxPropertiesChanged |= _skyboxProperties.setProperties(properties);
});
_hazePropertiesChanged = _hazeProperties.setProperties(properties);
_bloomPropertiesChanged = _bloomProperties.setProperties(properties);
_hazePropertiesChanged |= _hazeProperties.setProperties(properties);
_bloomPropertiesChanged |= _bloomProperties.setProperties(properties);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed);
@ -125,7 +125,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, _keyLightPropertiesChanged);
});
somethingChanged = somethingChanged || _keyLightPropertiesChanged;
somethingChanged |= _keyLightPropertiesChanged;
bytesRead += bytesFromKeylight;
dataAt += bytesFromKeylight;
}
@ -136,7 +136,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
bytesFromAmbientlight = _ambientLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, _ambientLightPropertiesChanged);
});
somethingChanged = somethingChanged || _ambientLightPropertiesChanged;
somethingChanged |= _ambientLightPropertiesChanged;
bytesRead += bytesFromAmbientlight;
dataAt += bytesFromAmbientlight;
}
@ -147,7 +147,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, _skyboxPropertiesChanged);
});
somethingChanged = somethingChanged || _skyboxPropertiesChanged;
somethingChanged |= _skyboxPropertiesChanged;
bytesRead += bytesFromSkybox;
dataAt += bytesFromSkybox;
}
@ -155,7 +155,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
{
int bytesFromHaze = _hazeProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, _hazePropertiesChanged);
somethingChanged = somethingChanged || _hazePropertiesChanged;
somethingChanged |= _hazePropertiesChanged;
bytesRead += bytesFromHaze;
dataAt += bytesFromHaze;
}
@ -163,7 +163,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
{
int bytesFromBloom = _bloomProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, _bloomPropertiesChanged);
somethingChanged = somethingChanged || _bloomPropertiesChanged;
somethingChanged |= _bloomPropertiesChanged;
bytesRead += bytesFromBloom;
dataAt += bytesFromBloom;
}
@ -444,6 +444,13 @@ uint32_t ZoneEntityItem::getSkyboxMode() const {
return _skyboxMode;
}
void ZoneEntityItem::setUserData(const QString& value) {
withWriteLock([&] {
_needsRenderUpdate |= _userData != value;
_userData = value;
});
}
void ZoneEntityItem::fetchCollisionGeometryResource() {
QUrl hullURL(getCompoundShapeURL());
if (hullURL.isEmpty()) {

View file

@ -104,6 +104,8 @@ public:
uint32_t getScreenshare() const { return _screenshare; }
void setScreenshare(uint32_t value) { _screenshare = value; }
void setUserData(const QString& value) override;
bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; }

View file

@ -96,7 +96,7 @@ void gl::initModuleGl() {
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)getGlProcessAddress("wglCreateContextAttribsARB");
#endif
#if defined(Q_OS_LINUX)
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
QueryCurrentRendererIntegerMESA = (PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)getGlProcessAddress("glXQueryCurrentRendererIntegerMESA");
#endif
@ -134,7 +134,7 @@ void gl::setSwapInterval(int interval) {
}
bool gl::queryCurrentRendererIntegerMESA(int attr, unsigned int *value) {
#if defined(Q_OS_LINUX)
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (QueryCurrentRendererIntegerMESA) {
return QueryCurrentRendererIntegerMESA(attr, value);
}

View file

@ -35,11 +35,6 @@ class GLPaintEngine : public QPaintEngine {
};
GLWidget::GLWidget() {
#ifdef Q_OS_LINUX
// Cause GLWidget::eventFilter to be called.
// It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux.
qApp->installEventFilter(this);
#endif
setAttribute(Qt::WA_AcceptTouchEvents);
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
@ -118,39 +113,6 @@ bool GLWidget::event(QEvent* event) {
return QWidget::event(event);
}
// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the
// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to
// receive keyPress events for the Alt (and Meta) key in a reliable manner.
//
// This filter catches events before QMenuBar can steal the keyboard focus.
// The idea was borrowed from
// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html
bool GLWidget::eventFilter(QObject*, QEvent* event) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::ShortcutOverride:
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) {
if (event->type() == QEvent::KeyPress) {
keyPressEvent(keyEvent);
} else if (event->type() == QEvent::KeyRelease) {
keyReleaseEvent(keyEvent);
} else {
QWidget::event(event);
}
return true;
}
}
default:
break;
}
return false;
}
bool GLWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) {
#ifdef Q_OS_WIN32
MSG* win32message = static_cast<MSG*>(message);

View file

@ -42,9 +42,6 @@ protected:
virtual bool event(QEvent* event) override;
gl::Context* _context { nullptr };
private slots:
virtual bool eventFilter(QObject*, QEvent* event) override;
private:
QPaintEngine* _paintEngine { nullptr };
bool _vsyncSupported { false };

View file

@ -153,9 +153,11 @@ void GLBackend::init() {
if (vendor.contains("NVIDIA") ) {
qCDebug(gpugllogging) << "NVIDIA card detected";
#if !defined(Q_OS_ANDROID) && !defined(USE_GLES)
GL_GET_INTEGER(GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX);
GL_GET_INTEGER(GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX);
GL_GET_INTEGER(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
#endif
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: " << GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX;
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX: " << GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX;
@ -168,7 +170,9 @@ void GLBackend::init() {
} else if (vendor.contains("ATI")) {
qCDebug(gpugllogging) << "ATI card detected";
#if !defined(Q_OS_ANDROID) && !defined(USE_GLES)
GL_GET_INTEGER(TEXTURE_FREE_MEMORY_ATI);
#endif
_totalMemory = TEXTURE_FREE_MEMORY_ATI * BYTES_PER_KIB;
_dedicatedMemory = _totalMemory;
@ -215,15 +219,21 @@ void GLBackend::init() {
}
size_t GLBackend::getAvailableMemory() {
GLint mem;
// GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX returns 1 value
// GL_TEXTURE_FREE_MEMORY_ATI returns 4 values, we only need the first
GLint mem[4] = {0,0,0,0};
switch( _videoCard ) {
case NVIDIA:
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem);
return mem * BYTES_PER_KIB;
#if !defined(Q_OS_ANDROID) && !defined(USE_GLES)
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem[0]);
#endif
return mem[0] * BYTES_PER_KIB;
case ATI:
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem);
return mem * BYTES_PER_KIB;
#if !defined(Q_OS_ANDROID) && !defined(USE_GLES)
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem[0]);
#endif
return mem[0] * BYTES_PER_KIB;
case MESA:
return 0; // Don't know the current value
case Unknown:
@ -994,4 +1004,4 @@ void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRend
void GLBackend::syncProgram(const gpu::ShaderPointer& program) {
gpu::gl::GLShader::sync(*this, *program);
}
}

View file

@ -72,7 +72,7 @@ namespace NetworkingConstants {
const QUrl HELP_FORUM_URL { "https://forums.vircadia.dev" };
const QUrl HELP_SCRIPTING_REFERENCE_URL{ "https://apidocs.vircadia.dev/" };
const QUrl HELP_RELEASE_NOTES_URL{ "https://docs.vircadia.dev/release-notes.html" };
const QUrl HELP_BUG_REPORT_URL{ "https://github.com/kasenvr/project-athena/issues" };
const QUrl HELP_BUG_REPORT_URL{ "https://github.com/vircadia/vircadia/issues" };
const QString DEFAULT_VIRCADIA_ADDRESS = "file:///~/serverless/tutorial.json";
const QString DEFAULT_HOME_ADDRESS = "file:///~/serverless/tutorial.json";

View file

@ -31,9 +31,9 @@ static inline void for_each_eye(const std::function<void(ovrEye)>& f) {
f(VRAPI_EYE_RIGHT);
}
static inline void for_each_hand(const std::function<void(ovrHandedness)>& f) {
f(VRAPI_HAND_LEFT);
f(VRAPI_HAND_RIGHT);
static inline void for_each_hand(const std::function<void(ovrTrackedDeviceTypeId)>& f) {
f(VRAPI_TRACKED_DEVICE_HAND_LEFT);
f(VRAPI_TRACKED_DEVICE_HAND_RIGHT);
}
static inline glm::mat4 toGlm(const ovrMatrix4f& om) {

View file

@ -324,7 +324,7 @@ struct VrSurface : public TaskQueue {
vrapi_SetTrackingSpace( session, VRAPI_TRACKING_SPACE_LOCAL);
vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, gettid());
vrapi_SetClockLevels(session, 2, 4);
vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC);
vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_ON);
// Generates a warning on the quest: "vrapi_SetDisplayRefreshRate: Dynamic Display Refresh Rate not supported"
// vrapi_SetDisplayRefreshRate(session, 72);
});

View file

@ -31,7 +31,7 @@ const quint64 LOST_TRACKING_DELAY = 3000000;
namespace ovr {
controller::Pose toControllerPose(ovrHandedness hand, const ovrRigidBodyPosef& handPose) {
controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
@ -111,7 +111,7 @@ namespace ovr {
return pose;
}
controller::Pose toControllerPose(ovrHandedness hand,
controller::Pose toControllerPose(ovrTrackedDeviceTypeId hand,
const ovrRigidBodyPosef& handPose,
const ovrRigidBodyPosef& lastHandPose) {
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
@ -165,9 +165,9 @@ public:
private:
void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
ovrHandedness hand, const ovrRigidBodyPosef& handPose);
ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose);
void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
ovrHandedness hand, const ovrRigidBodyPosef& handPose);
ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose);
void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
const ovrRigidBodyPosef& headPose);
@ -379,9 +379,9 @@ void OculusMobileInputDevice::update(float deltaTime, const controller::InputCal
handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose);
static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED;
ovr::for_each_hand([&](ovrHandedness hand) {
size_t handIndex = (hand == VRAPI_HAND_LEFT) ? 0 : 1;
int controller = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND;
ovr::for_each_hand([&](ovrTrackedDeviceTypeId hand) {
size_t handIndex = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? 0 : 1;
int controller = (hand == VRAPI_TRACKED_DEVICE_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND;
auto& handData = _hands[handIndex];
const auto& tracking = handData.tracking;
++numTrackedControllers;
@ -476,7 +476,7 @@ void OculusMobileInputDevice::focusOutEvent() {
void OculusMobileInputDevice::handlePose(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData,
ovrHandedness hand, const ovrRigidBodyPosef& handPose) {
ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) {
auto poseId = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND;
auto& pose = _poseStateMap[poseId];
pose = ovr::toControllerPose(hand, handPose);
@ -507,7 +507,7 @@ void OculusMobileInputDevice::handleHeadPose(float deltaTime,
}
void OculusMobileInputDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
ovrHandedness hand, const ovrRigidBodyPosef& handPose) {
ovrTrackedDeviceTypeId hand, const ovrRigidBodyPosef& handPose) {
auto poseId = (hand == VRAPI_HAND_LEFT ? controller::LEFT_HAND : controller::RIGHT_HAND);
auto& pose = _poseStateMap[poseId];
const auto& lastHandPose = (hand == VRAPI_HAND_LEFT) ? _hands[0].lastPose : _hands[1].lastPose;

View file

@ -425,12 +425,12 @@ void PhysicalEntitySimulation::buildPhysicsTransaction(PhysicsEngine::Transactio
continue;
}
bool needsNewShape = object->needsNewShape();
bool needsNewShape = object->needsNewShape() && object->_entity->isReadyToComputeShape();
if (needsNewShape) {
ShapeType shapeType = object->getShapeType();
if (shapeType == SHAPE_TYPE_STATIC_MESH) {
ShapeRequest shapeRequest(object->_entity);
ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest);
ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest);
if (requestItr == _shapeRequests.end()) {
ShapeInfo shapeInfo;
object->_entity->computeShapeInfo(shapeInfo);

View file

@ -338,7 +338,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
bool intersectedSomething = false;
// if we aren't active, we can't pick yet...
if (!isActive()) {
if (!isLoaded()) {
return intersectedSomething;
}
@ -493,7 +493,7 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
bool intersectedSomething = false;
// if we aren't active, we can't pick yet...
if (!isActive()) {
if (!isLoaded()) {
return intersectedSomething;
}
@ -958,16 +958,42 @@ void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) {
}
}
void Model::setPrimitiveMode(PrimitiveMode primitiveMode) {
void Model::setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene) {
if (_primitiveMode != primitiveMode) {
_primitiveMode = primitiveMode;
updateRenderItemsKey(nullptr);
if (!scene) {
_needsFixupInScene = true;
return;
}
bool useDualQuaternionSkinning = _useDualQuaternionSkinning;
std::unordered_map<int, bool> shouldInvalidatePayloadShapeKeyMap;
for (auto& shape : _modelMeshRenderItemShapes) {
shouldInvalidatePayloadShapeKeyMap[shape.meshIndex] = shouldInvalidatePayloadShapeKey(shape.meshIndex);
}
render::Transaction transaction;
for (int i = 0; i < (int)_modelMeshRenderItemIDs.size(); i++) {
auto itemID = _modelMeshRenderItemIDs[i];
auto meshIndex = _modelMeshRenderItemShapes[i].meshIndex;
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex);
transaction.updateItem<ModelMeshPartPayload>(itemID, [invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning] (ModelMeshPartPayload& data) {
data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning);
});
}
scene->enqueueTransaction(transaction);
}
}
void Model::setCullWithParent(bool cullWithParent) {
void Model::setCullWithParent(bool cullWithParent, const render::ScenePointer& scene) {
if (_cullWithParent != cullWithParent) {
_cullWithParent = cullWithParent;
if (!scene) {
_needsFixupInScene = true;
return;
}
render::Transaction transaction;
auto renderItemsKey = _renderItemKeyGlobalFlags;
@ -977,14 +1003,27 @@ void Model::setCullWithParent(bool cullWithParent) {
data.updateKey(renderItemsKey);
});
}
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
scene->enqueueTransaction(transaction);
}
}
void Model::setRenderWithZones(const QVector<QUuid>& renderWithZones) {
void Model::setRenderWithZones(const QVector<QUuid>& renderWithZones, const render::ScenePointer& scene) {
if (_renderWithZones != renderWithZones) {
_renderWithZones = renderWithZones;
setRenderItemsNeedUpdate();
if (!scene) {
_needsFixupInScene = true;
return;
}
render::Transaction transaction;
auto renderItemsKey = _renderItemKeyGlobalFlags;
for (auto item : _modelMeshRenderItemIDs) {
transaction.updateItem<ModelMeshPartPayload>(item, [renderWithZones, renderItemsKey](ModelMeshPartPayload& data) {
data.setRenderWithZones(renderWithZones);
});
}
scene->enqueueTransaction(transaction);
}
}
@ -1129,7 +1168,7 @@ void Model::renderDebugMeshBoxes(gpu::Batch& batch, bool forward) {
}
Extents Model::getBindExtents() const {
if (!isActive()) {
if (!isLoaded()) {
return Extents();
}
const Extents& bindExtents = getHFMModel().bindExtents;
@ -1143,7 +1182,7 @@ glm::vec3 Model::getNaturalDimensions() const {
}
Extents Model::getMeshExtents() const {
if (!isActive()) {
if (!isLoaded()) {
return Extents();
}
const Extents& extents = getHFMModel().meshExtents;
@ -1157,7 +1196,7 @@ Extents Model::getMeshExtents() const {
}
Extents Model::getUnscaledMeshExtents() const {
if (!isActive()) {
if (!isLoaded()) {
return Extents();
}
@ -1189,7 +1228,7 @@ void Model::setJointTranslation(int index, bool valid, const glm::vec3& translat
}
int Model::getParentJointIndex(int jointIndex) const {
return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).parentIndex : -1;
return (isLoaded() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).parentIndex : -1;
}
void Model::setTextures(const QVariantMap& textures) {
@ -1290,7 +1329,7 @@ QStringList Model::getJointNames() const {
Q_RETURN_ARG(QStringList, result));
return result;
}
return isActive() ? getHFMModel().getJointNames() : QStringList();
return isLoaded() ? getHFMModel().getJointNames() : QStringList();
}
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) {
@ -1306,7 +1345,7 @@ void Model::setScaleToFit(bool scaleToFit, float largestDimension, bool forceRes
// mesh, and so we can't do the needed calculations for scaling to fit to a single largest dimension. In this
// case we will record that we do want to do this, but we will stick our desired single dimension into the
// first element of the vec3 for the non-fixed aspect ration dimensions
if (!isActive()) {
if (!isLoaded()) {
_scaleToFit = scaleToFit;
if (scaleToFit) {
_scaleToFitDimensions = glm::vec3(largestDimension, FAKE_DIMENSION_PLACEHOLDER, FAKE_DIMENSION_PLACEHOLDER);
@ -1387,7 +1426,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit)
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
if (isActive() && fullUpdate) {
if (isLoaded() && fullUpdate) {
onInvalidate();
// check for scale to fit
@ -1454,7 +1493,6 @@ void Model::updateBlendshapes() {
}
void Model::deleteGeometry() {
_deleteGeometryCounter++;
_meshStates.clear();
_rig.destroyAnimGraph();
_blendedBlendshapeCoefficients.clear();

View file

@ -116,14 +116,14 @@ public:
void setHifiRenderLayer(render::hifi::Layer layer, const render::ScenePointer& scene = nullptr);
bool isCauterized() const { return _cauterized; }
void setCauterized(bool value, const render::ScenePointer& scene);
void setCauterized(bool value, const render::ScenePointer& scene = nullptr);
void setPrimitiveMode(PrimitiveMode primitiveMode);
void setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene = nullptr);
PrimitiveMode getPrimitiveMode() const { return _primitiveMode; }
void setCullWithParent(bool value);
void setCullWithParent(bool value, const render::ScenePointer& scene = nullptr);
void setRenderWithZones(const QVector<QUuid>& renderWithZones);
void setRenderWithZones(const QVector<QUuid>& renderWithZones, const render::ScenePointer& scene = nullptr);
const QVector<QUuid>& getRenderWithZones() const { return _renderWithZones; }
// Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model.
@ -183,10 +183,7 @@ public:
const HFMModel& getHFMModel() const { assert(isLoaded()); return _renderGeometry->getHFMModel(); }
const MaterialMapping& getMaterialMapping() const { assert(isLoaded()); return _renderGeometry->getMaterialMapping(); }
bool isActive() const { return isLoaded(); }
bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; }
bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; }
glm::mat4 getWorldToHFMMatrix() const;
@ -338,7 +335,6 @@ public:
const MeshState& getMeshState(int index) { return _meshStates.at(index); }
uint32_t getGeometryCounter() const { return _deleteGeometryCounter; }
const QMap<render::ItemID, render::PayloadPointer>& getRenderItems() const { return _modelMeshRenderItemsMap; }
BlendShapeOperator getModelBlendshapeOperator() const { return _modelBlendshapeOperator; }
@ -474,10 +470,7 @@ protected:
friend class ModelMeshPartPayload;
Rig _rig;
uint32_t _deleteGeometryCounter { 0 };
bool _visualGeometryRequestFailed { false };
bool _collisionGeometryRequestFailed { false };
bool _renderItemsNeedUpdate { false };

View file

@ -188,7 +188,7 @@ public slots:
* @param {number} roll - The roll angle in degrees.
* @returns {Quat} A quaternion created using the <code>pitch</code>, <code>yaw</code>, and <code>roll</code> Euler angles.
* @example <caption>Create a rotation of 180 degrees about the y axis.</caption>
* var rotation = Quat.fromPitchYawRollDgrees(0, 180, 0 );
* var rotation = Quat.fromPitchYawRollDegrees(0, 180, 0 );
*/
glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll);

View file

@ -451,7 +451,7 @@ void ScriptEngine::executeOnScriptThread(std::function<void()> function, const Q
function();
}
void ScriptEngine::waitTillDoneRunning() {
void ScriptEngine::waitTillDoneRunning(bool shutdown) {
// Engine should be stopped already, but be defensive
stop();
@ -520,12 +520,14 @@ void ScriptEngine::waitTillDoneRunning() {
}
}
// NOTE: This will be called on the main application thread (among other threads) from stopAllScripts.
// The thread will need to continue to process events, because
// the scripts will likely need to marshall messages across to the main thread, e.g.
// if they access Settings or Menu in any of their shutdown code. So:
// Process events for this thread, allowing invokeMethod calls to pass between threads.
QCoreApplication::processEvents();
if (shutdown) {
// NOTE: This will be called on the main application thread (among other threads) from stopAllScripts.
// The thread will need to continue to process events, because
// the scripts will likely need to marshall messages across to the main thread, e.g.
// if they access Settings or Menu in any of their shutdown code. So:
// Process events for this thread, allowing invokeMethod calls to pass between threads.
QCoreApplication::processEvents();
}
// Avoid a pure busy wait
QThread::yieldCurrentThread();
@ -1941,9 +1943,12 @@ QScriptValue ScriptEngine::require(const QString& moduleId) {
// modules get cached in `Script.require.cache` and (similar to Node.js) users can access it
// to inspect particular entries and invalidate them by deleting the key:
// `delete Script.require.cache[Script.require.resolve(moduleId)];`
// Check to see if we should invalidate the cache based on a user setting.
Setting::Handle<bool> getCachebustSetting {"cachebustScriptRequire", false };
// cacheMeta is just used right now to tell deleted keys apart from undefined ones
bool invalidateCache = module.isUndefined() && cacheMeta.property(moduleId).isValid();
bool invalidateCache = getCachebustSetting.get() || (module.isUndefined() && cacheMeta.property(moduleId).isValid());
// reset the cacheMeta record so invalidation won't apply next time, even if the module fails to load
cacheMeta.setProperty(modulePath, QScriptValue());

View file

@ -4,6 +4,7 @@
//
// Created by Brad Hefta-Gaub on 12/14/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -196,7 +197,7 @@ public:
Q_INVOKABLE void stop(bool marshal = false);
// Stop any evaluating scripts and wait for the scripting thread to finish.
void waitTillDoneRunning();
void waitTillDoneRunning(bool shutdown = false);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can

View file

@ -182,7 +182,7 @@ void ScriptEngines::shutdownScripting() {
// want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
// any application state after we leave this stopAllScripts() method
qCDebug(scriptengine) << "waiting on script:" << scriptName;
scriptEngine->waitTillDoneRunning();
scriptEngine->waitTillDoneRunning(true);
qCDebug(scriptengine) << "done waiting on script:" << scriptName;
}
// Once the script is stopped, we can remove it from our set

View file

@ -1,5 +1,5 @@
Collection of scripts to create server distribution packages. Most of these scripts assume
use of the build script at https://github.com/kasenvr/vircadia-builder, specifically that
use of the build script at https://github.com/vircadia/vircadia-builder, specifically that
the following directory structure exists
base folder/

View file

@ -5,8 +5,8 @@ Maintainer: Heather Anderson <heath@odysseus.anderson.name>
Build-Depends: debhelper (>= 10)
Standards-Version: 4.1.2
Homepage: https://vircadia.com
Vcs-Git: https://github.com/kasenvr/project-athena.git
Vcs-Browser: https://github.com/kasenvr/project-athena
Vcs-Git: https://github.com/vircadia/vircadia.git
Vcs-Browser: https://github.com/vircadia/vircadia
Package: vircadia-server
Architecture: any

View file

@ -9,7 +9,7 @@ Summary: Vircadia metaverse platform, based on the High Fidelity Engine.
License: ASL 2.0
URL: https://vircadia.com
Source0: https://github.com/kasenvr/vircadia-builder/blob/master/vircadia-builder
Source0: https://github.com/vircadia/vircadia-builder/blob/master/vircadia-builder
#BuildRequires: systemd-rpm-macros
BuildRequires: chrpath

View file

@ -53,5 +53,5 @@ set(DIR "opusCodec")
add_subdirectory(${DIR})
# example plugins
set(DIR "KasenAPIExample")
set(DIR "JSAPIExample")
add_subdirectory(${DIR})

View file

@ -0,0 +1,3 @@
set(TARGET_NAME JSAPIExample)
setup_hifi_client_server_plugin(scripting)
link_hifi_libraries(shared plugins)

View file

@ -0,0 +1,251 @@
//
// JSAPIExample.cpp
// plugins/JSAPIExample/src
//
// Copyright (c) 2019-2020 humbletim (humbletim@gmail.com)
// Copyright (c) 2019 Kalila L. (somnilibertas@gmail.com)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// Example of prototyping new JS APIs by leveraging the existing plugin system.
#include <QCoreApplication>
#include <QtCore/QByteArray>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QJsonObject>
#include <QtCore/QLoggingCategory>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptable>
#include <SettingHelpers.h> // for ::settingsFilename()
#include <SharedUtil.h> // for ::usecTimestampNow()
#include <shared/ScriptInitializerMixin.h>
// NOTE: replace this with your own namespace when starting a new plugin (to avoid .so/.dll symbol clashes)
namespace REPLACE_ME_WITH_UNIQUE_NAME {
static constexpr auto JSAPI_SEMANTIC_VERSION = "0.0.1";
static constexpr auto JSAPI_EXPORT_NAME = "JSAPIExample";
QLoggingCategory logger { "jsapiexample" };
inline QVariant raiseScriptingError(QScriptContext* context, const QString& message, const QVariant& returnValue = QVariant()) {
if (context) {
// when a QScriptContext is available throw an actual JS Exception (which can be caught using try/catch on JS side)
context->throwError(message);
} else {
// otherwise just log the error
qCWarning(logger) << "error:" << message;
}
return returnValue;
}
QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error);
class JSAPIExample : public QObject, public QScriptable {
Q_OBJECT
Q_PLUGIN_METADATA(IID "JSAPIExample" FILE "plugin.json")
Q_PROPERTY(QString version MEMBER _version CONSTANT)
public:
JSAPIExample() {
setObjectName(JSAPI_EXPORT_NAME);
auto scriptInit = DependencyManager::get<ScriptInitializers>();
if (!scriptInit) {
qCWarning(logger) << "COULD NOT INITIALIZE (ScriptInitializers unavailable)" << qApp << this;
return;
}
qCWarning(logger) << "registering w/ScriptInitializerMixin..." << scriptInit.data();
scriptInit->registerScriptInitializer([this](QScriptEngine* engine) {
auto value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
engine->globalObject().setProperty(objectName(), value);
// qCDebug(logger) << "setGlobalInstance" << objectName() << engine->property("fileName");
});
// qCInfo(logger) << "plugin loaded" << qApp << toString() << QThread::currentThread();
}
// NOTES: everything within the "public slots:" section below will be available from JS via overall plugin QObject
// also, to demonstrate future-proofing JS API code, QVariant's are used throughout most of these examples --
// which still makes them very Qt-specific, but avoids depending directly on deprecated QtScript/QScriptValue APIs.
// (as such this plugin class and its methods remain forward-compatible with other engines like QML's QJSEngine)
public slots:
// pretty-printed representation for logging eg: print(JSAPIExample)
// (note: Qt script engines automatically look for a ".toString" method on native classes when coercing values to strings)
QString toString() const { return QString("[%1 version=%2]").arg(objectName()).arg(_version); }
/**jsdoc
* Returns current microseconds (usecs) since Epoch. note: 1000usecs == 1ms
* @example <caption>Measure current setTimeout accuracy.</caption>
* var expected = 1000;
* var start = JSAPIExample.now();
* Script.setTimeout(function () {
* var elapsed = (JSAPIExample.now() - start)/1000;
* print("expected (ms):", expected, "actual (ms):", elapsed);
* }, expected);
*/
QVariant now() const { return usecTimestampNow(); }
/**jsdoc
* Example of returning a JS Object key-value map
* @example <caption>"zip" a list of keys and corresponding values to form key-value map</caption>
* print(JSON.stringify(JSAPIExample.zip(["a","b"], [1,2])); // { "a": 1, "b": 2 }
*/
QVariant zip(const QStringList& keys, const QVariantList& values) const {
QVariantMap out;
for (int i = 0; i < keys.size(); i++) {
out[keys[i]] = i < values.size() ? values[i] : QVariant();
}
return out;
}
/**jsdoc
* Example of returning a JS Array result
* @example <caption>emulate Object.values(keyValues)</caption>
* print(JSON.stringify(JSAPIExample.values({ "a": 1, "b": 2 }))); // [1,2]
*/
QVariant values(const QVariantMap& keyValues) const {
QVariantList values = keyValues.values();
return values;
}
/**jsdoc
* Another example of returning JS Array data
* @example <caption>generate an integer sequence (inclusive of [from, to])</caption>
* print(JSON.stringify(JSAPIExample.seq(1,5)));// [1,2,3,4,5]
*/
QVariant seq(int from, int to) const {
QVariantList out;
for (int i = from; i <= to; i++) {
out.append(i);
}
return out;
}
/**jsdoc
* Example of returning arbitrary binary data from C++ (resulting in a JS ArrayBuffer)
* see also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer#Examples
* @example <caption>return compressed/decompressed versions of the input data</caption>
* var data = "testing 1 2 3";
* var z = JSAPIExample.qCompressString(data); // z will be an ArrayBuffer
* var u = JSAPIExample.qUncompressString(z); // u will be a String value
* print(JSON.stringify({ input: data, compressed: z.byteLength, output: u, uncompressed: u.length }));
*/
QVariant qCompressString(const QString& jsString, int compress_level = -1) const {
QByteArray arrayBuffer = qCompress(jsString.toUtf8(), compress_level);
return arrayBuffer;
}
QVariant qUncompressString(const QByteArray& arrayBuffer) const {
QString jsString = QString::fromUtf8(qUncompress(arrayBuffer));
return jsString;
}
/**
* Example of exposing a custom "managed" C++ QObject to JS
* The lifecycle of the created QObject* instance becomes managed by the invoking QScriptEngine --
* it will be automatically cleaned up once no longer reachable from any JS variables/closures.
* @example <caption>access persistent settings stored in separate .json files</caption>
* var settings = JSAPIExample.getScopedSettings("example");
* print("example settings stored in:", settings.fileName());
* print("(before) example::timestamp", settings.getValue("timestamp"));
* settings.setValue("timestamp", Date.now());
* print("(after) example::timestamp", settings.getValue("timestamp"));
* print("all example::* keys", settings.allKeys());
* settings = null; // optional best pratice; allows the object to be reclaimed ASAP by the JS garbage collector
*/
QScriptValue getScopedSettings(const QString& scope) {
auto engine = QScriptable::engine();
if (!engine) {
return QScriptValue::NullValue;
}
QString error;
auto cppValue = createScopedSettings(scope, engine, error);
if (!cppValue) {
raiseScriptingError(context(), "error creating scoped settings instance: " + error);
return QScriptValue::NullValue;
}
return engine->newQObject(cppValue, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeDeleteLater);
}
private:
const QString _version { JSAPI_SEMANTIC_VERSION };
};
// JSSettingsHelper wraps a scoped (prefixed/separate) QSettings and exposes a subset of QSetting APIs as slots
class JSSettingsHelper : public QObject {
Q_OBJECT
public:
JSSettingsHelper(const QString& scope, QObject* parent = nullptr);
~JSSettingsHelper();
operator bool() const;
public slots:
QString fileName() const;
QString toString() const;
QVariant getValue(const QString& key, const QVariant& defaultValue = QVariant());
bool setValue(const QString& key, const QVariant& value);
QStringList allKeys() const;
protected:
QString _scope;
QString _fileName;
QSharedPointer<QSettings> _settings;
QString getLocalSettingsPath(const QString& scope) const;
};
// verifies the requested scope is sensible and creates/returns a scoped JSSettingsHelper instance
QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error) {
const QRegExp VALID_SETTINGS_SCOPE { "[-_A-Za-z0-9]{1,64}" };
if (!VALID_SETTINGS_SCOPE.exactMatch(scope)) {
error = QString("invalid scope (expected alphanumeric <= 64 chars not '%1')").arg(scope);
return nullptr;
}
return new JSSettingsHelper(scope, parent);
}
// --------------------------------------------------
// ----- inline JSSettingsHelper implementation -----
JSSettingsHelper::operator bool() const {
return (bool)_settings;
}
JSSettingsHelper::JSSettingsHelper(const QString& scope, QObject* parent) :
QObject(parent), _scope(scope), _fileName(getLocalSettingsPath(scope)),
_settings(_fileName.isEmpty() ? nullptr : new QSettings(_fileName, JSON_FORMAT)) {
}
JSSettingsHelper::~JSSettingsHelper() {
qCDebug(logger) << "~JSSettingsHelper" << _scope << _fileName << this;
}
QString JSSettingsHelper::fileName() const {
return _settings ? _settings->fileName() : "";
}
QString JSSettingsHelper::toString() const {
return QString("[JSSettingsHelper scope=%1 valid=%2]").arg(_scope).arg((bool)_settings);
}
QVariant JSSettingsHelper::getValue(const QString& key, const QVariant& defaultValue) {
return _settings ? _settings->value(key, defaultValue) : defaultValue;
}
bool JSSettingsHelper::setValue(const QString& key, const QVariant& value) {
if (_settings) {
if (value.isValid()) {
_settings->setValue(key, value);
} else {
_settings->remove(key);
}
return true;
}
return false;
}
QStringList JSSettingsHelper::allKeys() const {
return _settings ? _settings->allKeys() : QStringList{};
}
QString JSSettingsHelper::getLocalSettingsPath(const QString& scope) const {
// generate a prefixed filename (relative to the main application's Interface.json file)
const QString fileName = QString("jsapi_%1.json").arg(scope);
return QFileInfo(::settingsFilename()).dir().filePath(fileName);
}
// ----- /inline JSSettingsHelper implementation -----
} // namespace REPLACE_ME_WITH_UNIQUE_NAME
#include "JSAPIExample.moc"

View file

@ -0,0 +1,4 @@
{
"name":"JS API Example",
"version": 1
}

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