Merge branch 'master' into billboard

This commit is contained in:
HifiExperiments 2021-02-09 22:57:10 -08:00 committed by GitHub
commit ae26416c59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
139 changed files with 5577 additions and 1790 deletions

View file

@ -83,15 +83,15 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "${{ steps.buildenv1.outputs.symbols_archive }}" echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo "ARTIFACT_PATTERN=Vircadia-Alpha-*.$INSTALLER_EXT" >> $GITHUB_ENV echo "ARTIFACT_PATTERN=Vircadia-*.$INSTALLER_EXT" >> $GITHUB_ENV
# Build type variables # Build type variables
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
if [ "${{ matrix.build_type }}" = "full" ]; then if [ "${{ matrix.build_type }}" = "full" ]; then
echo "CLIENT_ONLY=FALSE" >> $GITHUB_ENV echo "CLIENT_ONLY=FALSE" >> $GITHUB_ENV
echo "INSTALLER=Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV echo "INSTALLER=Vircadia-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
else else
echo "CLIENT_ONLY=TRUE" >> $GITHUB_ENV echo "CLIENT_ONLY=TRUE" >> $GITHUB_ENV
echo "INSTALLER=Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV echo "INSTALLER=Vircadia-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
fi fi
- name: Clear working directory - name: Clear working directory
if: startsWith(matrix.os, 'windows') if: startsWith(matrix.os, 'windows')

View file

@ -86,9 +86,9 @@ jobs:
echo "${{ steps.buildenv1.outputs.symbols_archive }}" echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
if [[ "${{ matrix.build_type }}" != "android" ]]; then if [[ "${{ matrix.build_type }}" != "android" ]]; then
echo "ARTIFACT_PATTERN=Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV echo "ARTIFACT_PATTERN=Vircadia-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV
# Build type variables # Build type variables
echo "INSTALLER=Vircadia-Alpha-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV echo "INSTALLER=Vircadia-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
else else
echo "ARTIFACT_PATTERN=*.$INSTALLER_EXT" >> $GITHUB_ENV echo "ARTIFACT_PATTERN=*.$INSTALLER_EXT" >> $GITHUB_ENV
fi fi

View file

@ -59,9 +59,9 @@ You do not need to install vcpkg.
Building the dependencies can be lengthy and the resulting files will be stored in your OS temp directory. Building the dependencies can be lengthy and the resulting files will be stored in your OS temp directory.
However, those files can potentially get cleaned up by the OS, so in order to avoid this and having to redo the lengthy build step, you can set the following environment variable: However, those files can potentially get cleaned up by the OS, so in order to avoid this and having to redo the lengthy build step, you can set the following environment variable:
export HIFI_VCPKG_BASE=/path/to/directory export HIFI_VCPKG_BASE=/path/to/directory
Where /path/to/directory is the path to a directory where you wish the build files to get stored. Where `/path/to/directory` is the path to a directory where you wish the build files to get stored.
#### Generating Build Files #### Generating Build Files

View file

@ -7,38 +7,41 @@ This is a stand-alone guide for creating your first Vircadia build for Windows 6
Note: We are now using Visual Studio 2017 or 2019 and Qt 5.12.3. Note: We are now using Visual Studio 2017 or 2019 and Qt 5.12.3.
If you are upgrading from previous versions, do a clean uninstall of those versions before going through this guide. If you are upgrading from previous versions, do a clean uninstall of those versions before going through this guide.
Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. **Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.**
### Step 1. Visual Studio & Python 3.x ### Step 1. Visual Studio & Python 3.x
If you dont have Community or Professional edition of Visual Studio, download [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/). If you have Visual Studio 2017, you are not required to download Visual Studio 2019. If you dont have Community or Professional edition of Visual Studio, download [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/). If you have Visual Studio 2017, you are not required to download Visual Studio 2019.
When selecting components, check "Desktop development with C++". On the right on the Summary toolbar, select the following components. When selecting components, check "Desktop development with C++".
#### If you're installing Visual Studio 2017, If you do not already have a Python 3.x development environment installed and want to install it with Visual Studio, check "Python Development". If you already have Visual Studio installed and need to add Python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes.
On the right on the Summary toolbar, select the following components based on your Visual Studio version.
#### If you're installing Visual Studio 2017
* Windows 8.1 SDK and UCRT SDK * Windows 8.1 SDK and UCRT SDK
* VC++ 2015.3 v14.00 (v140) toolset for desktop * VC++ 2015.3 v14.00 (v140) toolset for desktop
#### If you're installing Visual Studio 2019, #### If you're installing Visual Studio 2019
* MSVC v142 - VS 2019 C++ X64/x86 build tools
* MSVC v141 - VS 2017 C++ x64/x86 build tools * MSVC v141 - VS 2017 C++ x64/x86 build tools
* MSVC v140 - VS 2015 C++ build tools (v14.00) * MSVC v140 - VS 2015 C++ build tools (v14.00)
If you do not already have a Python 3.x development environment installed, also check "Python Development" in this screen. ### Step 1a. Alternate Python
If you already have Visual Studio installed and need to add Python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes. If you do not wish to use the Python installation bundled with Visual Studio, you can download the installer from [here](https://www.python.org/downloads/). Ensure that you get version 3.6.6 or higher.
### Step 1a. Alternate Python
If you do not wish to use the Python installation bundled with Visual Studio, you can download the installer from [here](https://www.python.org/downloads/). Ensure you get version 3.6.6 or higher.
### Step 2. Python Dependencies ### Step 2. Python Dependencies
In a command-line that can access Python's pip you will need to run the following command: In an administrator command-line that can access Python's pip you will need to run the following command:
`pip install distro` `pip install distro`
If you do not use an administrator command-line, you will get errors.
### Step 3. Installing CMake ### Step 3. Installing CMake
Download and install the latest version of CMake 3.15. Download and install the latest version of CMake 3.15.
@ -46,7 +49,11 @@ Download and install the latest version of CMake 3.15.
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.15 Version page](https://cmake.org/files/v3.15/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.15 Version page](https://cmake.org/files/v3.15/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
### Step 4. Create VCPKG environment variable ### Step 4. Node.JS and NPM
Install version 10.15.0 LTS (or greater) of [Node.JS and NPM](<https://nodejs.org/en/download/>).
### Step 5. Create VCPKG environment variable
In the next step, you will use CMake to build Vircadia. By default, the CMake process builds dependency files in Windows' `%TEMP%` directory, which is periodically cleared by the operating system. To prevent you from having to re-build the dependencies in the event that Windows clears that directory, we recommend that you create a `HIFI_VCPKG_BASE` environment variable linked to a directory somewhere on your machine. That directory will contain all dependency files until you manually remove them. In the next step, you will use CMake to build Vircadia. By default, the CMake process builds dependency files in Windows' `%TEMP%` directory, which is periodically cleared by the operating system. To prevent you from having to re-build the dependencies in the event that Windows clears that directory, we recommend that you create a `HIFI_VCPKG_BASE` environment variable linked to a directory somewhere on your machine. That directory will contain all dependency files until you manually remove them.
To create this variable: To create this variable:
@ -65,7 +72,7 @@ To create this variable:
* Set "Variable name" to `HIFI_VCPKG_BOOTSTRAP` * Set "Variable name" to `HIFI_VCPKG_BOOTSTRAP`
* Set "Variable value" to `1` * Set "Variable value" to `1`
### Step 5. Running CMake to Generate Build Files ### Step 6. Running CMake to Generate Build Files
Run Command Prompt from Start and run the following commands: Run Command Prompt from Start and run the following commands:
`cd "%VIRCADIA_DIR%"` `cd "%VIRCADIA_DIR%"`
@ -80,7 +87,7 @@ Run `cmake .. -G "Visual Studio 16 2019" -A x64`.
Where `%VIRCADIA_DIR%` is the directory for the Vircadia repository. Where `%VIRCADIA_DIR%` is the directory for the Vircadia repository.
### Step 6. Making a Build ### Step 7. Making a Build
Open `%VIRCADIA_DIR%\build\vircadia.sln` using Visual Studio. Open `%VIRCADIA_DIR%\build\vircadia.sln` using Visual Studio.
@ -88,7 +95,7 @@ Change the Solution Configuration (menu ribbon under the menu bar, next to the g
Run from the menu bar `Build > Build Solution`. Run from the menu bar `Build > Build Solution`.
### Step 7. Testing Interface ### Step 8. Testing Interface
Create another environment variable (see Step #3) Create another environment variable (see Step #3)
* Set "Variable name": `_NO_DEBUG_HEAP` * Set "Variable name": `_NO_DEBUG_HEAP`
@ -104,11 +111,11 @@ Note: You can also run Interface by launching it from command line or File Explo
## Troubleshooting ## Troubleshooting
For any problems after Step #6, first try this: For any problems after Step #7, first try this:
* Delete your locally cloned copy of the Vircadia repository * Delete your locally cloned copy of the Vircadia repository
* Restart your computer * Restart your computer
* Redownload the [repository](https://github.com/vircadia/vircadia) * Redownload the [repository](https://github.com/vircadia/vircadia)
* Restart directions from Step #6 * Restart directions from Step #7
#### CMake gives you the same error message repeatedly after the build fails #### CMake gives you the same error message repeatedly after the build fails

View file

@ -60,8 +60,8 @@ To produce an executable installer on Windows, the following are required:
1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` 1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` 1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [Node.JS and NPM](<https://www.npmjs.com/get-npm>) 1. [Node.JS and NPM](<https://nodejs.org/en/download/>)
1. Install version 10.15.0 LTS 1. Install version 10.15.0 LTS (or greater)
##### Code Signing (optional) ##### Code Signing (optional)

View file

@ -1,5 +1,5 @@
Copyright (c) 2013-2019, High Fidelity, Inc. Copyright (c) 2013-2019, High Fidelity, Inc.
Copyright (c) 2019-2020, Vircadia contributors. Copyright (c) 2019-2021, Vircadia contributors.
All rights reserved. All rights reserved.
https://vircadia.com https://vircadia.com

View file

@ -1,10 +1,10 @@
# Vircadia # Vircadia (Codename Athena)
### What is this? ### What is this?
Vircadia is a 3D social software project seeking to incrementally bring about a truly free and open metaverse, in desktop and XR. Vircadia is a 3D social software project seeking to incrementally bring about a truly free and open metaverse, in desktop and XR.
### [Download](https://vircadia.com/download-vircadia/) ### [Website](https://vircadia.com/) | [Discord](https://discordapp.com/invite/Pvx2vke) | [Download](https://vircadia.com/download-vircadia/)
### Releases ### Releases
@ -12,50 +12,46 @@ Vircadia is a 3D social software project seeking to incrementally bring about a
### How to build the Interface ### How to build the Interface
[For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md) - [For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md)
- [For Mac](https://github.com/vircadia/vircadia/blob/master/BUILD_OSX.md)
[For Mac](https://github.com/vircadia/vircadia/blob/master/BUILD_OSX.md) - [For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md)
- [For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder)
[For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md)
[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder)
### How to deploy a Server ### How to deploy a Server
[For Windows and Linux](https://vircadia.com/deploy-a-server/) - [For Windows and Linux](https://vircadia.com/deploy-a-server/)
### How to build a Server ### How to build a Server
[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder) - [For Windows](https://github.com/vircadia/vircadia/blob/master/BUILD_WIN.md)
- [For Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md)
- [For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder)
### How to generate an Installer ### How to generate an Installer
[For Windows](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md) - [For Windows](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md)
- [For Mac](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md#os-x)
- [For Linux - AppImage - Vircadia Builder](https://github.com/vircadia/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](https://vircadia.com/vision/)
### Boot to Metaverse: The Goal
Having a place to experience adventure, a place to relax with calm breath, that's a world to live in. An engine to support infinite combinations and possibilities of worlds without censorship and interruption, that's a metaverse. Finding a way to make infinite realities our reality is the dream. Having a place to experience adventure, a place to relax with calm breath, that's a world to live in. An engine to support infinite combinations and possibilities of worlds without censorship and interruption, that's a metaverse. Finding a way to make infinite realities our reality is the dream.
### Boot to Metaverse: The Technicals ### Boot to Metaverse: The Technicals
Many developers have had personal combinations of High Fidelity from C++ modifications to different default scripts, all of which are lost to time as their fullest potential is never truly shared and propagated through the system. Vircadia consists of many projects and codebases with its unifying structure's goal being a decentralized metaverse.
The goal of this project is to achieve the metaverse dream through shared contribution and building. Setting goals that are achievable yet meaningful is key to making proper forward progress on the technical front whilst maintaining morale. - The Interface (Codename Athena) - You are here!
- The Server (Codename Athena) - You are also here!
- The UI Framework (Codename Nyx) - Codebase coming soon.
- [The Metaverse (Codename Iamus)](https://github.com/vircadia/Iamus/)
- [The Metaverse Dashboard (Codename Iamus)](https://github.com/vircadia/project-iamus-dashboard/)
- [The Launcher (Codename Pantheon)](https://github.com/vircadia/pantheon-launcher/)
### Why High Fidelity's Virtual Reality Platform? #### Child Projects
- [Vircadia Builder for Linux](https://github.com/vircadia/vircadia-builder/)
Because of all the options, it is the only starting point that is open-source, cross-platform, fully VR integrated + fully desktop integrated with an aim for quality visuals and performance. It also provides a foundation to build from including components like entity management, full body IK, etc. - [General Documentation](https://github.com/vircadia/vircadia-docs-sphinx/)
WebXR offers the open-source and decentralized aspect but does not have any of the full featured starting points such as avatars, IK, etc. which means that a lot of ground work will have to be laid to make something functional. Far more work will need to be done to create a truly seamless and extensive experience as well.
Platforms like NeosVR or VRChat are not viable from go due to their fundamental closed-source and centralized nature. A metaverse to live in cannot have the keys handed over to any singular entity, if any at all.
We need to do the best we can with what we've got and our best bet as open source developers is to not redesign the wheel if we can help it!
### Contribution ### Contribution
A special thanks to the contributors of Vircadia. There are many contributors to Vircadia. Code writers, reviewers, testers, documentation writers, modelers, and general supporters of the project are all integral to its development and success towards its goals. Find out how you can [contribute](CONTRIBUTING.md)!
[Contribution](CONTRIBUTING.md)

View file

@ -17,7 +17,7 @@ fi
ANDROID_APP=interface ANDROID_APP=interface
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE} ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
ANDROID_APK_NAME=Vircadia-Alpha-${ANDROID_APK_SUFFIX} ANDROID_APK_NAME=Vircadia-${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} ./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} cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View file

@ -31,7 +31,7 @@ macro(GENERATE_INSTALLERS)
set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME}) set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME})
set(CPACK_PACKAGE_VENDOR "Vircadia") set(CPACK_PACKAGE_VENDOR "Vircadia")
set(CPACK_PACKAGE_VERSION ${BUILD_VERSION}) set(CPACK_PACKAGE_VERSION ${BUILD_VERSION})
set(CPACK_PACKAGE_FILE_NAME "Vircadia-Alpha${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}") set(CPACK_PACKAGE_FILE_NAME "Vircadia${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}")
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
if (PR_BUILD) if (PR_BUILD)

View file

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

View file

@ -4,6 +4,7 @@
// //
// Created by Stephen Birarda on 1/14/16. // Created by Stephen Birarda on 1/14/16.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -24,6 +25,7 @@ namespace BuildInfo {
const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@"; const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@";
const QString ORGANIZATION_DOMAIN = "vircadia.com"; const QString ORGANIZATION_DOMAIN = "vircadia.com";
const QString VERSION = "@BUILD_VERSION@"; const QString VERSION = "@BUILD_VERSION@";
const QString RELEASE_NAME = "@RELEASE_NAME@";
const QString BUILD_NUMBER = "@BUILD_NUMBER@"; const QString BUILD_NUMBER = "@BUILD_NUMBER@";
const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@"; const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@";
const QString BUILD_TIME = "@BUILD_TIME@"; const QString BUILD_TIME = "@BUILD_TIME@";

View file

@ -505,7 +505,7 @@ function createDomainIDPrompt(callback) {
swal({ swal({
title: 'Finish Registering Domain', title: 'Finish Registering Domain',
type: 'input', type: 'input',
text: 'Enter a label for this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>This is a required step for registration.</br></br>', text: 'Enter a label for this Domain Server.</br></br>This will help you identify which domain ID belongs to which server.</br></br>This is a required step for registration.</br></br>Acceptable characters are [A-Z][a-z0-9]+-_.</br',
showCancelButton: true, showCancelButton: true,
confirmButtonText: "Create", confirmButtonText: "Create",
closeOnConfirm: false, closeOnConfirm: false,

View file

@ -193,7 +193,11 @@ function promptToCreateDomainID() {
var formJSON = { var formJSON = {
"metaverse": { "metaverse": {
"automatic_networking": "full",
"id": domainID "id": domainID
},
"descriptors": {
"world_name": label
} }
}; };

View file

@ -90,8 +90,8 @@ endif()
if 'Windows' == system: if 'Windows' == system:
self.exe = os.path.join(self.path, 'vcpkg.exe') self.exe = os.path.join(self.path, 'vcpkg.exe')
self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat'), '-disableMetrics' ] self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat'), '-disableMetrics' ]
self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-win32-client.zip%3FversionId=tSFzbw01VkkVFeRQ6YuAY4dro2HxJR9U' self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-win32-client-20210122.zip'
self.vcpkgHash = 'a650db47a63ccdc9904b68ddd16af74772e7e78170b513ea8de5a3b47d032751a3b73dcc7526d88bcb500753ea3dd9880639ca842bb176e2bddb1710f9a58cd3' self.vcpkgHash = '3df86b7d58c827bf08b3b7744f456f414b86a6d9bd58a507924103bc5a88f01ee495ce1f0fbf2f5b27f1ef6bfb1526e580ec13d3b9f87a89a462b3c50589fd6a'
self.hostTriplet = 'x64-windows' self.hostTriplet = 'x64-windows'
if usePrebuilt: if usePrebuilt:
self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx" self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx"

View file

@ -244,7 +244,7 @@ Item {
color: hifi.colors.darkGray; color: hifi.colors.darkGray;
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; enabled: selected && pal.activeTab == "nearbyTab" && isPresent;
hoverEnabled: enabled hoverEnabled: enabled
onClicked: { onClicked: {
goToUserInDomain(thisNameCard.uuid); goToUserInDomain(thisNameCard.uuid);

View file

@ -28,13 +28,18 @@ Rectangle {
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: "../../../images/vircadia-banner.svg" source: "../../../images/vircadia-banner.svg"
} }
Item { height: 30; width: 1 } Item { height: 25; width: 1 }
Column { Column {
id: buildColumm id: buildColumn
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 70 anchors.leftMargin: 0
RalewayRegular { RalewayRegular {
text: "Build " + About.buildVersion text: "Interface"
size: 16
color: "white"
}
RalewayRegular {
text: "Build " + About.buildVersion + " " + About.releaseName
size: 16 size: 16
color: "white" color: "white"
} }
@ -54,14 +59,25 @@ Rectangle {
textFormat: Text.StyledText textFormat: Text.StyledText
linkColor: "#00B4EF" linkColor: "#00B4EF"
color: "white" color: "white"
text: "<a href=\"https://github.com/vircadia/vircadia\">Vircadia Github</a>." text: "<a href=\"https://vircadia.com\">Website</a>"
size: 20 size: 20
onLinkActivated: { onLinkActivated: {
About.openUrl("https:/github.com/vircadia/vircadia"); About.openUrl("https://vircadia.com");
} }
} }
Item { height: 40; width: 1 } RalewayRegular {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "<a href=\"https://github.com/vircadia/vircadia\">Source</a>"
size: 20
onLinkActivated: {
About.openUrl("https://github.com/vircadia/vircadia");
}
}
Item { height: 25; width: 1 }
Row { Row {
spacing: 5 spacing: 5
Image { Image {
@ -117,7 +133,7 @@ Rectangle {
Item { height: 20; width: 1 } Item { height: 20; width: 1 }
RalewayRegular { RalewayRegular {
color: "white" color: "white"
text: "© 2019-2020 Vircadia contributors." text: "© 2019 - 2021 Vircadia contributors."
size: 14 size: 14
} }
RalewayRegular { RalewayRegular {
@ -135,5 +151,23 @@ Rectangle {
About.openUrl("http://www.apache.org/licenses/LICENSE-2.0.html"); About.openUrl("http://www.apache.org/licenses/LICENSE-2.0.html");
} }
} }
Item { height: 35; width: 1 }
RalewayRegular {
color: "white"
text: "In memoriam,"
size: 14
}
RalewayRegular {
color: "white"
text: "2012 - 2019 the High Fidelity virtual reality project."
size: 14
}
Item { height: 5; width: 1 }
Image {
id: hifiLogo
width: 200; height: 50
fillMode: Image.PreserveAspectFit
source: "../../../images/about-highfidelity.png"
}
} }
} }

View file

@ -4,6 +4,7 @@
// //
// Created by Vlad Stelmahovsky on 15/5/2018. // Created by Vlad Stelmahovsky on 15/5/2018.
// Copyright 2018 High Fidelity, Inc. // Copyright 2018 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -40,6 +41,10 @@ QString AboutUtil::getBuildVersion() const {
return BuildInfo::VERSION; return BuildInfo::VERSION;
} }
QString AboutUtil::getReleaseName() const {
return BuildInfo::RELEASE_NAME;
}
QString AboutUtil::getQtVersion() const { QString AboutUtil::getQtVersion() const {
return qVersion(); return qVersion();
} }
@ -57,15 +62,15 @@ void AboutUtil::openUrl(const QString& url) const {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"); auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>(); auto hmd = DependencyManager::get<HMDScriptingInterface>();
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && offscreenUI) {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { offscreenUI->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
newObject->setProperty("url", url); newObject->setProperty("url", url);
}); });
} else { } else {
if (!hmd->getShouldShowTablet() && !qApp->isHMDMode()) { if (!hmd->getShouldShowTablet() && !qApp->isHMDMode() && offscreenUI) {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { offscreenUI->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
newObject->setProperty("url", url); newObject->setProperty("url", url);
}); });
} else { } else {

View file

@ -30,12 +30,14 @@
* <em>Read-only.</em> * <em>Read-only.</em>
* @property {string} buildDate - The build date of Interface that is currently running. <em>Read-only.</em> * @property {string} buildDate - The build date of Interface that is currently running. <em>Read-only.</em>
* @property {string} buildVersion - The build version of Interface that is currently running. <em>Read-only.</em> * @property {string} buildVersion - The build version of Interface that is currently running. <em>Read-only.</em>
* @property {string} releaseName - The release codename of the version that Interface is currently running. <em>Read-only.</em>
* @property {string} qtVersion - The Qt version used in Interface that is currently running. <em>Read-only.</em> * @property {string} qtVersion - The Qt version used in Interface that is currently running. <em>Read-only.</em>
* *
* @example <caption>Report information on the version of Interface currently running.</caption> * @example <caption>Report information on the version of Interface currently running.</caption>
* print("Interface platform: " + About.platform); * print("Interface platform: " + About.platform);
* print("Interface build date: " + About.buildDate); * print("Interface build date: " + About.buildDate);
* print("Interface version: " + About.buildVersion); * print("Interface version: " + About.buildVersion);
* print("Interface release name: " + About.releaseName);
* print("Qt version: " + About.qtVersion); * print("Qt version: " + About.qtVersion);
*/ */
@ -66,6 +68,7 @@ class AboutUtil : public QObject {
Q_PROPERTY(QString platform READ getPlatformName CONSTANT) Q_PROPERTY(QString platform READ getPlatformName CONSTANT)
Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT) Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT)
Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT) Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT)
Q_PROPERTY(QString releaseName READ getReleaseName CONSTANT)
Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT) Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT)
public: public:
static AboutUtil* getInstance(); static AboutUtil* getInstance();
@ -74,6 +77,7 @@ public:
QString getPlatformName() const { return "Vircadia"; } QString getPlatformName() const { return "Vircadia"; }
QString getBuildDate() const; QString getBuildDate() const;
QString getBuildVersion() const; QString getBuildVersion() const;
QString getReleaseName() const;
QString getQtVersion() const; QString getQtVersion() const;
public slots: public slots:

View file

@ -7190,6 +7190,10 @@ void Application::updateWindowTitle() const {
QString buildVersion = " - Vircadia - " QString buildVersion = " - Vircadia - "
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
+ " " + applicationVersion(); + " " + applicationVersion();
if (BuildInfo::RELEASE_NAME != "") {
buildVersion += " - " + BuildInfo::RELEASE_NAME;
}
QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" :
nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";

View file

@ -61,7 +61,6 @@ void Bookmarks::deleteBookmark(const QString& bookmarkName) {
void Bookmarks::addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark) { void Bookmarks::addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark) {
Menu* menubar = Menu::getInstance(); Menu* menubar = Menu::getInstance();
if (contains(bookmarkName)) { if (contains(bookmarkName)) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
ModalDialogListener* dlg = OffscreenUi::asyncWarning("Duplicate Bookmark", ModalDialogListener* dlg = OffscreenUi::asyncWarning("Duplicate Bookmark",
"The bookmark name you entered already exists in your list.", "The bookmark name you entered already exists in your list.",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

View file

@ -58,7 +58,9 @@ bool AvatarPackager::open() {
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode()) {
static const QUrl url{ "hifi/AvatarPackagerWindow.qml" }; static const QUrl url{ "hifi/AvatarPackagerWindow.qml" };
DependencyManager::get<OffscreenUi>()->show(url, "AvatarPackager", packageModelDialogCreated); if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
offscreenUI->show(url, "AvatarPackager", packageModelDialogCreated);
}
return true; return true;
} }

File diff suppressed because it is too large Load diff

View file

@ -283,15 +283,16 @@ class MyAvatar : public Avatar {
* the value.</p> * the value.</p>
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme.
* @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior.
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting * <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is * @property {boolean} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting;
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). * <code>false</code> if the user wearing the HMD is determined to be standing. This can affect whether the avatar
* If <code>userRecenterModel == 2</code> (i.e., "auto") the property value automatically updates as the user sits * is allowed to stand, lean or recenter its footing, depending on user preferences.
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current * The property value automatically updates as the user sits or stands. Setting the property value overrides the current
* sitting / standing state, which is updated when the user next sits or stands unless * sitting / standing state, which is updated when the user next sits or stands.
* <code>isSitStandStateLocked == true</code>.
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this * @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
* to disable automatically changing state. * to disable automatically changing state.
* <p class="important">Deprecated: This property is deprecated and will be removed.
* See also: <code>getUserRecenterModel</code> and <code>setUserRecenterModel</code>.</p>
* @property {boolean} allowTeleporting - <code>true</code> if teleporting is enabled in the Interface settings, * @property {boolean} allowTeleporting - <code>true</code> if teleporting is enabled in the Interface settings,
* <code>false</code> if it isn't. <em>Read-only.</em> * <code>false</code> if it isn't. <em>Read-only.</em>
* *
@ -413,8 +414,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged); Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged);
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged);
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState);
Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); // Deprecated
Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); // Deprecated
Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting) Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_LEFT_HAND = "left";
@ -519,6 +520,7 @@ public:
/**jsdoc /**jsdoc
* <p>Specifies different avatar leaning and recentering behaviors.</p> * <p>Specifies different avatar leaning and recentering behaviors.</p>
* <p class="important">Deprecated: This type is deprecated and will be removed.</p>
* <table> * <table>
* <thead> * <thead>
* <tr><th>Value</th><th>Name</th><th>Description</th></tr> * <tr><th>Value</th><th>Name</th><th>Description</th></tr>
@ -549,6 +551,29 @@ public:
}; };
Q_ENUM(SitStandModelType) Q_ENUM(SitStandModelType)
// Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order.
enum class AllowAvatarStandingPreference : uint {
WhenUserIsStanding,
Always,
Count,
Default = Always
};
Q_ENUM(AllowAvatarStandingPreference)
// Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order.
enum class AllowAvatarLeaningPreference : uint {
WhenUserIsStanding,
Always,
Never,
AlwaysNoRecenter, // experimental
Count,
Default = WhenUserIsStanding
};
Q_ENUM(AllowAvatarLeaningPreference)
static const std::array<QString, (uint)AllowAvatarStandingPreference::Count> allowAvatarStandingPreferenceStrings;
static const std::array<QString, (uint)AllowAvatarLeaningPreference::Count> allowAvatarLeaningPreferenceStrings;
explicit MyAvatar(QThread* thread); explicit MyAvatar(QThread* thread);
virtual ~MyAvatar(); virtual ~MyAvatar();
@ -1417,7 +1442,6 @@ public:
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const; controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const; controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
glm::quat getOffHandRotation() const;
bool hasDriveInput() const; bool hasDriveInput() const;
@ -1596,7 +1620,7 @@ public:
* @function MyAvatar.getAvatarScale * @function MyAvatar.getAvatarScale
* @returns {number} The target scale for the avatar, range <code>0.005</code> &ndash; <code>1000.0</code>. * @returns {number} The target scale for the avatar, range <code>0.005</code> &ndash; <code>1000.0</code>.
*/ */
Q_INVOKABLE float getAvatarScale(); Q_INVOKABLE float getAvatarScale() const;
/**jsdoc /**jsdoc
* Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on * Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
@ -1709,7 +1733,7 @@ public:
// derive avatar body position and orientation from the current HMD Sensor location. // derive avatar body position and orientation from the current HMD Sensor location.
// results are in sensor frame (-z forward) // results are in sensor frame (-z forward)
glm::mat4 deriveBodyFromHMDSensor() const; glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
glm::mat4 getSpine2RotationRigSpace() const; glm::mat4 getSpine2RotationRigSpace() const;
@ -1753,10 +1777,14 @@ public:
bool getIsInWalkingState() const; bool getIsInWalkingState() const;
void setIsInSittingState(bool isSitting); void setIsInSittingState(bool isSitting);
bool getIsInSittingState() const; bool getIsInSittingState() const;
void setUserRecenterModel(MyAvatar::SitStandModelType modelName); void setUserRecenterModel(MyAvatar::SitStandModelType modelName); // Deprecated, will be removed.
MyAvatar::SitStandModelType getUserRecenterModel() const; MyAvatar::SitStandModelType getUserRecenterModel() const; // Deprecated, will be removed.
void setIsSitStandStateLocked(bool isLocked); void setIsSitStandStateLocked(bool isLocked); // Deprecated, will be removed.
bool getIsSitStandStateLocked() const; bool getIsSitStandStateLocked() const; // Deprecated, will be removed.
void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference);
AllowAvatarStandingPreference getAllowAvatarStandingPreference() const;
void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference);
AllowAvatarLeaningPreference getAllowAvatarLeaningPreference() const;
void setWalkSpeed(float value); void setWalkSpeed(float value);
float getWalkSpeed() const; float getWalkSpeed() const;
void setWalkBackwardSpeed(float value); void setWalkBackwardSpeed(float value);
@ -1989,6 +2017,10 @@ public:
glm::vec3 getLookAtPivotPoint(); glm::vec3 getLookAtPivotPoint();
glm::vec3 getCameraEyesPosition(float deltaTime); glm::vec3 getCameraEyesPosition(float deltaTime);
bool isJumping(); bool isJumping();
bool getHMDCrouchRecenterEnabled() const;
bool isAllowedToLean() const;
bool areFeetTracked() const { return _isBodyPartTracked._feet; }; // Determine if the feet are under direct control.
bool areHipsTracked() const { return _isBodyPartTracked._hips; }; // Determine if the hips are under direct control.
public slots: public slots:
@ -2709,6 +2741,16 @@ private:
bool _isBraking { false }; bool _isBraking { false };
bool _isAway { false }; bool _isAway { false };
// Indicates which parts of the body are under direct control (tracked).
struct {
bool _feet { false }; // Left or right foot.
bool _feetPreviousUpdate{ false };// Value of _feet on the previous update.
bool _hips{ false };
bool _leftHand{ false };
bool _rightHand{ false };
bool _head{ false };
} _isBodyPartTracked;
float _boomLength { ZOOM_DEFAULT }; float _boomLength { ZOOM_DEFAULT };
float _yawSpeed; // degrees/sec float _yawSpeed; // degrees/sec
float _pitchSpeed; // degrees/sec float _pitchSpeed; // degrees/sec
@ -2791,6 +2833,7 @@ private:
void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation); void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation);
void resetPointAt(); void resetPointAt();
static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation); static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation);
void centerBodyInternal(const bool forceFollowYPos = false);
// Avatar Preferences // Avatar Preferences
QUrl _fullAvatarURLFromPreferences; QUrl _fullAvatarURLFromPreferences;
@ -2841,26 +2884,21 @@ private:
struct FollowHelper { struct FollowHelper {
FollowHelper(); FollowHelper();
enum FollowType { CharacterController::FollowTimePerType _timeRemaining;
Rotation = 0,
Horizontal,
Vertical,
NumFollowTypes
};
float _timeRemaining[NumFollowTypes];
void deactivate(); void deactivate();
void deactivate(FollowType type); void deactivate(CharacterController::FollowType type);
void activate(); void activate(CharacterController::FollowType type, const bool snapFollow);
void activate(FollowType type);
bool isActive() const; bool isActive() const;
bool isActive(FollowType followType) const; bool isActive(CharacterController::FollowType followType) const;
float getMaxTimeRemaining() const;
void decrementTimeRemaining(float dt); void decrementTimeRemaining(float dt);
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const;
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar,
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; const glm::mat4& desiredBodyMatrix,
const glm::mat4& currentBodyMatrix,
bool& resetModeOut,
bool& goToWalkingStateOut) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const; bool getForceActivateRotation() const;
@ -2871,16 +2909,23 @@ private:
void setForceActivateHorizontal(bool val); void setForceActivateHorizontal(bool val);
bool getToggleHipsFollowing() const; bool getToggleHipsFollowing() const;
void setToggleHipsFollowing(bool followHead); void setToggleHipsFollowing(bool followHead);
bool _squatDetected { false };
std::atomic<bool> _forceActivateRotation { false }; std::atomic<bool> _forceActivateRotation { false };
std::atomic<bool> _forceActivateVertical { false }; std::atomic<bool> _forceActivateVertical { false };
std::atomic<bool> _forceActivateHorizontal { false }; std::atomic<bool> _forceActivateHorizontal { false };
std::atomic<bool> _toggleHipsFollowing { true }; std::atomic<bool> _toggleHipsFollowing { true };
private:
bool shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar,
const glm::mat4& desiredBodyMatrix,
const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontal_userStanding(const MyAvatar& myAvatar,
bool& resetModeOut,
bool& goToWalkingStateOut) const;
}; };
FollowHelper _follow; FollowHelper _follow;
bool isFollowActive(FollowHelper::FollowType followType) const; bool isFollowActive(CharacterController::FollowType followType) const;
bool _goToPending { false }; bool _goToPending { false };
bool _physicsSafetyPending { false }; bool _physicsSafetyPending { false };
@ -2922,6 +2967,9 @@ private:
bool _centerOfGravityModelEnabled { true }; bool _centerOfGravityModelEnabled { true };
bool _hmdLeanRecenterEnabled { true }; bool _hmdLeanRecenterEnabled { true };
bool _hmdCrouchRecenterEnabled {
true
}; // Is MyAvatar allowed to recenter vertically (stand) when the user is sitting in the real world.
bool _sprint { false }; bool _sprint { false };
AnimPose _prePhysicsRoomPose; AnimPose _prePhysicsRoomPose;
@ -2953,7 +3001,6 @@ private:
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT }; ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
float _averageUserHeightSensorSpace { _userHeight.get() }; float _averageUserHeightSensorSpace { _userHeight.get() };
bool _sitStandStateChange { false }; bool _sitStandStateChange { false };
ThreadSafeValueCache<bool> _lockSitStandState { false };
// max unscaled forward movement speed // max unscaled forward movement speed
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
@ -2969,9 +3016,13 @@ private:
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
bool _isInWalkingState { false }; bool _isInWalkingState { false };
ThreadSafeValueCache<bool> _isInSittingState { false }; ThreadSafeValueCache<bool> _isInSittingState { false };
ThreadSafeValueCache<MyAvatar::SitStandModelType> _userRecenterModel { MyAvatar::SitStandModelType::Auto }; ThreadSafeValueCache<MyAvatar::AllowAvatarStandingPreference> _allowAvatarStandingPreference{
MyAvatar::AllowAvatarStandingPreference::Default
}; // The user preference of when MyAvatar may stand.
ThreadSafeValueCache<MyAvatar::AllowAvatarLeaningPreference> _allowAvatarLeaningPreference{
MyAvatar::AllowAvatarLeaningPreference::Default
}; // The user preference of when MyAvatar may lean.
float _sitStandStateTimer { 0.0f }; float _sitStandStateTimer { 0.0f };
float _squatTimer { 0.0f };
float _tippingPoint { _userHeight.get() }; float _tippingPoint { _userHeight.get() };
// load avatar scripts once when rig is ready // load avatar scripts once when rig is ready
@ -3012,7 +3063,8 @@ private:
Setting::Handle<int> _controlSchemeIndexSetting; Setting::Handle<int> _controlSchemeIndexSetting;
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings; std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings; std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
Setting::Handle<QString> _userRecenterModelSetting; Setting::Handle<QString> _allowAvatarStandingPreferenceSetting;
Setting::Handle<QString> _allowAvatarLeaningPreferenceSetting;
// AvatarEntities stuff: // AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute

View file

@ -26,7 +26,9 @@ void MyCharacterController::RayShotgunResult::reset() {
walkable = true; walkable = true;
} }
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) { MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar,
const FollowTimePerType& followTimeRemainingPerType) :
CharacterController(followTimeRemainingPerType) {
assert(avatar); assert(avatar);
_avatar = avatar; _avatar = avatar;

View file

@ -23,7 +23,7 @@ class DetailedMotionState;
class MyCharacterController : public CharacterController { class MyCharacterController : public CharacterController {
public: public:
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar); explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar, const FollowTimePerType& followTimeRemainingPerType);
~MyCharacterController (); ~MyCharacterController ();
void addToWorld() override; void addToWorld() override;

View file

@ -65,13 +65,21 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
return result; return result;
} }
// Use the center-of-gravity model if the user and the avatar are standing, unless flying or walking.
// If artificial standing is disabled, use center-of-gravity regardless of the user's sit/stand state.
bool useCenterOfGravityModel =
myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() &&
(!myAvatar->getHMDCrouchRecenterEnabled() || !myAvatar->getIsInSittingState()) &&
myAvatar->getHMDLeanRecenterEnabled() &&
(myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter);
glm::mat4 hipsMat; glm::mat4 hipsMat;
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) { if (useCenterOfGravityModel) {
// then we use center of gravity model // then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel(); hipsMat = myAvatar->deriveBodyUsingCgModel();
} else { } else {
// otherwise use the default of putting the hips under the head // otherwise use the default of putting the hips under the head
hipsMat = myAvatar->deriveBodyFromHMDSensor(); hipsMat = myAvatar->deriveBodyFromHMDSensor(true);
} }
glm::vec3 hipsPos = extractTranslation(hipsMat); glm::vec3 hipsPos = extractTranslation(hipsMat);
glm::quat hipsRot = glmExtractRotation(hipsMat); glm::quat hipsRot = glmExtractRotation(hipsMat);
@ -82,7 +90,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
// dampen hips rotation, by mixing it with the avatar orientation in sensor space // dampen hips rotation, by mixing it with the avatar orientation in sensor space
// turning this off for center of gravity model because it is already mixed in there // turning this off for center of gravity model because it is already mixed in there
if (!(myAvatar->getCenterOfGravityModelEnabled())) { if (!useCenterOfGravityModel) {
const float MIX_RATIO = 0.5f; const float MIX_RATIO = 0.5f;
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
} }

View file

@ -415,7 +415,7 @@ gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabo
for (auto& key : keys) { for (auto& key : keys) {
gpu::StatePointer state = gpu::StatePointer(new gpu::State()); gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL);
if (std::get<0>(key)) { if (std::get<0>(key)) {
PrepareStencil::testMask(*state); PrepareStencil::testMask(*state);
} else { } else {

View file

@ -76,8 +76,13 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping,
"Use the field below to place your file in a specific folder or to rename it. " "Use the field below to place your file in a specific folder or to rename it. "
"Specifying a new folder name will automatically create that folder for you."; "Specifying a new folder name will automatically create that folder for you.";
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path", if (!offscreenUI) {
completedCallback.call({ -1 });
return;
}
auto result = offscreenUI->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path",
dropEvent ? dropHelpText : helpText, mapping); dropEvent ? dropHelpText : helpText, mapping);
if (!result.isValid() || result.toString() == "") { if (!result.isValid() || result.toString() == "") {
@ -94,7 +99,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping,
// Check for override // Check for override
if (isKnownMapping(mapping)) { if (isKnownMapping(mapping)) {
auto message = mapping + "\n" + "This file already exists. Do you want to overwrite it?"; auto message = mapping + "\n" + "This file already exists. Do you want to overwrite it?";
auto button = offscreenUi->messageBox(OffscreenUi::ICON_QUESTION, "Overwrite File", message, auto button = offscreenUI->messageBox(OffscreenUi::ICON_QUESTION, "Overwrite File", message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (button == QMessageBox::No) { if (button == QMessageBox::No) {
completedCallback.call({ -1 }); completedCallback.call({ -1 });

View file

@ -99,11 +99,16 @@ void DesktopScriptingInterface::setHUDAlpha(float alpha) {
} }
void DesktopScriptingInterface::show(const QString& path, const QString& title) { void DesktopScriptingInterface::show(const QString& path, const QString& title) {
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
return;
}
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "show", Qt::QueuedConnection, Q_ARG(QString, path), Q_ARG(QString, title)); QMetaObject::invokeMethod(this, "show", Qt::QueuedConnection, Q_ARG(QString, path), Q_ARG(QString, title));
return; return;
} }
DependencyManager::get<OffscreenUi>()->show(path, title); offscreenUI->show(path, title);
} }
InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) { InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) {

View file

@ -96,8 +96,9 @@ bool HMDScriptingInterface::shouldShowHandControllers() const {
void HMDScriptingInterface::activateHMDHandMouse() { void HMDScriptingInterface::activateHMDHandMouse() {
QWriteLocker lock(&_hmdHandMouseLock); QWriteLocker lock(&_hmdHandMouseLock);
auto offscreenUi = DependencyManager::get<OffscreenUi>(); if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", true); offscreenUI->getDesktop()->setProperty("hmdHandMouseActive", true);
}
_hmdHandMouseCount++; _hmdHandMouseCount++;
} }
@ -105,8 +106,9 @@ void HMDScriptingInterface::deactivateHMDHandMouse() {
QWriteLocker lock(&_hmdHandMouseLock); QWriteLocker lock(&_hmdHandMouseLock);
_hmdHandMouseCount = std::max(_hmdHandMouseCount - 1, 0); _hmdHandMouseCount = std::max(_hmdHandMouseCount - 1, 0);
if (_hmdHandMouseCount == 0) { if (_hmdHandMouseCount == 0) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", false); offscreenUI->getDesktop()->setProperty("hmdHandMouseActive", false);
}
} }
} }

View file

@ -217,7 +217,7 @@ PlatformInfoScriptingInterface::PlatformTier PlatformInfoScriptingInterface::get
} }
QStringList PlatformInfoScriptingInterface::getPlatformTierNames() { QStringList PlatformInfoScriptingInterface::getPlatformTierNames() {
static const QStringList platformTierNames = { "UNKNWON", "LOW", "MID", "HIGH" }; static const QStringList platformTierNames = { "UNKNOWN", "LOW", "MID", "HIGH" };
return platformTierNames; return platformTierNames;
} }

View file

@ -199,9 +199,9 @@ void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitia
DependencyManager::get<NodeList>()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode); DependencyManager::get<NodeList>()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode);
} }
bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
return offscreenUi->isPointOnDesktopWindow(point); return offscreenUI ? offscreenUI->isPointOnDesktopWindow(point) : false;
} }
/// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and /// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and
@ -553,12 +553,14 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu
* @typedef {number} Window.MessageBoxButton * @typedef {number} Window.MessageBoxButton
*/ */
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) { int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text, if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
static_cast<QFlags<QMessageBox::StandardButton>>(buttons), static_cast<QMessageBox::StandardButton>(defaultButton)); auto messageBox = offscreenUI->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int))); static_cast<QFlags<QMessageBox::StandardButton>>(buttons), static_cast<QMessageBox::StandardButton>(defaultButton));
connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int)));
_lastMessageBoxID += 1; _lastMessageBoxID += 1;
_messageBoxes.insert(_lastMessageBoxID, messageBox); _messageBoxes.insert(_lastMessageBoxID, messageBox);
}
return _lastMessageBoxID; return _lastMessageBoxID;
} }
@ -646,13 +648,17 @@ void WindowScriptingInterface::setActiveDisplayPlugin(int index) {
} }
void WindowScriptingInterface::openWebBrowser(const QString& url) { void WindowScriptingInterface::openWebBrowser(const QString& url) {
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
return;
}
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "openWebBrowser", Q_ARG(const QString&, url)); QMetaObject::invokeMethod(this, "openWebBrowser", Q_ARG(const QString&, url));
return; return;
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>(); offscreenUI->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
if (!url.isEmpty()) { if (!url.isEmpty()) {
newObject->setProperty("url", url); newObject->setProperty("url", url);
} }

View file

@ -67,13 +67,13 @@ void AnimStats::updateStats(bool force) {
// print if we are recentering or not. // print if we are recentering or not.
_recenterText = "Recenter: "; _recenterText = "Recenter: ";
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Rotation)) { if (myAvatar->isFollowActive(CharacterController::FollowType::Rotation)) {
_recenterText += "Rotation "; _recenterText += "Rotation ";
} }
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Horizontal)) { if (myAvatar->isFollowActive(CharacterController::FollowType::Horizontal)) {
_recenterText += "Horizontal "; _recenterText += "Horizontal ";
} }
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Vertical)) { if (myAvatar->isFollowActive(CharacterController::FollowType::Vertical)) {
_recenterText += "Vertical "; _recenterText += "Vertical ";
} }
emit recenterTextChanged(); emit recenterTextChanged();

View file

@ -100,10 +100,10 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
// threads, we need to use a sync object to deteremine when // threads, we need to use a sync object to deteremine when
// the current UI texture is no longer being read from, and only // the current UI texture is no longer being read from, and only
// then release it back to the UI for re-use // then release it back to the UI for re-use
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
OffscreenQmlSurface::TextureAndFence newTextureAndFence; OffscreenQmlSurface::TextureAndFence newTextureAndFence;
bool newTextureAvailable = offscreenUi->fetchTexture(newTextureAndFence); bool newTextureAvailable = offscreenUI ? offscreenUI->fetchTexture(newTextureAndFence) : false;
if (newTextureAvailable) { if (newTextureAvailable) {
_uiTexture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second); _uiTexture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
} }

View file

@ -362,10 +362,11 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
object->setObjectName("InteractiveWindow"); object->setObjectName("InteractiveWindow");
object->setProperty(SOURCE_PROPERTY, sourceURL); object->setProperty(SOURCE_PROPERTY, sourceURL);
}; };
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Build the event bridge and wrapper on the main thread if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, objectInitLambda, contextInitLambda); // Build the event bridge and wrapper on the main thread
offscreenUI->loadInNewContext(CONTENT_WINDOW_QML, objectInitLambda, contextInitLambda);
}
} }
} }

View file

@ -422,40 +422,40 @@ void setupPreferences() {
preferences->addPreference(preference); preferences->addPreference(preference);
} }
{ {
auto getter = [myAvatar]()->int { IntPreference::Getter getter = [myAvatar]() -> int {
switch (myAvatar->getUserRecenterModel()) { return static_cast<int>(myAvatar->getAllowAvatarStandingPreference());
case MyAvatar::SitStandModelType::Auto:
default:
return 0;
case MyAvatar::SitStandModelType::ForceSit:
return 1;
case MyAvatar::SitStandModelType::ForceStand:
return 2;
case MyAvatar::SitStandModelType::DisableHMDLean:
return 3;
}
}; };
auto setter = [myAvatar](int value) {
switch (value) { IntPreference::Setter setter = [myAvatar](const int& value) {
case 0: myAvatar->setAllowAvatarStandingPreference(static_cast<MyAvatar::AllowAvatarStandingPreference>(value));
default:
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto);
break;
case 1:
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit);
break;
case 2:
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceStand);
break;
case 3:
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean);
break;
}
}; };
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Force Stand / Disable Recenter", getter, setter);
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to stand", getter, setter);
QStringList items; QStringList items;
items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Standing - enables avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; items << "When I'm standing"
preference->setHeading("Avatar leaning behavior"); << "Always"; // Must match the order in MyAvatar::AllowAvatarStandingPreference.
assert(items.size() == static_cast<uint>(MyAvatar::AllowAvatarStandingPreference::Count));
preference->setHeading("Allow my avatar to stand:");
preference->setItems(items);
preferences->addPreference(preference);
}
{
IntPreference::Getter getter = [myAvatar]() -> int {
return static_cast<int>(myAvatar->getAllowAvatarLeaningPreference());
};
IntPreference::Setter setter = [myAvatar](const int& value) {
myAvatar->setAllowAvatarLeaningPreference(static_cast<MyAvatar::AllowAvatarLeaningPreference>(value));
};
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to lean", getter, setter);
QStringList items;
items << "When I'm standing"
<< "Always"
<< "Never"
<< "Always, no recenter (Experimental)"; // Must match the order in MyAvatar::AllowAvatarLeaningPreference.
assert(items.size() == static_cast<uint>(MyAvatar::AllowAvatarLeaningPreference::Count));
preference->setHeading("Allow my avatar to lean:");
preference->setItems(items); preference->setItems(items);
preferences->addPreference(preference); preferences->addPreference(preference);
} }

View file

@ -248,9 +248,9 @@ private: \
* <em>Read-only.</em> * <em>Read-only.</em>
* @property {string} lodStatus - Description of the current LOD. * @property {string} lodStatus - Description of the current LOD.
* <em>Read-only.</em> * <em>Read-only.</em>
* @property {string} numEntityUpdates - The number of entity updates that happened last frame. * @property {number} numEntityUpdates - The number of entity updates that happened last frame.
* <em>Read-only.</em> * <em>Read-only.</em>
* @property {string} numNeededEntityUpdates - The total number of entity updates scheduled for last frame. * @property {number} numNeededEntityUpdates - The total number of entity updates scheduled for last frame.
* <em>Read-only.</em> * <em>Read-only.</em>
* @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of * @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of
* the code. Provided only if <code>timingExpanded</code> is <code>true</code>. Only the top 10 items are provided if * the code. Provided only if <code>timingExpanded</code> is <code>true</code>. Only the top 10 items are provided if
@ -547,8 +547,8 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, lodAngle, 0) STATS_PROPERTY(int, lodAngle, 0)
STATS_PROPERTY(int, lodTargetFramerate, 0) STATS_PROPERTY(int, lodTargetFramerate, 0)
STATS_PROPERTY(QString, lodStatus, QString()) STATS_PROPERTY(QString, lodStatus, QString())
STATS_PROPERTY(int, numEntityUpdates, 0) STATS_PROPERTY(quint64, numEntityUpdates, 0)
STATS_PROPERTY(int, numNeededEntityUpdates, 0) STATS_PROPERTY(quint64, numNeededEntityUpdates, 0)
STATS_PROPERTY(QString, timingStats, QString()) STATS_PROPERTY(QString, timingStats, QString())
STATS_PROPERTY(QString, gameUpdateStats, QString()) STATS_PROPERTY(QString, gameUpdateStats, QString())
STATS_PROPERTY(int, serverElements, 0) STATS_PROPERTY(int, serverElements, 0)

View file

@ -1212,8 +1212,8 @@ float Overlays::width() {
return result; return result;
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
return offscreenUi->getWindow()->size().width(); return offscreenUI ? offscreenUI->getWindow()->size().width() : -1.0f;
} }
float Overlays::height() { float Overlays::height() {
@ -1224,8 +1224,8 @@ float Overlays::height() {
return result; return result;
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
return offscreenUi->getWindow()->size().height(); return offscreenUI ? offscreenUI->getWindow()->size().height() : -1.0f;
} }
void Overlays::mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event) { void Overlays::mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event) {

View file

@ -24,13 +24,17 @@ QmlOverlay::QmlOverlay(const QUrl& url, const QmlOverlay* overlay)
} }
void QmlOverlay::buildQmlElement(const QUrl& url) { void QmlOverlay::buildQmlElement(const QUrl& url) {
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
return;
}
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "buildQmlElement", Q_ARG(QUrl, url)); QMetaObject::invokeMethod(this, "buildQmlElement", Q_ARG(QUrl, url));
return; return;
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>(); offscreenUI->load(url, [=](QQmlContext* context, QObject* object) {
offscreenUi->load(url, [=](QQmlContext* context, QObject* object) {
_qmlElement = dynamic_cast<QQuickItem*>(object); _qmlElement = dynamic_cast<QQuickItem*>(object);
connect(_qmlElement, &QObject::destroyed, this, &QmlOverlay::qmlElementDestroyed); connect(_qmlElement, &QObject::destroyed, this, &QmlOverlay::qmlElementDestroyed);
}); });

View file

@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin
return position; return position;
} }
// Get the scale factor to convert distances in the geometry frame into the unscaled rig frame.
// Typically it will be the unit conversion from cm to m.
float Rig::GetScaleFactorGeometryToUnscaledRig() const {
// Normally the model offset transform will contain the avatar scale factor; we explicitly remove it here.
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
return geomToRigWithoutAvatarScale.scale().x; // in practice this is always a uniform scale factor.
}
void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated,
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose, const AnimPose& leftHandPose, const AnimPose& rightHandPose,
@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule(
Extents totalExtents; Extents totalExtents;
totalExtents.reset(); totalExtents.reset();
// HACK by convention our Avatars are always modeled such that y=0 is the ground plane. // HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane.
// add the zero point so that our avatars will always have bounding volumes that are flush with the ground // add the ground point so that our avatars will always have bounding volumes that are flush with the ground
// even if they do not have legs (default robot) // even if they do not have legs (default robot)
totalExtents.addPoint(glm::vec3(0.0f)); totalExtents.addPoint(glm::vec3(0.0f, GEOMETRY_GROUND_Y, 0.0f));
// To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
// from the head to the hips when computing the rest of the bounding capsule. // from the head to the hips when computing the rest of the bounding capsule.
@ -2747,24 +2757,20 @@ void Rig::initFlow(bool isActive) {
} }
} }
// Get the vertical position of eye joints, in the rig coordinate frame, ignoring the avatar scale.
float Rig::getUnscaledEyeHeight() const { float Rig::getUnscaledEyeHeight() const {
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here.
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame. // Factor to scale distances in the geometry frame into the unscaled rig frame.
// Typically it will be the unit conversion from cm to m. float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
int headTopJoint = indexOfJoint("HeadTop_End"); int headTopJoint = indexOfJoint("HeadTop_End");
int headJoint = indexOfJoint("Head"); int headJoint = indexOfJoint("Head");
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase");
// Makes assumption that the y = 0 plane in geometry is the ground plane.
// We also make that assumption in Rig::computeAvatarBoundingCapsule()
const float GROUND_Y = 0.0f;
// Values from the skeleton are in the geometry coordinate frame. // Values from the skeleton are in the geometry coordinate frame.
auto skeleton = getAnimSkeleton(); auto skeleton = getAnimSkeleton();
if (eyeJoint >= 0 && toeJoint >= 0) { if (eyeJoint >= 0 && toeJoint >= 0) {
@ -2772,8 +2778,8 @@ float Rig::getUnscaledEyeHeight() const {
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
return scaleFactor * eyeHeight; return scaleFactor * eyeHeight;
} else if (eyeJoint >= 0) { } else if (eyeJoint >= 0) {
// Measure Eye joint to y = 0 plane. // Measure Eye joint to ground plane.
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * eyeHeight; return scaleFactor * eyeHeight;
} else if (headTopJoint >= 0 && toeJoint >= 0) { } else if (headTopJoint >= 0 && toeJoint >= 0) {
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const {
} else if (headTopJoint >= 0) { } else if (headTopJoint >= 0) {
// Measure from HeadTop_End joint to the ground, then remove forehead distance. // Measure from HeadTop_End joint to the ground, then remove forehead distance.
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * (headHeight - headHeight * ratio); return scaleFactor * (headHeight - headHeight * ratio);
} else if (headJoint >= 0) { } else if (headJoint >= 0) {
// Measure Head joint to the ground, then add in distance from neck to eye. // Measure Head joint to the ground, then add in distance from neck to eye.
const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * (neckHeight + neckHeight * ratio); return scaleFactor * (neckHeight + neckHeight * ratio);
} else { } else {
return DEFAULT_AVATAR_EYE_HEIGHT; return DEFAULT_AVATAR_EYE_HEIGHT;
} }
} }
// Get the vertical position of the hips joint, in the rig coordinate frame, ignoring the avatar scale.
float Rig::getUnscaledHipsHeight() const {
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame.
float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
int hipsJoint = indexOfJoint("Hips");
// Values from the skeleton are in the geometry coordinate frame.
if (hipsJoint >= 0) {
// Measure hip joint to ground plane.
float hipsHeight = getAnimSkeleton()->getAbsoluteDefaultPose(hipsJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * hipsHeight;
} else {
return DEFAULT_AVATAR_HIPS_HEIGHT;
}
}
void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) { void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) {
_animVars.set(targetName, blendingTarget); _animVars.set(targetName, blendingTarget);
_animVars.set(alphaName, alpha); _animVars.set(alphaName, alpha);

View file

@ -251,6 +251,7 @@ public:
Flow& getFlow() { return _internalFlow; } Flow& getFlow() { return _internalFlow; }
float getUnscaledEyeHeight() const; float getUnscaledEyeHeight() const;
float getUnscaledHipsHeight() const;
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const; void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const;
int getOverrideJointCount() const; int getOverrideJointCount() const;
@ -287,6 +288,11 @@ protected:
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const;
// Get the scale factor to convert distances in the geometry frame into the unscaled rig frame.
float GetScaleFactorGeometryToUnscaledRig() const;
// The ground plane Y position in geometry space.
static constexpr float GEOMETRY_GROUND_Y = 0.0f;
AnimPose _modelOffset; // model to rig space AnimPose _modelOffset; // model to rig space
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)

View file

@ -33,6 +33,7 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
{ {
// SkeletonModels, and by extention Avatars, use Dual Quaternion skinning. // SkeletonModels, and by extention Avatars, use Dual Quaternion skinning.
_useDualQuaternionSkinning = true; _useDualQuaternionSkinning = true;
_forceOffset = true;
// Avatars all cast shadow // Avatars all cast shadow
setCanCastShadow(true); setCanCastShadow(true);
@ -156,17 +157,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
updateAttitude(_owningAvatar->getWorldOrientation()); updateAttitude(_owningAvatar->getWorldOrientation());
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients()); setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
Parent::simulate(deltaTime, fullUpdate);
if (fullUpdate) { if (fullUpdate) {
Parent::simulate(deltaTime, fullUpdate);
// let rig compute the model offset // let rig compute the model offset
glm::vec3 registrationPoint; glm::vec3 registrationPoint;
if (_rig.getModelRegistrationPoint(registrationPoint)) { if (_rig.getModelRegistrationPoint(registrationPoint)) {
setOffset(registrationPoint); setOffset(registrationPoint);
} }
} else {
Parent::simulate(deltaTime, fullUpdate);
} }
// FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem, // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem,

View file

@ -136,8 +136,8 @@ public:
static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName); static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName);
static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName); static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName);
int getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; } size_t getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; }
int getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; } size_t getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; }
signals: signals:
void enterEntity(const EntityItemID& entityItemID); void enterEntity(const EntityItemID& entityItemID);
@ -253,8 +253,8 @@ private:
ReadWriteLockable _changedEntitiesGuard; ReadWriteLockable _changedEntitiesGuard;
std::unordered_set<EntityItemID> _changedEntities; std::unordered_set<EntityItemID> _changedEntities;
int _prevNumEntityUpdates { 0 }; size_t _prevNumEntityUpdates { 0 };
int _prevTotalNeededEntityUpdates { 0 }; size_t _prevTotalNeededEntityUpdates { 0 };
std::unordered_set<EntityRendererPointer> _renderablesToUpdate; std::unordered_set<EntityRendererPointer> _renderablesToUpdate;
std::unordered_map<EntityItemID, EntityRendererPointer> _entitiesInScene; std::unordered_map<EntityItemID, EntityRendererPointer> _entitiesInScene;

View file

@ -149,10 +149,12 @@ void RenderableModelEntityItem::updateModelBounds() {
bool overridingModelTransform = model->isOverridingModelTransformAndOffset(); bool overridingModelTransform = model->isOverridingModelTransformAndOffset();
glm::vec3 scaledDimensions = getScaledDimensions(); glm::vec3 scaledDimensions = getScaledDimensions();
glm::vec3 registrationPoint = getRegistrationPoint(); glm::vec3 registrationPoint = getRegistrationPoint();
bool needsSimulate = false;
if (!overridingModelTransform && if (!overridingModelTransform &&
(model->getScaleToFitDimensions() != scaledDimensions || (model->getScaleToFitDimensions() != scaledDimensions ||
model->getRegistrationPoint() != registrationPoint || model->getRegistrationPoint() != registrationPoint ||
!model->getIsScaledToFit() || _needsToRescaleModel)) { !model->getIsScaledToFit() || _needsToRescaleModel ||
_useOriginalPivot == model->getSnapModelToRegistrationPoint())) {
// The machinery for updateModelBounds will give existing models the opportunity to fix their // 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 // 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. // have guards to make sure they don't happen after they've already been set. Here we reset those guards.
@ -162,9 +164,9 @@ void RenderableModelEntityItem::updateModelBounds() {
// now recalculate the bounds and registration // now recalculate the bounds and registration
model->setScaleToFit(true, scaledDimensions); model->setScaleToFit(true, scaledDimensions);
model->setSnapModelToRegistrationPoint(true, registrationPoint); model->setSnapModelToRegistrationPoint(!_useOriginalPivot, registrationPoint);
updateRenderItems = true; updateRenderItems = true;
model->scaleToFit(); needsSimulate = true;
_needsToRescaleModel = false; _needsToRescaleModel = false;
} }
@ -176,10 +178,11 @@ void RenderableModelEntityItem::updateModelBounds() {
updateRenderItems = true; updateRenderItems = true;
} }
if (_needsInitialSimulation || _needsJointSimulation || isAnimatingSomething()) { if (_needsInitialSimulation || _needsJointSimulation || needsSimulate || isAnimatingSomething()) {
// NOTE: on isAnimatingSomething() we need to call Model::simulate() which calls Rig::updateRig() // NOTE: on isAnimatingSomething() we need to call Model::simulate() which calls Rig::updateRig()
// TODO: there is opportunity to further optimize the isAnimatingSomething() case. // TODO: there is opportunity to further optimize the isAnimatingSomething() case.
model->simulate(0.0f); model->simulate(0.0f);
locationChanged();
_needsInitialSimulation = false; _needsInitialSimulation = false;
_needsJointSimulation = false; _needsJointSimulation = false;
updateRenderItems = true; updateRenderItems = true;
@ -219,6 +222,16 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper
return properties; return properties;
} }
glm::vec3 RenderableModelEntityItem::getPivot() const {
auto model = getModel();
auto pivot = EntityItem::getPivot();
if (!model || !model->isLoaded() || !_useOriginalPivot) {
return pivot;
}
return pivot + model->getOriginalOffset();
}
bool RenderableModelEntityItem::supportsDetailedIntersection() const { bool RenderableModelEntityItem::supportsDetailedIntersection() const {
return true; return true;
} }
@ -443,14 +456,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
// multiply each point by scale before handing the point-set off to the physics engine. // multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model. // also determine the extents of the collision model.
glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
glm::vec3 offset = model->getSnapModelToRegistrationPoint() ? model->getOffset() : glm::vec3(0.0f);
for (int32_t i = 0; i < pointCollection.size(); i++) { for (int32_t i = 0; i < pointCollection.size(); i++) {
for (int32_t j = 0; j < pointCollection[i].size(); j++) { for (int32_t j = 0; j < pointCollection[i].size(); j++) {
// back compensate for registration so we can apply that offset to the shapeInfo later // back compensate for registration so we can apply that offset to the shapeInfo later
pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset; pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + offset) - registrationOffset;
} }
} }
shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL()); shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL() + model->getSnapModelToRegistrationPoint());
adjustShapeInfoByRegistration(shapeInfo); adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint());
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
updateModelBounds(); updateModelBounds();
model->updateGeometry(); model->updateGeometry();
@ -682,8 +696,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
} }
} }
shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL()); shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL() + model->getSnapModelToRegistrationPoint());
adjustShapeInfoByRegistration(shapeInfo); adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint());
} else { } else {
EntityItem::computeShapeInfo(shapeInfo); EntityItem::computeShapeInfo(shapeInfo);
} }

View file

@ -42,7 +42,7 @@ protected:
void setModel(const ModelPointer& model); void setModel(const ModelPointer& model);
ModelPointer getModel() const; ModelPointer getModel() const;
bool _needsInitialSimulation{ true }; bool _needsInitialSimulation { true };
private: private:
ModelPointer _model; ModelPointer _model;
}; };
@ -63,6 +63,7 @@ public:
virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override;
void updateModelBounds(); void updateModelBounds();
glm::vec3 getPivot() const override;
virtual bool supportsDetailedIntersection() const override; virtual bool supportsDetailedIntersection() const override;
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance, const glm::vec3& viewFrustumPos, OctreeElementPointer& element, float& distance,

View file

@ -676,8 +676,10 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
} }
void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) { void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) {
if (value != _registrationPoint) { if (value != getRegistrationPoint()) {
_shapeReady = false; withWriteLock([&] {
_shapeReady = false;
});
EntityItem::setRegistrationPoint(value); EntityItem::setRegistrationPoint(value);
startUpdates(); startUpdates();
} }

View file

@ -1609,8 +1609,13 @@ void EntityItem::recordCreationTime() {
const Transform EntityItem::getTransformToCenter(bool& success) const { const Transform EntityItem::getTransformToCenter(bool& success) const {
Transform result = getTransform(success); Transform result = getTransform(success);
if (getRegistrationPoint() != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center glm::vec3 pivot = getPivot();
result.postTranslate((ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()) * getScaledDimensions()); // Position to center if (pivot != ENTITY_ITEM_ZERO_VEC3) {
result.postTranslate(pivot);
}
glm::vec3 registrationPoint = getRegistrationPoint();
if (registrationPoint != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center
result.postTranslate((ENTITY_ITEM_HALF_VEC3 - registrationPoint) * getScaledDimensions()); // Position to center
} }
return result; return result;
} }
@ -1625,15 +1630,16 @@ AACube EntityItem::getMaximumAACube(bool& success) const {
_recalcMaxAACube = false; _recalcMaxAACube = false;
// we want to compute the furthestExtent that an entity can extend out from its "position" // we want to compute the furthestExtent that an entity can extend out from its "position"
// to do this we compute the max of these two vec3s: registration and 1-registration // to do this we compute the max of these two vec3s: registration and 1-registration
// and then scale by dimensions // and then scale by dimensions and add the absolute value of the pivot
glm::vec3 maxExtents = getScaledDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint); glm::vec3 registrationPoint = getRegistrationPoint();
glm::vec3 maxExtents = getScaledDimensions() * glm::max(registrationPoint, glm::vec3(1.0f) - registrationPoint);
// there exists a sphere that contains maxExtents for all rotations // there exists a sphere that contains maxExtents for all rotations
float radius = glm::length(maxExtents); float radius = glm::length(maxExtents);
// put a cube around the sphere // put a cube around the sphere
// TODO? replace _maxAACube with _boundingSphereRadius // TODO? replace _maxAACube with _boundingSphereRadius
glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); glm::vec3 minimumCorner = (centerOfRotation + getWorldOrientation() * getPivot()) - glm::vec3(radius, radius, radius);
_maxAACube = AACube(minimumCorner, radius * 2.0f); _maxAACube = AACube(minimumCorner, radius * 2.0f);
} }
} else { } else {
@ -1652,9 +1658,12 @@ AACube EntityItem::getMinimumAACube(bool& success) const {
if (success) { if (success) {
_recalcMinAACube = false; _recalcMinAACube = false;
glm::vec3 dimensions = getScaledDimensions(); glm::vec3 dimensions = getScaledDimensions();
glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint); glm::vec3 registrationPoint = getRegistrationPoint();
glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint); glm::vec3 pivot = getPivot();
glm::vec3 unrotatedMinRelativeToEntity = -(dimensions * registrationPoint);
glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (ENTITY_ITEM_ONE_VEC3 - registrationPoint);
Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
extents.shiftBy(pivot);
extents.rotate(getWorldOrientation()); extents.rotate(getWorldOrientation());
// shift the extents to be relative to the position/registration point // shift the extents to be relative to the position/registration point
@ -1682,9 +1691,12 @@ AABox EntityItem::getAABox(bool& success) const {
if (success) { if (success) {
_recalcAABox = false; _recalcAABox = false;
glm::vec3 dimensions = getScaledDimensions(); glm::vec3 dimensions = getScaledDimensions();
glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint); glm::vec3 registrationPoint = getRegistrationPoint();
glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint); glm::vec3 pivot = getPivot();
glm::vec3 unrotatedMinRelativeToEntity = -(dimensions * registrationPoint);
glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (ENTITY_ITEM_ONE_VEC3 - registrationPoint);
Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
extents.shiftBy(pivot);
extents.rotate(getWorldOrientation()); extents.rotate(getWorldOrientation());
// shift the extents to be relative to the position/registration point // shift the extents to be relative to the position/registration point
@ -1724,12 +1736,22 @@ float EntityItem::getRadius() const {
return 0.5f * glm::length(getScaledDimensions()); return 0.5f * glm::length(getScaledDimensions());
} }
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot) const {
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { glm::vec3 offset;
glm::mat4 scale = glm::scale(getScaledDimensions()); glm::vec3 registrationPoint = getRegistrationPoint();
glm::mat4 registration = scale * glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); if (registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
glm::vec3 regTransVec = glm::vec3(registration[3]); // extract position component from matrix offset += (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - registrationPoint) * getScaledDimensions();
info.setOffset(regTransVec); }
if (includePivot) {
glm::vec3 pivot = getPivot();
if (pivot != ENTITY_ITEM_ZERO_VEC3) {
offset += pivot;
}
}
if (offset != ENTITY_ITEM_ZERO_VEC3) {
info.setOffset(offset);
} }
} }
@ -1741,7 +1763,7 @@ bool EntityItem::contains(const glm::vec3& point) const {
// anything with shapeType == SPHERE must collide as a bounding sphere in the world-frame regardless of dimensions // anything with shapeType == SPHERE must collide as a bounding sphere in the world-frame regardless of dimensions
// therefore we must do math using an unscaled localPoint relative to sphere center // therefore we must do math using an unscaled localPoint relative to sphere center
glm::vec3 dimensions = getScaledDimensions(); glm::vec3 dimensions = getScaledDimensions();
glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()))); glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()) + getPivot()));
const float HALF_SQUARED = 0.25f; const float HALF_SQUARED = 0.25f;
return glm::length2(localPoint) < HALF_SQUARED * glm::length2(dimensions); return glm::length2(localPoint) < HALF_SQUARED * glm::length2(dimensions);
} }
@ -1799,11 +1821,16 @@ float EntityItem::getVolumeEstimate() const {
} }
void EntityItem::setRegistrationPoint(const glm::vec3& value) { void EntityItem::setRegistrationPoint(const glm::vec3& value) {
if (value != _registrationPoint) { bool changed = false;
withWriteLock([&] { withWriteLock([&] {
if (value != _registrationPoint) {
_registrationPoint = glm::clamp(value, glm::vec3(ENTITY_ITEM_MIN_REGISTRATION_POINT), _registrationPoint = glm::clamp(value, glm::vec3(ENTITY_ITEM_MIN_REGISTRATION_POINT),
glm::vec3(ENTITY_ITEM_MAX_REGISTRATION_POINT)); glm::vec3(ENTITY_ITEM_MAX_REGISTRATION_POINT));
}); changed = true;
}
});
if (changed) {
dimensionsChanged(); // Registration Point affects the bounding box dimensionsChanged(); // Registration Point affects the bounding box
markDirtyFlags(Simulation::DIRTY_SHAPE); markDirtyFlags(Simulation::DIRTY_SHAPE);
} }
@ -2894,11 +2921,9 @@ QString EntityItem::getCollisionSoundURL() const {
} }
glm::vec3 EntityItem::getRegistrationPoint() const { glm::vec3 EntityItem::getRegistrationPoint() const {
glm::vec3 result; return resultWithReadLock<glm::vec3>([&] {
withReadLock([&] { return _registrationPoint;
result = _registrationPoint;
}); });
return result;
} }
float EntityItem::getAngularDamping() const { float EntityItem::getAngularDamping() const {

View file

@ -204,6 +204,8 @@ public:
virtual glm::vec3 getScaledDimensions() const; virtual glm::vec3 getScaledDimensions() const;
virtual void setScaledDimensions(const glm::vec3& value); virtual void setScaledDimensions(const glm::vec3& value);
virtual glm::vec3 getPivot() const { return glm::vec3(0.0f); } // pivot offset for positioning, mainly for model entities
glm::vec3 getUnscaledDimensions() const; glm::vec3 getUnscaledDimensions() const;
virtual void setUnscaledDimensions(const glm::vec3& value); virtual void setUnscaledDimensions(const glm::vec3& value);
@ -402,7 +404,7 @@ public:
// TODO: get rid of users of getRadius()... // TODO: get rid of users of getRadius()...
float getRadius() const; float getRadius() const;
virtual void adjustShapeInfoByRegistration(ShapeInfo& info) const; virtual void adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot = true) const;
virtual bool contains(const glm::vec3& point) const; virtual bool contains(const glm::vec3& point) const;
virtual bool isReadyToComputeShape() const { return !isDead(); } virtual bool isReadyToComputeShape() const { return !isDead(); }

View file

@ -538,6 +538,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled); CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled);
CHECK_PROPERTY_CHANGE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); CHECK_PROPERTY_CHANGE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients);
CHECK_PROPERTY_CHANGE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot);
changedProperties += _animation.getChangedProperties(); changedProperties += _animation.getChangedProperties();
// Light // Light
@ -1004,6 +1005,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {string} blendshapeCoefficients - A JSON string of a map of blendshape names to values. Only stores set values. * @property {string} blendshapeCoefficients - A JSON string of a map of blendshape names to values. Only stores set values.
* When editing this property, only coefficients that you are editing will change; it will not explicitly reset other * When editing this property, only coefficients that you are editing will change; it will not explicitly reset other
* coefficients. * coefficients.
* @property {boolean} useOriginalPivot=false - If <code>false</code>, the model will be centered based on its content,
* ignoring any offset in the model itself. If <code>true</code>, the model will respect its original offset. Currently,
* only pivots relative to <code>{x: 0, y: 0, z: 0}</code> are supported.
* @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the
* model's original textures. Use a texture name from the <code>originalTextures</code> property to override that texture. * model's original textures. Use a texture name from the <code>originalTextures</code> property to override that texture.
* Only the texture names and URLs to be overridden need be specified; original textures are used where there are no * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no
@ -1733,6 +1737,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot);
if (!psuedoPropertyFlagsButDesiredEmpty) { if (!psuedoPropertyFlagsButDesiredEmpty) {
_animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
} }
@ -2139,6 +2144,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints);
COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled); COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled);
COPY_PROPERTY_FROM_QSCRIPTVALUE(blendshapeCoefficients, QString, setBlendshapeCoefficients); COPY_PROPERTY_FROM_QSCRIPTVALUE(blendshapeCoefficients, QString, setBlendshapeCoefficients);
COPY_PROPERTY_FROM_QSCRIPTVALUE(useOriginalPivot, bool, setUseOriginalPivot);
_animation.copyFromScriptValue(object, _defaultSettings); _animation.copyFromScriptValue(object, _defaultSettings);
// Light // Light
@ -2431,6 +2437,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(relayParentJoints); COPY_PROPERTY_IF_CHANGED(relayParentJoints);
COPY_PROPERTY_IF_CHANGED(groupCulled); COPY_PROPERTY_IF_CHANGED(groupCulled);
COPY_PROPERTY_IF_CHANGED(blendshapeCoefficients); COPY_PROPERTY_IF_CHANGED(blendshapeCoefficients);
COPY_PROPERTY_IF_CHANGED(useOriginalPivot);
_animation.merge(other._animation); _animation.merge(other._animation);
// Light // Light
@ -2787,6 +2794,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool); ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool);
ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool); ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool);
ADD_PROPERTY_TO_MAP(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString); ADD_PROPERTY_TO_MAP(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString);
ADD_PROPERTY_TO_MAP(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool);
{ // Animation { // Animation
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation);
@ -3229,6 +3237,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints()); APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints());
APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled()); APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled());
APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, properties.getBlendshapeCoefficients()); APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, properties.getBlendshapeCoefficients());
APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, properties.getUseOriginalPivot());
_staticAnimation.setProperties(properties); _staticAnimation.setProperties(properties);
_staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState);
@ -3716,6 +3725,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot);
properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
} }
@ -4122,6 +4132,7 @@ void EntityItemProperties::markAllChanged() {
_relayParentJointsChanged = true; _relayParentJointsChanged = true;
_groupCulledChanged = true; _groupCulledChanged = true;
_blendshapeCoefficientsChanged = true; _blendshapeCoefficientsChanged = true;
_useOriginalPivotChanged = true;
_animation.markAllChanged(); _animation.markAllChanged();
// Light // Light
@ -4694,6 +4705,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (blendshapeCoefficientsChanged()) { if (blendshapeCoefficientsChanged()) {
out += "blendshapeCoefficients"; out += "blendshapeCoefficients";
} }
if (useOriginalPivotChanged()) {
out += "useOriginalPivot";
}
getAnimation().listChangedProperties(out); getAnimation().listChangedProperties(out);
// Light // Light

View file

@ -302,6 +302,7 @@ public:
DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS); DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS);
DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false); DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false);
DEFINE_PROPERTY_REF(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString, ""); DEFINE_PROPERTY_REF(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString, "");
DEFINE_PROPERTY_REF(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool, false);
DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup);
// Light // Light

View file

@ -218,16 +218,17 @@ enum EntityPropertyList {
PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_6, PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_6,
PROP_GROUP_CULLED = PROP_DERIVED_7, PROP_GROUP_CULLED = PROP_DERIVED_7,
PROP_BLENDSHAPE_COEFFICIENTS = PROP_DERIVED_8, PROP_BLENDSHAPE_COEFFICIENTS = PROP_DERIVED_8,
PROP_USE_ORIGINAL_PIVOT = PROP_DERIVED_9,
// Animation // Animation
PROP_ANIMATION_URL = PROP_DERIVED_9, PROP_ANIMATION_URL = PROP_DERIVED_10,
PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_10, PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_11,
PROP_ANIMATION_FPS = PROP_DERIVED_11, PROP_ANIMATION_FPS = PROP_DERIVED_12,
PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_12, PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_13,
PROP_ANIMATION_PLAYING = PROP_DERIVED_13, PROP_ANIMATION_PLAYING = PROP_DERIVED_14,
PROP_ANIMATION_LOOP = PROP_DERIVED_14, PROP_ANIMATION_LOOP = PROP_DERIVED_15,
PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_15, PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_16,
PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_16, PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_17,
PROP_ANIMATION_HOLD = PROP_DERIVED_17, PROP_ANIMATION_HOLD = PROP_DERIVED_18,
// Light // Light
PROP_IS_SPOTLIGHT = PROP_DERIVED_0, PROP_IS_SPOTLIGHT = PROP_DERIVED_0,

View file

@ -205,10 +205,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
// (this is faster and more likely to cull results than the filter check below so we do it first) // (this is faster and more likely to cull results than the filter check below so we do it first)
bool success; bool success;
AABox entityBox = entity->getAABox(success); AABox entityBox = entity->getAABox(success);
if (!success) { if (!success || !entityBox.rayHitsBoundingSphere(origin, direction)) {
return;
}
if (!entityBox.rayHitsBoundingSphere(origin, direction)) {
return; return;
} }
@ -229,7 +226,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
glm::vec3 dimensions = entity->getScaledDimensions(); glm::vec3 dimensions = entity->getScaledDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint); glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions); AABox entityFrameBox(corner, dimensions);
@ -280,11 +277,12 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
bool result = false; bool result = false;
withReadLock([&] { withReadLock([&] {
foreach(EntityItemPointer entity, _entityItems) { foreach(EntityItemPointer entity, _entityItems) {
glm::vec3 entityCenter = entity->getWorldPosition(); bool success;
glm::vec3 entityCenter = entity->getCenterPosition(success);
float entityRadius = entity->getRadius(); float entityRadius = entity->getRadius();
// don't penetrate yourself // don't penetrate yourself
if (entityCenter == center && entityRadius == radius) { if (!success || (entityCenter == center && entityRadius == radius)) {
return; return;
} }
@ -352,15 +350,12 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3
// (this is faster and more likely to cull results than the filter check below so we do it first) // (this is faster and more likely to cull results than the filter check below so we do it first)
bool success; bool success;
AABox entityBox = entity->getAABox(success); AABox entityBox = entity->getAABox(success);
if (!success) {
return;
}
// Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane // Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane
// defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic, // defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic,
// the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection // the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection
// below // below
if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) { if (!success || !entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) {
return; return;
} }
@ -381,7 +376,7 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3
glm::vec3 dimensions = entity->getScaledDimensions(); glm::vec3 dimensions = entity->getScaledDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint); glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions); AABox entityFrameBox(corner, dimensions);
@ -451,7 +446,6 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra
bool success; bool success;
AABox entityBox = entity->getAABox(success); AABox entityBox = entity->getAABox(success);
// if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case
glm::vec3 penetration; glm::vec3 penetration;
if (success && entityBox.findSpherePenetration(position, radius, penetration)) { if (success && entityBox.findSpherePenetration(position, radius, penetration)) {
@ -470,10 +464,9 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra
float entityTrueRadius = dimensions.x / 2.0f; float entityTrueRadius = dimensions.x / 2.0f;
bool success; bool success;
if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { glm::vec3 center = entity->getCenterPosition(success);
if (success) { if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) {
foundEntities.push_back(entity->getID()); foundEntities.push_back(entity->getID());
}
} }
} else { } else {
// determine the worldToEntityMatrix that doesn't include scale because // determine the worldToEntityMatrix that doesn't include scale because
@ -484,7 +477,7 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint); glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions); AABox entityFrameBox(corner, dimensions);
@ -505,7 +498,6 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position,
bool success; bool success;
AABox entityBox = entity->getAABox(success); AABox entityBox = entity->getAABox(success);
// if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case
glm::vec3 penetration; glm::vec3 penetration;
if (success && entityBox.findSpherePenetration(position, radius, penetration)) { if (success && entityBox.findSpherePenetration(position, radius, penetration)) {
@ -524,10 +516,9 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position,
float entityTrueRadius = dimensions.x / 2.0f; float entityTrueRadius = dimensions.x / 2.0f;
bool success; bool success;
if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { glm::vec3 center = entity->getCenterPosition(success);
if (success) { if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) {
foundEntities.push_back(entity->getID()); foundEntities.push_back(entity->getID());
}
} }
} else { } else {
// determine the worldToEntityMatrix that doesn't include scale because // determine the worldToEntityMatrix that doesn't include scale because
@ -538,7 +529,7 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position,
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint); glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions); AABox entityFrameBox(corner, dimensions);
@ -581,12 +572,11 @@ void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position,
// NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the // NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the
// maximum bounding sphere, which is actually larger than our actual radius // maximum bounding sphere, which is actually larger than our actual radius
float entityTrueRadius = dimensions.x / 2.0f; float entityTrueRadius = dimensions.x / 2.0f;
bool success; bool success;
if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { glm::vec3 center = entity->getCenterPosition(success);
if (success) {
foundEntities.push_back(entity->getID()); if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) {
} foundEntities.push_back(entity->getID());
} }
} else { } else {
// determine the worldToEntityMatrix that doesn't include scale because // determine the worldToEntityMatrix that doesn't include scale because
@ -597,7 +587,7 @@ void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position,
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint); glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions); AABox entityFrameBox(corner, dimensions);
@ -618,6 +608,7 @@ void EntityTreeElement::evalEntitiesInCube(const AACube& cube, PickFilter search
bool success; bool success;
AABox entityBox = entity->getAABox(success); AABox entityBox = entity->getAABox(success);
// FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// FIXME - consider allowing the entity to determine penetration so that // FIXME - consider allowing the entity to determine penetration so that
// entities could presumably dull actuall hull testing if they wanted to // entities could presumably dull actuall hull testing if they wanted to
@ -648,6 +639,7 @@ void EntityTreeElement::evalEntitiesInBox(const AABox& box, PickFilter searchFil
bool success; bool success;
AABox entityBox = entity->getAABox(success); AABox entityBox = entity->getAABox(success);
// FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// FIXME - consider allowing the entity to determine penetration so that // FIXME - consider allowing the entity to determine penetration so that
// entities could presumably dull actuall hull testing if they wanted to // entities could presumably dull actuall hull testing if they wanted to
@ -686,7 +678,7 @@ void EntityTreeElement::evalEntitiesInFrustum(const ViewFrustum& frustum, PickFi
}); });
} }
void EntityTreeElement::getEntities(EntityItemFilter& filter, QVector<EntityItemPointer>& foundEntities) { void EntityTreeElement::getEntities(EntityItemFilter& filter, QVector<EntityItemPointer>& foundEntities) {
forEachEntity([&](EntityItemPointer entity) { forEachEntity([&](EntityItemPointer entity) {
if (filter(entity)) { if (filter(entity)) {
foundEntities.push_back(entity); foundEntities.push_back(entity);

View file

@ -73,6 +73,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d
COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled); COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(blendshapeCoefficients, getBlendshapeCoefficients); COPY_ENTITY_PROPERTY_TO_PROPERTIES(blendshapeCoefficients, getBlendshapeCoefficients);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(useOriginalPivot, getUseOriginalPivot);
withReadLock([&] { withReadLock([&] {
_animationProperties.getProperties(properties); _animationProperties.getProperties(properties);
}); });
@ -96,6 +97,7 @@ bool ModelEntityItem::setSubClassProperties(const EntityItemProperties& properti
SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled); SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(blendshapeCoefficients, setBlendshapeCoefficients); SET_ENTITY_PROPERTY_FROM_PROPERTIES(blendshapeCoefficients, setBlendshapeCoefficients);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(useOriginalPivot, setUseOriginalPivot);
withWriteLock([&] { withWriteLock([&] {
AnimationPropertyGroup animationProperties = _animationProperties; AnimationPropertyGroup animationProperties = _animationProperties;
@ -130,6 +132,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints);
READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled); READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled);
READ_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients); READ_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients);
READ_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot);
// grab a local copy of _animationProperties to avoid multiple locks // grab a local copy of _animationProperties to avoid multiple locks
int bytesFromAnimation; int bytesFromAnimation;
@ -169,6 +172,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams&
requestedProperties += PROP_RELAY_PARENT_JOINTS; requestedProperties += PROP_RELAY_PARENT_JOINTS;
requestedProperties += PROP_GROUP_CULLED; requestedProperties += PROP_GROUP_CULLED;
requestedProperties += PROP_BLENDSHAPE_COEFFICIENTS; requestedProperties += PROP_BLENDSHAPE_COEFFICIENTS;
requestedProperties += PROP_USE_ORIGINAL_PIVOT;
requestedProperties += _animationProperties.getEntityProperties(params); requestedProperties += _animationProperties.getEntityProperties(params);
return requestedProperties; return requestedProperties;
@ -198,6 +202,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints()); APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints());
APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled()); APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled());
APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, getBlendshapeCoefficients()); APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, getBlendshapeCoefficients());
APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, getUseOriginalPivot());
withReadLock([&] { withReadLock([&] {
_animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, _animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
@ -251,6 +256,7 @@ void ModelEntityItem::debugDump() const {
qCDebug(entities) << " model URL:" << getModelURL(); qCDebug(entities) << " model URL:" << getModelURL();
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
qCDebug(entities) << " blendshapeCoefficients:" << getBlendshapeCoefficients(); qCDebug(entities) << " blendshapeCoefficients:" << getBlendshapeCoefficients();
qCDebug(entities) << " useOrigialPivot:" << getUseOriginalPivot();
} }
void ModelEntityItem::setShapeType(ShapeType type) { void ModelEntityItem::setShapeType(ShapeType type) {
@ -713,3 +719,25 @@ QVector<float> ModelEntityItem::getBlendshapeCoefficientVector() {
return _blendshapeCoefficientsVector; return _blendshapeCoefficientsVector;
}); });
} }
void ModelEntityItem::setUseOriginalPivot(bool value) {
bool changed = false;
withWriteLock([&] {
if (_useOriginalPivot != value) {
_needsRenderUpdate = true;
_useOriginalPivot = value;
changed = true;
}
});
if (changed) {
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
locationChanged();
}
}
bool ModelEntityItem::getUseOriginalPivot() const {
return resultWithReadLock<bool>([&] {
return _useOriginalPivot;
});
}

View file

@ -119,6 +119,9 @@ public:
bool blendshapesChanged() const { return _blendshapesChanged; } bool blendshapesChanged() const { return _blendshapesChanged; }
QVector<float> getBlendshapeCoefficientVector(); QVector<float> getBlendshapeCoefficientVector();
bool getUseOriginalPivot() const;
void setUseOriginalPivot(bool useOriginalPivot);
private: private:
void setAnimationSettings(const QString& value); // only called for old bitstream format void setAnimationSettings(const QString& value); // only called for old bitstream format
bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); bool applyNewAnimationProperties(AnimationPropertyGroup newProperties);
@ -152,6 +155,7 @@ protected:
bool _relayParentJoints; bool _relayParentJoints;
bool _groupCulled { false }; bool _groupCulled { false };
QVariantMap _blendshapeCoefficientsMap; QVariantMap _blendshapeCoefficientsMap;
bool _useOriginalPivot { false };
ThreadSafeValueCache<QString> _compoundShapeURL; ThreadSafeValueCache<QString> _compoundShapeURL;

View file

@ -104,7 +104,7 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const {
auto checkEachPrimitive = [=](HFMMesh& mesh, QVector<int> indices, int primitiveSize) -> bool { auto checkEachPrimitive = [=](HFMMesh& mesh, QVector<int> indices, int primitiveSize) -> bool {
// Check whether the point is "behind" all the primitives. // Check whether the point is "behind" all the primitives.
// But first must transform from model-frame into mesh-frame // But first must transform from model-frame into mesh-frame
glm::vec3 transformedPoint = glm::vec3(glm::inverse(mesh.modelTransform) * glm::vec4(point, 1.0f)); glm::vec3 transformedPoint = glm::vec3(glm::inverse(offset * mesh.modelTransform) * glm::vec4(point, 1.0f));
int verticesSize = mesh.vertices.size(); int verticesSize = mesh.vertices.size();
for (int j = 0; for (int j = 0;
j < indices.size() - 2; // -2 in case the vertices aren't the right size -- we access j + 2 below j < indices.size() - 2; // -2 in case the vertices aren't the right size -- we access j + 2 below

View file

@ -283,6 +283,7 @@ enum class EntityVersion : PacketVersion {
ZoneOcclusion, ZoneOcclusion,
ModelBlendshapes, ModelBlendshapes,
TransparentWeb, TransparentWeb,
UseOriginalPivot,
AllBillboardMode, AllBillboardMode,
// Add new versions above here // Add new versions above here

View file

@ -107,12 +107,12 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const
static uint32_t _numCharacterControllers { 0 }; static uint32_t _numCharacterControllers { 0 };
CharacterController::CharacterController() { CharacterController::CharacterController(const FollowTimePerType& followTimeRemainingPerType) :
_followTimeRemainingPerType(followTimeRemainingPerType) {
_floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; _floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
_targetVelocity.setValue(0.0f, 0.0f, 0.0f); _targetVelocity.setValue(0.0f, 0.0f, 0.0f);
_followDesiredBodyTransform.setIdentity(); _followDesiredBodyTransform.setIdentity();
_followTimeRemaining = 0.0f;
_state = State::Hover; _state = State::Hover;
_isPushingUp = false; _isPushingUp = false;
_rayHitStartTime = 0; _rayHitStartTime = 0;
@ -350,64 +350,103 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity; btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
computeNewVelocity(dt, velocity); computeNewVelocity(dt, velocity);
const float MINIMUM_TIME_REMAINING = 0.005f; constexpr float MINIMUM_TIME_REMAINING = 0.005f;
const float MAX_DISPLACEMENT = 0.5f * _radius; static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true.");
_followTimeRemaining -= dt;
if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) {
btTransform bodyTransform = _rigidBody->getWorldTransform();
bool hasFollowTimeRemaining = false;
for (float followTime : _followTimeRemainingPerType) {
if (followTime > MINIMUM_TIME_REMAINING) {
hasFollowTimeRemaining = true;
break;
}
}
if (hasFollowTimeRemaining) {
const float MAX_DISPLACEMENT = 0.5f * _radius;
btTransform bodyTransform = _rigidBody->getWorldTransform();
btVector3 startPos = bodyTransform.getOrigin(); btVector3 startPos = bodyTransform.getOrigin();
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos;
btVector3 vel = deltaPos / _followTimeRemaining;
btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. btVector3 linearDisplacement(0.0f, 0.0f, 0.0f);
{
float horizontalTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Horizontal)];
float verticalTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Vertical)];
if (horizontalTime == FOLLOW_TIME_IMMEDIATE_SNAP) {
linearDisplacement.setX(deltaPos.x());
linearDisplacement.setZ(deltaPos.z());
} else if (horizontalTime > MINIMUM_TIME_REMAINING) {
linearDisplacement.setX((deltaPos.x() * dt) / horizontalTime);
linearDisplacement.setZ((deltaPos.z() * dt) / horizontalTime);
}
if (verticalTime == FOLLOW_TIME_IMMEDIATE_SNAP) {
linearDisplacement.setY(deltaPos.y());
} else if (verticalTime > MINIMUM_TIME_REMAINING) {
linearDisplacement.setY((deltaPos.y() * dt) / verticalTime);
}
linearDisplacement = clampLength(linearDisplacement, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling.
}
btVector3 endPos = startPos + linearDisplacement; btVector3 endPos = startPos + linearDisplacement;
// resolve the simple linearDisplacement // resolve the simple linearDisplacement
_followLinearDisplacement += linearDisplacement; _followLinearDisplacement += linearDisplacement;
// now for the rotational part... // now for the rotational part...
btQuaternion startRot = bodyTransform.getRotation(); btQuaternion startRot = bodyTransform.getRotation();
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
// startRot as default rotation // startRot as default rotation
btQuaternion endRot = startRot; btQuaternion endRot = startRot;
// the dot product between two quaternions is equal to +/- cos(angle/2) float rotationTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Rotation)];
// where 'angle' is that of the rotation between them if (rotationTime > MINIMUM_TIME_REMAINING) {
float qDot = desiredRot.dot(startRot); btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
// when the abs() value of the dot product is approximately 1.0 // the dot product between two quaternions is equal to +/- cos(angle/2)
// then the two rotations are effectively adjacent // where 'angle' is that of the rotation between them
const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees float qDot = desiredRot.dot(startRot);
if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
if (qDot < 0.0f) { // when the abs() value of the dot product is approximately 1.0
// the quaternions are actually on opposite hyperhemispheres // then the two rotations are effectively adjacent
// so we move one to agree with the other and negate qDot const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees
desiredRot = -desiredRot; if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
qDot = -qDot; if (qDot < 0.0f) {
// the quaternions are actually on opposite hyperhemispheres
// so we move one to agree with the other and negate qDot
desiredRot = -desiredRot;
qDot = -qDot;
}
btQuaternion deltaRot = desiredRot * startRot.inverse();
// the axis is the imaginary part, but scaled by sin(angle/2)
btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ());
axis /= sqrtf(1.0f - qDot * qDot);
// compute the angle we will resolve for this dt, but don't overshoot
float angle = 2.0f * acosf(qDot);
if (rotationTime != FOLLOW_TIME_IMMEDIATE_SNAP) {
if (dt < rotationTime) {
angle *= dt / rotationTime;
}
}
// accumulate rotation
deltaRot = btQuaternion(axis, angle);
_followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize();
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
endRot = deltaRot * startRot;
btVector3 swingDisplacement =
rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
_followLinearDisplacement += swingDisplacement;
} }
btQuaternion deltaRot = desiredRot * startRot.inverse();
// the axis is the imaginary part, but scaled by sin(angle/2)
btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ());
axis /= sqrtf(1.0f - qDot * qDot);
// compute the angle we will resolve for this dt, but don't overshoot
float angle = 2.0f * acosf(qDot);
if (dt < _followTimeRemaining) {
angle *= dt / _followTimeRemaining;
}
// accumulate rotation
deltaRot = btQuaternion(axis, angle);
_followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize();
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
endRot = deltaRot * startRot;
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
_followLinearDisplacement += swingDisplacement;
} }
_rigidBody->setWorldTransform(btTransform(endRot, endPos)); _rigidBody->setWorldTransform(btTransform(endRot, endPos));
} }
@ -606,8 +645,7 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) {
_parentVelocity = glmToBullet(velocity); _parentVelocity = glmToBullet(velocity);
} }
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) { void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) {
_followTimeRemaining = timeRemaining;
_followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset)); _followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));
} }

View file

@ -53,7 +53,20 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f;
class CharacterController : public btCharacterControllerInterface { class CharacterController : public btCharacterControllerInterface {
public: public:
CharacterController(); enum class FollowType : uint8_t {
Rotation,
Horizontal,
Vertical,
Count
};
// Remaining follow time for each FollowType
typedef std::array<float, static_cast<size_t>(FollowType::Count)> FollowTimePerType;
// Follow time value meaning that we should snap immediately to the target.
static constexpr float FOLLOW_TIME_IMMEDIATE_SNAP = FLT_MAX;
CharacterController(const FollowTimePerType& followTimeRemainingPerType);
virtual ~CharacterController(); virtual ~CharacterController();
bool needsRemoval() const; bool needsRemoval() const;
bool needsAddition() const; bool needsAddition() const;
@ -99,7 +112,8 @@ public:
void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const; void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
void setParentVelocity(const glm::vec3& parentVelocity); void setParentVelocity(const glm::vec3& parentVelocity);
void setFollowParameters(const glm::mat4& desiredWorldMatrix, float timeRemaining);
void setFollowParameters(const glm::mat4& desiredWorldMatrix);
float getFollowTime() const { return _followTime; } float getFollowTime() const { return _followTime; }
glm::vec3 getFollowLinearDisplacement() const; glm::vec3 getFollowLinearDisplacement() const;
glm::quat getFollowAngularDisplacement() const; glm::quat getFollowAngularDisplacement() const;
@ -144,7 +158,7 @@ public:
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
void setSeated(bool isSeated) { _isSeated = isSeated; } void setSeated(bool isSeated) { _isSeated = isSeated; }
bool getSeated() { return _isSeated; } bool getSeated() const { return _isSeated; }
void resetStuckCounter() { _numStuckSubsteps = 0; } void resetStuckCounter() { _numStuckSubsteps = 0; }
@ -178,7 +192,7 @@ protected:
btVector3 _preSimulationVelocity; btVector3 _preSimulationVelocity;
btVector3 _velocityChange; btVector3 _velocityChange;
btTransform _followDesiredBodyTransform; btTransform _followDesiredBodyTransform;
btScalar _followTimeRemaining; const FollowTimePerType& _followTimeRemainingPerType;
btTransform _characterBodyTransform; btTransform _characterBodyTransform;
btVector3 _position; btVector3 _position;
btQuaternion _rotation; btQuaternion _rotation;

View file

@ -125,7 +125,7 @@ Procedural::Procedural() {
opaqueStencil(_opaqueState); opaqueStencil(_opaqueState);
_transparentState->setCullMode(gpu::State::CULL_NONE); _transparentState->setCullMode(gpu::State::CULL_NONE);
_transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); _transparentState->setDepthTest(true, false, gpu::LESS_EQUAL);
_transparentState->setBlendFunction(true, _transparentState->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);

View file

@ -2027,7 +2027,7 @@ void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bo
for (auto& key : keys) { for (auto& key : keys) {
gpu::StatePointer state = gpu::StatePointer(new gpu::State()); gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL);
if (std::get<0>(key)) { if (std::get<0>(key)) {
PrepareStencil::testMask(*state); PrepareStencil::testMask(*state);
} else { } else {
@ -2135,7 +2135,7 @@ gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent, bool
auto pipeline = (transparent || forward) ? web_browser_forward : web_browser; auto pipeline = (transparent || forward) ? web_browser_forward : web_browser;
gpu::StatePointer state = gpu::StatePointer(new gpu::State()); gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setDepthTest(true, !transparent, gpu::LESS_EQUAL);
// FIXME: do we need a testMaskDrawNoAA? // FIXME: do we need a testMaskDrawNoAA?
PrepareStencil::testMaskDrawShapeNoAA(*state); PrepareStencil::testMaskDrawShapeNoAA(*state);
state->setBlendFunction(transparent, state->setBlendFunction(transparent,
@ -2207,7 +2207,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp
} else { } else {
state->setCullMode(gpu::State::CULL_BACK); state->setCullMode(gpu::State::CULL_BACK);
} }
state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setDepthTest(true, !config.isTransparent(), gpu::LESS_EQUAL);
if (config.hasDepthBias()) { if (config.hasDepthBias()) {
state->setDepthBias(1.0f); state->setDepthBias(1.0f);
state->setDepthBiasSlopeScale(1.0f); state->setDepthBiasSlopeScale(1.0f);

View file

@ -331,7 +331,11 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
// extents is the entity relative, scaled, centered extents of the entity // extents is the entity relative, scaled, centered extents of the entity
glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos); glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos);
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(rotation, _translation); glm::mat4 transRot = createMatFromQuatAndPos(rotation, _translation);
glm::mat4 modelToWorldMatrix = transRot;
if (!_snapModelToRegistrationPoint) {
modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset());
}
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated Extents modelExtents = getMeshExtents(); // NOTE: unrotated
@ -363,8 +367,12 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
calculateTriangleSets(hfmModel); calculateTriangleSets(hfmModel);
} }
glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); glm::mat4 meshToWorldMatrix = transRot;
glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; if (_snapModelToRegistrationPoint || _forceOffset) {
meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset));
} else {
meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
}
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@ -487,7 +495,11 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
// extents is the entity relative, scaled, centered extents of the entity // extents is the entity relative, scaled, centered extents of the entity
glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos); glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos);
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(rotation, _translation); glm::mat4 transRot = createMatFromQuatAndPos(rotation, _translation);
glm::mat4 modelToWorldMatrix = transRot;
if (!_snapModelToRegistrationPoint) {
modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset());
}
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated Extents modelExtents = getMeshExtents(); // NOTE: unrotated
@ -520,8 +532,12 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
calculateTriangleSets(hfmModel); calculateTriangleSets(hfmModel);
} }
glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); glm::mat4 meshToWorldMatrix = transRot;
glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; if (_snapModelToRegistrationPoint || _forceOffset) {
meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset));
} else {
meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
}
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@ -1188,17 +1204,8 @@ glm::vec3 Model::getNaturalDimensions() const {
} }
Extents Model::getMeshExtents() const { Extents Model::getMeshExtents() const {
if (!isLoaded()) { Extents extents = getUnscaledMeshExtents();
return Extents(); return { extents.minimum * _scale, extents.maximum * _scale };
}
const Extents& extents = getHFMModel().meshExtents;
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
// is captured in the offset matrix
glm::vec3 minimum = glm::vec3(getHFMModel().offset * glm::vec4(extents.minimum, 1.0f));
glm::vec3 maximum = glm::vec3(getHFMModel().offset * glm::vec4(extents.maximum, 1.0f));
Extents scaledExtents = { minimum * _scale, maximum * _scale };
return scaledExtents;
} }
Extents Model::getUnscaledMeshExtents() const { Extents Model::getUnscaledMeshExtents() const {
@ -1423,6 +1430,15 @@ void Model::snapToRegistrationPoint() {
_snappedToRegistrationPoint = true; _snappedToRegistrationPoint = true;
} }
glm::vec3 Model::getOriginalOffset() const {
Extents modelMeshExtents = getUnscaledMeshExtents();
glm::vec3 dimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum);
glm::vec3 offset = modelMeshExtents.minimum + (0.5f * dimensions);
glm::mat4 transform = glm::scale(_scale) * glm::translate(offset);
return transform[3];
}
void Model::setUseDualQuaternionSkinning(bool value) { void Model::setUseDualQuaternionSkinning(bool value) {
_useDualQuaternionSkinning = value; _useDualQuaternionSkinning = value;
} }
@ -1443,7 +1459,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
snapToRegistrationPoint(); snapToRegistrationPoint();
} }
// update the world space transforms for all joints // update the world space transforms for all joints
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); glm::mat4 parentTransform = glm::scale(_scale) * ((_snapModelToRegistrationPoint || _forceOffset) ?
glm::translate(_offset) : glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
updateRig(deltaTime, parentTransform); updateRig(deltaTime, parentTransform);
} }
} }

View file

@ -171,6 +171,7 @@ public:
void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint);
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
bool getSnappedToRegistrationPoint() { return _snappedToRegistrationPoint; }
virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void simulate(float deltaTime, bool fullUpdate = true);
virtual void updateClusterMatrices(); virtual void updateClusterMatrices();
@ -207,6 +208,7 @@ public:
void setOffset(const glm::vec3& offset); void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; } const glm::vec3& getOffset() const { return _offset; }
glm::vec3 getOriginalOffset() const;
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale = false); void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale = false);
@ -352,6 +354,7 @@ public:
virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override;
void scaleToFit(); void scaleToFit();
void snapToRegistrationPoint();
bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; } bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; }
void setUseDualQuaternionSkinning(bool value); void setUseDualQuaternionSkinning(bool value);
@ -413,14 +416,14 @@ protected:
bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space
bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point
glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to glm::vec3 _registrationPoint { glm::vec3(0.5f) }; /// the point in model space our center is snapped to
bool _forceOffset { false };
std::vector<MeshState> _meshStates; std::vector<MeshState> _meshStates;
virtual void initJointStates(); virtual void initJointStates();
void setScaleInternal(const glm::vec3& scale); void setScaleInternal(const glm::vec3& scale);
void snapToRegistrationPoint();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform); virtual void updateRig(float deltaTime, glm::mat4 parentTransform);

View file

@ -276,7 +276,7 @@ void Font::setupGPU() {
for (auto& key : keys) { for (auto& key : keys) {
auto state = std::make_shared<gpu::State>(); auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK); state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL);
state->setBlendFunction(std::get<0>(key), state->setBlendFunction(std::get<0>(key),
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);

View file

@ -20,6 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters
const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters
const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD;
const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
const float DEFAULT_AVATAR_HIPS_HEIGHT = 1.01327407f; // meters
const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f; const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f;
const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;

View file

@ -64,6 +64,8 @@ LogHandler::LogHandler() {
_shouldOutputThreadID = true; _shouldOutputThreadID = true;
} else if (option == "milliseconds") { } else if (option == "milliseconds") {
_shouldDisplayMilliseconds = true; _shouldDisplayMilliseconds = true;
} else if (option == "keep_repeats") {
_keepRepeats = true;
} else if (option != "") { } else if (option != "") {
fprintf(stdout, "Unrecognized option in VIRCADIA_LOG_OPTIONS: '%s'\n", option.toUtf8().constData()); fprintf(stdout, "Unrecognized option in VIRCADIA_LOG_OPTIONS: '%s'\n", option.toUtf8().constData());
} }
@ -202,7 +204,18 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
resetColor = colorReset(); resetColor = colorReset();
} }
fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor); if (_keepRepeats || _previousMessage != message) {
if (_repeatCount > 0) {
fprintf(stdout, "[Previous message was repeated %i times]\n", _repeatCount);
}
fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor);
_repeatCount = 0;
} else {
_repeatCount++;
}
_previousMessage = message;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
// On windows, this will output log lines into the Visual Studio "output" tab // On windows, this will output log lines into the Visual Studio "output" tab
OutputDebugStringA(qPrintable(logMessage)); OutputDebugStringA(qPrintable(logMessage));

View file

@ -67,6 +67,11 @@ private:
bool _shouldOutputThreadID { false }; bool _shouldOutputThreadID { false };
bool _shouldDisplayMilliseconds { false }; bool _shouldDisplayMilliseconds { false };
bool _useColor { false }; bool _useColor { false };
bool _keepRepeats { false };
QString _previousMessage;
int _repeatCount { 0 };
int _currentMessageID { 0 }; int _currentMessageID { 0 };
struct RepeatedMessageRecord { struct RepeatedMessageRecord {

View file

@ -26,14 +26,15 @@ static void quickViewDeleter(QQuickView* quickView) {
} }
DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent) { DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
auto qmlEngine = offscreenUi->getSurfaceContext()->engine(); auto qmlEngine = offscreenUI->getSurfaceContext()->engine();
_quickView = std::shared_ptr<QQuickView>(new QQuickView(qmlEngine, nullptr), quickViewDeleter); _quickView = std::shared_ptr<QQuickView>(new QQuickView(qmlEngine, nullptr), quickViewDeleter);
_quickView->setFormat(getDefaultOpenGLSurfaceFormat()); _quickView->setFormat(getDefaultOpenGLSurfaceFormat());
QWidget* widget = QWidget::createWindowContainer(_quickView.get()); QWidget* widget = QWidget::createWindowContainer(_quickView.get());
setWidget(widget); setWidget(widget);
QWidget* headerWidget = new QWidget(); QWidget* headerWidget = new QWidget();
setTitleBarWidget(headerWidget); setTitleBarWidget(headerWidget);
}
} }
void DockWidget::setSource(const QUrl& url) { void DockWidget::setSource(const QUrl& url) {

View file

@ -65,17 +65,18 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue
} }
infoVersion.set(version); infoVersion.set(version);
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>(); if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
QString infoViewName(NAME + "_" + path); QString infoViewName(NAME + "_" + path);
offscreenUi->show(QML, NAME + "_" + path, [=](QQmlContext* context, QObject* newObject){ offscreenUI->show(QML, NAME + "_" + path, [=] (QQmlContext* context, QObject* newObject) {
QQuickItem* item = dynamic_cast<QQuickItem*>(newObject); QQuickItem* item = dynamic_cast<QQuickItem*>(newObject);
item->setWidth(1024); item->setWidth(1024);
item->setHeight(720); item->setHeight(720);
InfoView* newInfoView = newObject->findChild<InfoView*>(); InfoView* newInfoView = newObject->findChild<InfoView*>();
Q_ASSERT(newInfoView); Q_ASSERT(newInfoView);
newInfoView->parent()->setObjectName(infoViewName); newInfoView->parent()->setObjectName(infoViewName);
newInfoView->setUrl(url); newInfoView->setUrl(url);
}); });
}
} }
QUrl InfoView::url() { QUrl InfoView::url() {

View file

@ -53,25 +53,30 @@ private:
} \ } \
\ \
void x::show(std::function<void(QQmlContext*, QObject*)> f) { \ void x::show(std::function<void(QQmlContext*, QObject*)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ auto offscreenUI = DependencyManager::get<OffscreenUi>(); \
if (!registered) { \ if (!registered) { \
x::registerType(); \ x::registerType(); \
} \ } \
offscreenUi->show(QML, NAME, f); \ if (offscreenUI) { \
offscreenUI->show(QML, NAME, f); \
} \
} \ } \
\ \
void x::hide() { \ void x::hide() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->hide(NAME); \ offscreenUI->hide(NAME); \
} \
} \ } \
\ \
void x::toggle(std::function<void(QQmlContext*, QObject*)> f) { \ void x::toggle(std::function<void(QQmlContext*, QObject*)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->toggle(QML, NAME, f); \ offscreenUI->toggle(QML, NAME, f); \
} \
} \ } \
void x::load(std::function<void(QQmlContext*, QObject*)> f) { \ void x::load(std::function<void(QQmlContext*, QObject*)> f) { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->load(QML, f); \ offscreenUI->load(QML, f); \
} \
} }
#define HIFI_QML_DEF_LAMBDA(x, f) \ #define HIFI_QML_DEF_LAMBDA(x, f) \
@ -82,21 +87,25 @@ private:
qmlRegisterType<x>("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \ qmlRegisterType<x>("Hifi", 1, 0, NAME.toLocal8Bit().constData()); \
} \ } \
void x::show() { \ void x::show() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->show(QML, NAME, f); \ offscreenUI->show(QML, NAME, f); \
} \
} \ } \
void x::hide() { \ void x::hide() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->hide(NAME); \ offscreenUI->hide(NAME); \
} \
} \ } \
\ \
void x::toggle() { \ void x::toggle() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->toggle(QML, NAME, f); \ offscreenUI->toggle(QML, NAME, f); \
} \
} \ } \
void x::load() { \ void x::load() { \
auto offscreenUi = DependencyManager::get<OffscreenUi>(); \ if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) { \
offscreenUi->load(QML, f); \ offscreenUI->load(QML, f); \
} \
} }
#endif #endif

View file

@ -189,10 +189,12 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(
} }
void OffscreenUi::hideDesktopWindows() { void OffscreenUi::hideDesktopWindows() {
if (QThread::currentThread() != thread()) { if (_desktop) {
BLOCKING_INVOKE_METHOD(this, "hideDesktopWindows"); if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "hideDesktopWindows");
}
QMetaObject::invokeMethod(_desktop, "hideDesktopWindows");
} }
QMetaObject::invokeMethod(_desktop, "hideDesktopWindows");
} }
void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) { void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
@ -208,11 +210,14 @@ void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<voi
} }
bool OffscreenUi::isPointOnDesktopWindow(QVariant point) { bool OffscreenUi::isPointOnDesktopWindow(QVariant point) {
QVariant result; if (_desktop) {
BLOCKING_INVOKE_METHOD(_desktop, "isPointOnWindow", QVariant result;
Q_RETURN_ARG(QVariant, result), BLOCKING_INVOKE_METHOD(_desktop, "isPointOnWindow",
Q_ARG(QVariant, point)); Q_RETURN_ARG(QVariant, result),
return result.toBool(); Q_ARG(QVariant, point));
return result.toBool();
}
return false;
} }
void OffscreenUi::hide(const QString& name) { void OffscreenUi::hide(const QString& name) {
@ -226,12 +231,14 @@ void OffscreenUi::hide(const QString& name) {
} }
bool OffscreenUi::isVisible(const QString& name) { bool OffscreenUi::isVisible(const QString& name) {
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name); auto rootItem = getRootItem();
if (item) { if (rootItem) {
return QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).read().toBool(); QQuickItem* item = rootItem->findChild<QQuickItem*>(name);
} else { if (item) {
return false; return QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).read().toBool();
}
} }
return false;
} }
class MessageBoxListener : public ModalDialogListener { class MessageBoxListener : public ModalDialogListener {
@ -280,12 +287,11 @@ QQuickItem* OffscreenUi::createMessageBox(Icon icon, const QString& title, const
bool invokeResult; bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox", invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox",
Q_RETURN_ARG(QVariant, result), Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, QVariant::fromValue(map))); Q_ARG(QVariant, QVariant::fromValue(map)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "messageBox", invokeResult = QMetaObject::invokeMethod(tabletRoot, "messageBox",
Q_RETURN_ARG(QVariant, result), Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, QVariant::fromValue(map))); Q_ARG(QVariant, QVariant::fromValue(map)));
@ -533,21 +539,21 @@ ModalDialogListener* OffscreenUi::customInputDialogAsync(const Icon icon, const
} }
void OffscreenUi::togglePinned() { void OffscreenUi::togglePinned() {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned"); bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "togglePinned");
if (!invokeResult) { if (!invokeResult) {
qWarning() << "Failed to toggle window visibility"; qWarning() << "Failed to toggle window visibility";
} }
} }
void OffscreenUi::setPinned(bool pinned) { void OffscreenUi::setPinned(bool pinned) {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned));
if (!invokeResult) { if (!invokeResult) {
qWarning() << "Failed to set window visibility"; qWarning() << "Failed to set window visibility";
} }
} }
void OffscreenUi::setConstrainToolbarToCenterX(bool constrained) { void OffscreenUi::setConstrainToolbarToCenterX(bool constrained) {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_ARG(QVariant, constrained)); bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_ARG(QVariant, constrained));
if (!invokeResult) { if (!invokeResult) {
qWarning() << "Failed to set toolbar constraint"; qWarning() << "Failed to set toolbar constraint";
} }
@ -575,17 +581,17 @@ QQuickItem* OffscreenUi::createInputDialog(const Icon icon, const QString& title
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
bool invokeResult; bool invokeResult;
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog", invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
Q_RETURN_ARG(QVariant, result), Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, QVariant::fromValue(map))); Q_ARG(QVariant, QVariant::fromValue(map)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog", invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog",
Q_RETURN_ARG(QVariant, result), Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, QVariant::fromValue(map))); Q_ARG(QVariant, QVariant::fromValue(map)));
emit tabletScriptingInterface->tabletNotification(); emit tabletScriptingInterface->tabletNotification();
} }
if (!invokeResult) { if (!invokeResult) {
qWarning() << "Failed to create message box"; qWarning() << "Failed to create message box";
return nullptr; return nullptr;
@ -603,12 +609,11 @@ QQuickItem* OffscreenUi::createCustomInputDialog(const Icon icon, const QString&
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
bool invokeResult; bool invokeResult;
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog", invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
Q_RETURN_ARG(QVariant, result), Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, QVariant::fromValue(map))); Q_ARG(QVariant, QVariant::fromValue(map)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog", invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog",
Q_RETURN_ARG(QVariant, result), Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, QVariant::fromValue(map))); Q_ARG(QVariant, QVariant::fromValue(map)));
@ -718,7 +723,7 @@ QObject* OffscreenUi::getRootMenu() {
} }
void OffscreenUi::unfocusWindows() { void OffscreenUi::unfocusWindows() {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows"); bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "unfocusWindows");
Q_ASSERT(invokeResult); Q_ASSERT(invokeResult);
} }
@ -752,12 +757,11 @@ QString OffscreenUi::fileDialog(const QVariantMap& properties) {
bool invokeResult; bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog", invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog", invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
@ -782,12 +786,11 @@ ModalDialogListener* OffscreenUi::fileDialogAsync(const QVariantMap& properties)
bool invokeResult; bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog", invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog", invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
@ -1003,12 +1006,11 @@ QString OffscreenUi::assetDialog(const QVariantMap& properties) {
bool invokeResult; bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog", invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog", invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
@ -1034,12 +1036,11 @@ ModalDialogListener *OffscreenUi::assetDialogAsync(const QVariantMap& properties
bool invokeResult; bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode() && _desktop) {
invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog", invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));
} else { } else if (QQuickItem* tabletRoot = tablet->getTabletRoot()) {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog", invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog",
Q_RETURN_ARG(QVariant, buildDialogResult), Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties))); Q_ARG(QVariant, QVariant::fromValue(properties)));

View file

@ -14,9 +14,6 @@
#include <shared/QtHelpers.h> #include <shared/QtHelpers.h>
#include "OffscreenUi.h"
std::mutex QmlFragmentClass::_mutex; std::mutex QmlFragmentClass::_mutex;
std::map<QString, QScriptValue> QmlFragmentClass::_fragments; std::map<QString, QScriptValue> QmlFragmentClass::_fragments;
@ -40,7 +37,6 @@ QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QSc
} }
auto properties = parseArguments(context); auto properties = parseArguments(context);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QmlFragmentClass* retVal = new QmlFragmentClass(restricted, qml.toString()); QmlFragmentClass* retVal = new QmlFragmentClass(restricted, qml.toString());
Q_ASSERT(retVal); Q_ASSERT(retVal);
if (QThread::currentThread() != qApp->thread()) { if (QThread::currentThread() != qApp->thread()) {

View file

@ -14,7 +14,6 @@
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
#include <shared/QtHelpers.h> #include <shared/QtHelpers.h>
#include "OffscreenUi.h"
static const char* const URL_PROPERTY = "source"; static const char* const URL_PROPERTY = "source";
static const char* const SCRIPT_PROPERTY = "scriptUrl"; static const char* const SCRIPT_PROPERTY = "scriptUrl";
@ -22,7 +21,6 @@ static const char* const SCRIPT_PROPERTY = "scriptUrl";
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) {
auto properties = parseArguments(context); auto properties = parseArguments(context);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted); QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted);
Q_ASSERT(retVal); Q_ASSERT(retVal);
if (QThread::currentThread() != qApp->thread()) { if (QThread::currentThread() != qApp->thread()) {

View file

@ -72,7 +72,6 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) {
auto properties = parseArguments(context); auto properties = parseArguments(context);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QmlWindowClass* retVal = new QmlWindowClass(restricted); QmlWindowClass* retVal = new QmlWindowClass(restricted);
Q_ASSERT(retVal); Q_ASSERT(retVal);
if (QThread::currentThread() != qApp->thread()) { if (QThread::currentThread() != qApp->thread()) {
@ -349,7 +348,6 @@ void QmlWindowClass::raise() {
return; return;
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (_qmlWindow) { if (_qmlWindow) {
QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection); QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection);
} }

View file

@ -321,8 +321,8 @@ void TabletScriptingInterface::processEvent(const QKeyEvent* event) {
QObject* TabletScriptingInterface::getFlags() { QObject* TabletScriptingInterface::getFlags() {
Q_ASSERT(QThread::currentThread() == qApp->thread()); Q_ASSERT(QThread::currentThread() == qApp->thread());
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
return offscreenUi->getFlags(); return offscreenUI ? offscreenUI->getFlags() : nullptr;
} }
// //
@ -364,8 +364,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
_toolbarMode = toolbarMode; _toolbarMode = toolbarMode;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (toolbarMode) { if (toolbarMode) {
#if !defined(DISABLE_QML) #if !defined(DISABLE_QML)
closeDialog(); closeDialog();
@ -388,13 +386,18 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) { if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) {
loadHomeScreen(true); loadHomeScreen(true);
} }
//check if running scripts window opened and save it for reopen in Tablet
if (offscreenUi->isVisible("RunningScripts")) { auto offscreenUI = DependencyManager::get<OffscreenUi>();
offscreenUi->hide("RunningScripts"); if (offscreenUI) {
_showRunningScripts = true; //check if running scripts window opened and save it for reopen in Tablet
if (offscreenUI->isVisible("RunningScripts")) {
offscreenUI->hide("RunningScripts");
_showRunningScripts = true;
}
offscreenUI->hideDesktopWindows();
} }
offscreenUi->hideDesktopWindows();
// destroy desktop window // destroy desktop window
if (_desktopWindow) { if (_desktopWindow) {
_desktopWindow->deleteLater(); _desktopWindow->deleteLater();
@ -577,9 +580,9 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) {
root = _desktopWindow->asQuickItem(); root = _desktopWindow->asQuickItem();
} }
if (root) { auto offscreenUI = DependencyManager::get<OffscreenUi>();
auto offscreenUi = DependencyManager::get<OffscreenUi>(); if (root && offscreenUI) {
QObject* menu = offscreenUi->getRootMenu(); QObject* menu = offscreenUI->getRootMenu();
QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu)));
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
_state = State::Menu; _state = State::Menu;

View file

@ -203,14 +203,14 @@ void updateFromOpenVrKeyboardInput() {
} }
void finishOpenVrKeyboardInput() { void finishOpenVrKeyboardInput() {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUI = DependencyManager::get<OffscreenUi>();
updateFromOpenVrKeyboardInput(); updateFromOpenVrKeyboardInput();
// Simulate an enter press on the top level window to trigger the action // Simulate an enter press on the top level window to trigger the action
if (0 == (_currentHints & Qt::ImhMultiLine)) { if (0 == (_currentHints & Qt::ImhMultiLine) && offscreenUI) {
auto keyPress = QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n")); auto keyPress = QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"));
auto keyRelease = QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers()); auto keyRelease = QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers());
qApp->sendEvent(offscreenUi->getWindow(), &keyPress); qApp->sendEvent(offscreenUI->getWindow(), &keyPress);
qApp->sendEvent(offscreenUi->getWindow(), &keyRelease); qApp->sendEvent(offscreenUI->getWindow(), &keyRelease);
} }
} }
@ -221,10 +221,8 @@ void enableOpenVrKeyboard(PluginContainer* container) {
if (disableSteamVrKeyboard) { if (disableSteamVrKeyboard) {
return; return;
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>();
_overlay = vr::VROverlay(); _overlay = vr::VROverlay();
auto menu = container->getPrimaryMenu(); auto menu = container->getPrimaryMenu();
auto action = menu->getActionForOption(MenuOption::Overlays); auto action = menu->getActionForOption(MenuOption::Overlays);
@ -282,7 +280,9 @@ void handleOpenVrEvents() {
case vr::VREvent_KeyboardClosed: case vr::VREvent_KeyboardClosed:
_keyboardFocusObject = nullptr; _keyboardFocusObject = nullptr;
_keyboardShown = false; _keyboardShown = false;
DependencyManager::get<OffscreenUi>()->unfocusWindows(); if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
offscreenUI->unfocusWindows();
}
break; break;
default: default:

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@
}, },
"homepage": "https://github.com/highfidelity/hifi#readme", "homepage": "https://github.com/highfidelity/hifi#readme",
"devDependencies": { "devDependencies": {
"electron": "^6.0.12", "electron": "^7.2.4",
"electron-packager": "^14.0.6" "electron-packager": "^14.0.6"
}, },
"dependencies": { "dependencies": {

View file

@ -36,7 +36,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/inspect.js", "system/inspect.js",
"system/keyboardShortcuts/keyboardShortcuts.js", "system/keyboardShortcuts/keyboardShortcuts.js",
"system/checkForUpdates.js", "system/checkForUpdates.js",
"system/onFirstRun.js" "system/onFirstRun.js",
"system/appreciate/appreciate_app.js"
]; ];
var DEFAULT_SCRIPTS_SEPARATE = [ var DEFAULT_SCRIPTS_SEPARATE = [
"system/controllers/controllerScripts.js", "system/controllers/controllerScripts.js",

View file

@ -0,0 +1,40 @@
# Appreciate
## Description
Show someone else that you like what they're doing. Open the app to see usage instructions and some options!
## Releases
### v1.5 | [48d8247](https://github.com/highfidelity/hifi-content/commit/48d8247)
- Fixed an issue where Appreciate app users wearing avatars without a specific joint wouldn't hear the Appreciate sound or see the Appreciation Dodecahedron
### 2019-03-08_11-37-00 | Marketplace v1.4 | [93bf464](https://github.com/highfidelity/hifi-content/commit/93bf464)
- Fixed an issue where a user could press the "Z" key to appreciate while the Appreciate UI was focused even if the Appreciate switch was turned off
- Fixed an issue where Appreciation Intensity decayed too quickly after switching from HMD mode to Desktop mode
### 2019-02-22_10-49-00 | Marketplace v1.3 | [51704b5](https://github.com/highfidelity/hifi-content/commit/51704b5)
- Optimize app
- Add option to not show Appreciation Dodecahedron while Appreciating
- Forward Z keypresses to the Appreciate script that the user makes when the App's HTML UI is focused
### 2019-02-19_13-09-00 | Marketplace v1.2 | [0e2fa82](https://github.com/highfidelity/hifi-content/commit/0e2fa82)
- Introduced functionality to stop running versions of Appreciate when those versions are baked into the client installation AND other versions of Appreciate are running
### 2019-02-15_17-03-00 | Marketplace v1.1 | [83f8927](https://github.com/highfidelity/hifi-content/commit/83f8927)
- Ensure that old Appreciation Dodecahedrons will be deleted in the event of a client crashing while Appreciating
### 2019-02-14_10-00-00 | Marketplace v1.0 | [658ed4e](https://github.com/highfidelity/hifi-content/commit/658ed4e)
- Initial Release
## Project Links
[Trello Card](https://trello.com/c/2iMbEgdw/36-appreciation-app)
## Known issues
- N/A

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
{
"name": "Appreciate App",
"version": "1.5.0",
"description": "Show someone else that you like what they're doing. Open the app to see usage instructions and some options!",
"homepage": "http://www.vircadia.com",
"bugs": "",
"keywords": [
"Clapping",
"Clap",
"Applause"
],
"icon": "/appreciate.jpg",
"images": [
"/appreciate.jpg"
],
"author": {
"name": "High Fidelity",
"email": "",
"url": "",
"license": "Apache 2.0"
},
"sublicense": [
],
"contributors": [
],
"repository": {
"type": "git",
"url": "https://github.com/vircadia/vircadia-content.git"
},
"main": "/appreciate_app.js",
"type": "app",
"meta": {
},
"dependencies": {
},
"engines": {
},
"resource": {
"version": 1.0.0
}
}

View file

@ -0,0 +1,84 @@
<!DOCTYPE html>
<!--
Appreciate
Created by Zach Fox on 2019-01-30
Copyright 2019 High Fidelity, Inc.
Distributed under the Apache License, Version 2.0.
See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-->
<html>
<head>
<title>Appreciate</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/style.css">
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
<!--I modified jscolor to prevent it from printing the color code to the value of the input onchange. -->
<script type="text/javascript" src="js/jscolor.js"></script>
</head>
<body>
<div id="loadingContainer">
</div>
<div id="mainContainer">
<div id="titleBarContainer">
<div id="titleText" style="font-weight:bold;">
Appreciate v1.5
</div>
<label class="switch">
<input id="appreciateSwitch" type="checkbox" onclick="appreciateSwitchClicked(this)">
<span class="slider round"></span>
</label>
</div>
<div id="firstRun" style="display:none;">
<i id="tutorialArrow"></i>
<div>&nbsp;Use this switch to enable and disable the Appreciate app.</div>
</div>
<div id="progressBarContainer">
<span style="font-weight:bold;">Intensity Meter</span>
<div id="currentIntensityDisplay">
<span id="currentIntensity" style="width:0%"></span>
</div>
<div id="crosshatch"></div>
</div>
<div id="optionsContainer">
<span style="font-weight:bold;">Options</span>
<label id="neverWhistleContainer">
Never Whistle
<input id="neverWhistleCheckbox" type="checkbox" onclick="neverWhistleCheckboxClicked(this)">
<span class="checkmark"></span>
</label>
<label id="showAppreciationEntityContainer">
Show Appreciation Dodecahedron
<input id="showAppreciationEntityCheckbox" type="checkbox" onclick="showAppreciationEntityCheckboxClicked(this)">
<span class="checkmark"></span>
</label>
<div id="colorPickerContainer">
<input type="button" id="colorPicker" class="jscolor"
onchange="setEntityColor(this.jscolor)" value="Choose Dodecahedron Color">
</input>
</div>
</div>
<div id="instructions">
<div id="desktopModeInstructions">
Desktop Mode:<br/>Tap or hold the Z key on your keyboard!
</div>
<div id="vrModeInstructions">
VR Mode:<br/>Raise your hands above your head and shake them!
</div>
</div>
</div>
<!--Include this script here so it only executes once the DOM is ready.-->
<script type="text/javascript" src="js/appreciate_ui.js"></script>
</body>
</html>

View file

@ -0,0 +1,284 @@
*, *:before, *:after {
-webkit-box-sizing: inherit;
-moz-box-sizing: inherit;
box-sizing: inherit;
}
html {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
font-family: 'Raleway', sans-serif;
background-color: #393939;
color: #afafaf;
overflow: hidden;
margin: 0;
padding: 0;
}
#mainContainer {
width: 100vw;
height: 100vh;
}
#loadingContainer {
background-color: rgba(0, 0, 0, 0.8);
background-image: url('../images/loadingSpinner.svg');
background-repeat: no-repeat;
background-position: center center;
width: 100vw;
height: 100vh;
position: fixed;
z-index: 999;
}
#firstRun {
background-color: rgba(0, 0, 0, 0.9);
width: 100vw;
height: calc(100vh - 60px);
position: fixed;
z-index: 998;
padding: 8px 12px 0 50%;
font-size: 24px;
text-align: right;
}
#tutorialArrow {
border: solid #00b4ef;
border-width: 0 5px 5px 0;
margin-right: 28px;
display: inline-block;
padding: 5px;
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
}
/* START SWITCH CSS
Mostly from: https://www.w3schools.com/howto/howto_css_switch.asp
*/
#titleBarContainer {
display: flex;
align-items: center;
height: 60px;
padding: 0 16px;
font-size: 24px;
background-color: #121212;
color: #ffffff;
justify-content: space-between;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: block;
width: 70px;
height: 34px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #00b4ef;
}
input:focus + .slider {
box-shadow: 0 0 1px #00b4ef;
}
input:checked + .slider:before {
-webkit-transform: translateX(35px);
-ms-transform: translateX(35px);
transform: translateX(35px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
/* END SWITCH CSS */
/* START PROGRESS BAR CSS */
#progressBarContainer {
width: calc(100vw - 24px);
margin: 24px auto 0 auto;
}
#progressBarContainer > span {
font-size: 18px;
}
#currentIntensityDisplay {
width: 100%;
height: 175px;
margin-top: 8px;
background: #FFFFFF;
background-image: linear-gradient(to right, #EEE 0, #EEE 55%, #FFF 55%, #FFF 100%);
}
#crosshatch {
display: none;
float: right;
position: relative;
top: -175px;
height: 175px;
width: 45%;
background: repeating-linear-gradient(45deg, transparent 0px, transparent 4px, rgba(0, 0, 0, 0.1) 4px, rgba(0, 0, 0, 0.1) 8px);
}
#currentIntensity {
display: block;
height: 100%;
background-color: #1ac567;
background-image: linear-gradient(to right,#1ac567 0, #C62147 100%);
position: relative;
overflow: hidden;
}
/* END PROGRESS BAR CSS */
#optionsContainer {
display: flex;
flex-direction: column;
height: 150px;
width: calc(100vw - 24px);
margin: 12px 12px 0 12px;
position: absolute;
}
#colorPickerContainer {
margin: 8px 0 0 0;
visibility: hidden;
}
#colorPickerContainer > input {
font-family: 'Raleway', sans-serif;
height: 34px;
font-size: 18px;
min-width: 185px;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #eee;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
#neverWhistleContainer,
#showAppreciationEntityContainer {
display: block;
margin: 8px 0 0 0;
height: 25px;
position: relative;
padding-left: 35px;
cursor: pointer;
font-size: 18px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#showAppreciationEntityContainer {
margin-top: 16px;
}
/* Hide the browser's default checkbox */
#neverWhistleContainer input,
#showAppreciationEntityContainer input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* On mouse-over, add a grey background color */
#neverWhistleContainer:hover input ~ .checkmark,
#showAppreciationEntityContainer:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a blue background */
#neverWhistleContainer input:checked ~ .checkmark,
#showAppreciationEntityContainer input:checked ~ .checkmark {
background-color: #0093C5;
}
/* Show the checkmark when checked */
#neverWhistleContainer input:checked ~ .checkmark:after,
#showAppreciationEntityContainer input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
#neverWhistleContainer .checkmark:after,
#showAppreciationEntityContainer .checkmark:after {
left: 9px;
top: 3px;
width: 8px;
height: 15px;
border: solid white;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
}
#instructions {
position: fixed;
height: 150px;
bottom: 0;
left: 0;
right: 0;
margin: 0 12px;
font-size: 18px;
}
#instructions > div {
margin-top: 16px;
}

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 91.046409 92.5831"
x="0px"
y="0px"
version="1.1"
id="svg16"
sodipodi:docname="appreciate-a.svg"
width="91.04641"
height="92.583099"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata22">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>SCHOOL_ICONS_100</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs20" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview18"
showgrid="false"
inkscape:zoom="1.888"
inkscape:cx="46.168739"
inkscape:cy="33.542603"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg16" />
<title
id="title2">SCHOOL_ICONS_100</title>
<path
d="m 90.06874,62.450495 v -0.14 a 16.31,16.31 0 0 0 -1.58,-5.39 l -9.97,-27.22 a 5.72,5.72 0 0 0 -4.2,-4.08 7.27,7.27 0 0 0 -6.78,2 5.71,5.71 0 0 0 -1.37,5.67 v 0.17 l -13.51,-13.59 c -3.17,-3.17 -7.08,-2.58 -9.31,-0.36 a 6.31,6.31 0 0 0 -1.69,3 l -3.26,-3.26 a 6.42,6.42 0 0 0 -9.05,-0.33 6.83,6.83 0 0 0 -2.37,5.81 6.28,6.28 0 0 0 -6.32,5.71 l -3.68,-2.79 a 6.38,6.38 0 0 0 -6.460001,-1 7.12,7.12 0 0 0 -4.2399997,5.58 6.37,6.37 0 0 0 2.52,5.87 6.07,6.07 0 0 0 -2.87,2.07 c -1.92,2.53 -1.84,6.61 2,9.54 l 2.8099997,2.13 a 6.15,6.15 0 0 0 -3.1799997,2.3 c -1.76,2.49 -1.78,6.59 2.31,9.7 l 32.2300007,24.51 a 16.43,16.43 0 0 0 21.19,-1.08 16.49,16.49 0 0 0 15.06,-4.47 l 7,-7 a 16.49,16.49 0 0 0 4.79,-12.69 1.51,1.51 0 0 0 -0.07,-0.66 z m -65.9,-33.21 a 3.21,3.21 0 0 1 2.67,-1.48 3.72,3.72 0 0 1 1.65,0.41 v 0 l 0.33,0.37 a 6.15,6.15 0 0 0 -4.65,4.28 3.19,3.19 0 0 1 0,-3.58 z m -14.9100007,3.46 a 4.13,4.13 0 0 1 2.4300007,-3.16 3.36,3.36 0 0 1 3.49,0.58 l 7.79,5.9 1.56,1.18 c 0.05,0.13 0.1,0.26 0.16,0.39 a 9.06,9.06 0 0 0 1.75,2.4 l 2.47,2.48 a 6.24,6.24 0 0 0 -1.73,0.58 6.1,6.1 0 0 0 -2.9,3.17 l -8.47,-6.42 -0.15,-0.14 -4.910001,-3.72 a 3.36,3.36 0 0 1 -1.4899997,-3.24 z m 34.6600007,53.26 -32.21,-24.42 c -2.5200007,-1.91 -2.6600007,-4.18 -1.670001,-5.58 a 3.21,3.21 0 0 1 2.670001,-1.35 5.18,5.18 0 0 1 2.8,0.94 l 14.35,10.89 a 1.5,1.5 0 1 0 1.81,-2.39 l -13.72,-10.41 -0.11,-0.1 -0.22,-0.16 -7.8500007,-6 c -2.41,-1.82 -2.46,-4 -1.44,-5.33 1.02,-1.33 3.0900007,-1.86 5.4700007,-0.09 l 0.11,0.11 10,7.59 a 8.58,8.58 0 0 0 2.52,4.55 l 15,15 9.88,9.88 a 50.23,50.23 0 0 0 5.7,5.45 16.7,16.7 0 0 0 2.73,1.6 l 0.19,0.09 a 13.48,13.48 0 0 1 -16.01,-0.27 z m 39.25,-12.24 -7,7 a 13.51,13.51 0 0 1 -19.09,0 l -28.52,-28.62 a 5.35,5.35 0 0 1 -1.73,-3.34 3.14,3.14 0 0 1 1.71,-3.07 4.19,4.19 0 0 1 4.51,0.91 v 0 l 12.82,12.82 a 1.5,1.5 0 0 0 2.12,-2.12 l -19.43,-19.46 a 5.52,5.52 0 0 1 -1.39,-2.12 3.16,3.16 0 0 1 1.24,-3.8 c 1.13,-0.71 3,-0.78 4.94,1.14 0,0 0,0 0,0.07 l 19.43,19.41 a 1.5,1.5 0 0 0 2.12,-2.12 l -19.39,-19.42 a 0.76,0.76 0 0 0 -0.06,-0.08 l -4.35,-4.38 a 3.23,3.23 0 0 1 -0.41,-0.48 3.46,3.46 0 0 1 0.64,-4.88 3.44,3.44 0 0 1 5,0.19 l 23.81,23.8 a 1.5,1.5 0 0 0 2.03,-2.09 l -16.34,-16.38 c -1.87,-1.87 -1.53,-3.89 -0.36,-5.07 1.17,-1.18 3.19,-1.52 5.07,0.36 l 17.72,17.72 a 1.5,1.5 0 0 0 2.5,-1.48 l -1.68,-5.78 a 2.68,2.68 0 0 1 0.63,-2.77 4.3,4.3 0 0 1 3.9,-1.14 2.75,2.75 0 0 1 2.06,2.1 l 10,27.39 0.07,0.15 a 13.32,13.32 0 0 1 1.33,4.58 1.4,1.4 0 0 0 0,0.14 c 0,0 0,0.07 0,0.11 a 13.5,13.5 0 0 1 -3.9,10.71 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 7.2587393,5.1404947 a 1.5,1.5 0 1 0 -2.09,2.14 L 15.44874,17.440495 a 1.5,1.5 0 0 0 2.11,-2.13 z"
id="path6"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 11.83874,22.870495 a 1.5,1.5 0 0 0 0.4,-2.95 l -9.5000007,-2.63 a 1.5,1.5 0 0 0 -0.8,2.89 l 9.5000007,2.63 a 1.5,1.5 0 0 0 0.4,0.06 z"
id="path8"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 21.37874,0.88049471 a 1.5,1.5 0 0 0 -1.37,1.65999999 l 0.74,9.3300003 a 1.5,1.5 0 0 0 1.49,1.38 h 0.12 a 1.5,1.5 0 0 0 1.38,-1.71 l -0.74,-9.3300003 a 1.5,1.5 0 0 0 -1.62,-1.32999999 z"
id="path10"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<text
x="-3.8312604"
y="111.54049"
font-size="5px"
font-weight="bold"
id="text12"
style="font-weight:bold;font-size:5px;font-family:'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif;fill:#000000">Created by Rena</text>
<text
x="-3.8312604"
y="116.54049"
font-size="5px"
font-weight="bold"
id="text14"
style="font-weight:bold;font-size:5px;font-family:'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif;fill:#000000">from the Noun Project</text>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 91.046409 92.5831"
x="0px"
y="0px"
version="1.1"
id="svg16"
sodipodi:docname="appreciate-i.svg"
width="91.04641"
height="92.583099"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata22">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>SCHOOL_ICONS_100</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs20" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview18"
showgrid="false"
inkscape:zoom="1.888"
inkscape:cx="46.168739"
inkscape:cy="33.542603"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg16" />
<title
id="title2">SCHOOL_ICONS_100</title>
<path
d="m 90.06874,62.450495 v -0.14 a 16.31,16.31 0 0 0 -1.58,-5.39 l -9.97,-27.22 a 5.72,5.72 0 0 0 -4.2,-4.08 7.27,7.27 0 0 0 -6.78,2 5.71,5.71 0 0 0 -1.37,5.67 v 0.17 l -13.51,-13.59 c -3.17,-3.17 -7.08,-2.58 -9.31,-0.36 a 6.31,6.31 0 0 0 -1.69,3 l -3.26,-3.26 a 6.42,6.42 0 0 0 -9.05,-0.33 6.83,6.83 0 0 0 -2.37,5.81 6.28,6.28 0 0 0 -6.32,5.71 l -3.68,-2.79 a 6.38,6.38 0 0 0 -6.460001,-1 7.12,7.12 0 0 0 -4.2399997,5.58 6.37,6.37 0 0 0 2.52,5.87 6.07,6.07 0 0 0 -2.87,2.07 c -1.92,2.53 -1.84,6.61 2,9.54 l 2.8099997,2.13 a 6.15,6.15 0 0 0 -3.1799997,2.3 c -1.76,2.49 -1.78,6.59 2.31,9.7 l 32.2300007,24.51 a 16.43,16.43 0 0 0 21.19,-1.08 16.49,16.49 0 0 0 15.06,-4.47 l 7,-7 a 16.49,16.49 0 0 0 4.79,-12.69 1.51,1.51 0 0 0 -0.07,-0.66 z m -65.9,-33.21 a 3.21,3.21 0 0 1 2.67,-1.48 3.72,3.72 0 0 1 1.65,0.41 v 0 l 0.33,0.37 a 6.15,6.15 0 0 0 -4.65,4.28 3.19,3.19 0 0 1 0,-3.58 z m -14.9100007,3.46 a 4.13,4.13 0 0 1 2.4300007,-3.16 3.36,3.36 0 0 1 3.49,0.58 l 7.79,5.9 1.56,1.18 c 0.05,0.13 0.1,0.26 0.16,0.39 a 9.06,9.06 0 0 0 1.75,2.4 l 2.47,2.48 a 6.24,6.24 0 0 0 -1.73,0.58 6.1,6.1 0 0 0 -2.9,3.17 l -8.47,-6.42 -0.15,-0.14 -4.910001,-3.72 a 3.36,3.36 0 0 1 -1.4899997,-3.24 z m 34.6600007,53.26 -32.21,-24.42 c -2.5200007,-1.91 -2.6600007,-4.18 -1.670001,-5.58 a 3.21,3.21 0 0 1 2.670001,-1.35 5.18,5.18 0 0 1 2.8,0.94 l 14.35,10.89 a 1.5,1.5 0 1 0 1.81,-2.39 l -13.72,-10.41 -0.11,-0.1 -0.22,-0.16 -7.8500007,-6 c -2.41,-1.82 -2.46,-4 -1.44,-5.33 1.02,-1.33 3.0900007,-1.86 5.4700007,-0.09 l 0.11,0.11 10,7.59 a 8.58,8.58 0 0 0 2.52,4.55 l 15,15 9.88,9.88 a 50.23,50.23 0 0 0 5.7,5.45 16.7,16.7 0 0 0 2.73,1.6 l 0.19,0.09 a 13.48,13.48 0 0 1 -16.01,-0.27 z m 39.25,-12.24 -7,7 a 13.51,13.51 0 0 1 -19.09,0 l -28.52,-28.62 a 5.35,5.35 0 0 1 -1.73,-3.34 3.14,3.14 0 0 1 1.71,-3.07 4.19,4.19 0 0 1 4.51,0.91 v 0 l 12.82,12.82 a 1.5,1.5 0 0 0 2.12,-2.12 l -19.43,-19.46 a 5.52,5.52 0 0 1 -1.39,-2.12 3.16,3.16 0 0 1 1.24,-3.8 c 1.13,-0.71 3,-0.78 4.94,1.14 0,0 0,0 0,0.07 l 19.43,19.41 a 1.5,1.5 0 0 0 2.12,-2.12 l -19.39,-19.42 a 0.76,0.76 0 0 0 -0.06,-0.08 l -4.35,-4.38 a 3.23,3.23 0 0 1 -0.41,-0.48 3.46,3.46 0 0 1 0.64,-4.88 3.44,3.44 0 0 1 5,0.19 l 23.81,23.8 a 1.5,1.5 0 0 0 2.03,-2.09 l -16.34,-16.38 c -1.87,-1.87 -1.53,-3.89 -0.36,-5.07 1.17,-1.18 3.19,-1.52 5.07,0.36 l 17.72,17.72 a 1.5,1.5 0 0 0 2.5,-1.48 l -1.68,-5.78 a 2.68,2.68 0 0 1 0.63,-2.77 4.3,4.3 0 0 1 3.9,-1.14 2.75,2.75 0 0 1 2.06,2.1 l 10,27.39 0.07,0.15 a 13.32,13.32 0 0 1 1.33,4.58 1.4,1.4 0 0 0 0,0.14 c 0,0 0,0.07 0,0.11 a 13.5,13.5 0 0 1 -3.9,10.71 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 7.2587393,5.1404947 a 1.5,1.5 0 1 0 -2.09,2.14 L 15.44874,17.440495 a 1.5,1.5 0 0 0 2.11,-2.13 z"
id="path6"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 11.83874,22.870495 a 1.5,1.5 0 0 0 0.4,-2.95 l -9.5000007,-2.63 a 1.5,1.5 0 0 0 -0.8,2.89 l 9.5000007,2.63 a 1.5,1.5 0 0 0 0.4,0.06 z"
id="path8"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 21.37874,0.88049471 a 1.5,1.5 0 0 0 -1.37,1.65999999 l 0.74,9.3300003 a 1.5,1.5 0 0 0 1.49,1.38 h 0.12 a 1.5,1.5 0 0 0 1.38,-1.71 l -0.74,-9.3300003 a 1.5,1.5 0 0 0 -1.62,-1.32999999 z"
id="path10"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.75;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<text
x="-3.8312604"
y="111.54049"
font-size="5px"
font-weight="bold"
id="text12"
style="font-weight:bold;font-size:5px;font-family:'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif;fill:#000000">Created by Rena</text>
<text
x="-3.8312604"
y="116.54049"
font-size="5px"
font-weight="bold"
id="text14"
style="font-weight:bold;font-size:5px;font-family:'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif;fill:#000000">from the Noun Project</text>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -0,0 +1 @@
<svg width="72px" height="72px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-rolling" style="background: none;"><circle cx="50" cy="50" fill="none" ng-attr-stroke="{{config.color}}" ng-attr-stroke-width="{{config.width}}" ng-attr-r="{{config.radius}}" ng-attr-stroke-dasharray="{{config.dasharray}}" stroke="#00b4ef" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(239.933 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform></circle></svg>

After

Width:  |  Height:  |  Size: 685 B

View file

@ -0,0 +1,188 @@
/*
Appreciate
Created by Zach Fox on 2019-01-30
Copyright 2019 High Fidelity, Inc.
Distributed under the Apache License, Version 2.0.
See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
*/
/* globals document EventBridge setTimeout */
// Called when the user clicks the switch in the top right of the app.
// Sends an event to the App JS and clears the `firstRun` `div`.
function appreciateSwitchClicked(checkbox) {
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "appreciateSwitchClicked",
appreciateEnabled: checkbox.checked
}));
document.getElementById("firstRun").style.display = "none";
}
// Called when the user checks/unchecks the Never Whistle checkbox.
// Adds the crosshatch div to the UI and sends an event to the App JS.
function neverWhistleCheckboxClicked(checkbox) {
var crosshatch = document.getElementById("crosshatch");
if (checkbox.checked) {
crosshatch.style.display = "inline-block";
} else {
crosshatch.style.display = "none";
}
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "neverWhistleCheckboxClicked",
neverWhistle: checkbox.checked
}));
}
// Called when the user checks/unchecks the Show Appreciation Entity checkbox.
// Sends an event to the App JS.
function showAppreciationEntityCheckboxClicked(checkbox) {
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "showAppreciationEntityCheckboxClicked",
showAppreciationEntity: checkbox.checked
}));
if (checkbox.checked) {
document.getElementById("colorPickerContainer").style.visibility = "visible";
} else {
document.getElementById("colorPickerContainer").style.visibility = "hidden";
}
}
// Called when the user changes the entity's color using the jscolor picker.
// Modifies the color of the Intensity Meter gradient and sends a message to the App JS.
var START_COLOR_MULTIPLIER = 0.2;
function setEntityColor(jscolor) {
var newEntityColor = {
"red": jscolor.rgb[0],
"green": jscolor.rgb[1],
"blue": jscolor.rgb[2]
};
var startColor = {
"red": Math.floor(newEntityColor.red * START_COLOR_MULTIPLIER),
"green": Math.floor(newEntityColor.green * START_COLOR_MULTIPLIER),
"blue": Math.floor(newEntityColor.blue * START_COLOR_MULTIPLIER)
};
var currentIntensityDisplayWidth = document.getElementById("currentIntensityDisplay").offsetWidth;
var bgString = "linear-gradient(to right, rgb(" + startColor.red + ", " +
startColor.green + ", " + startColor.blue + ") 0, " +
jscolor.toHEXString() + " " + currentIntensityDisplayWidth + "px)";
document.getElementById("currentIntensity").style.backgroundImage = bgString;
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "setEntityColor",
entityColor: newEntityColor
}));
}
// Handle EventBridge messages from *_app.js.
function onScriptEventReceived(message) {
try {
message = JSON.parse(message);
} catch (error) {
console.log("Couldn't parse script event message: " + error);
return;
}
// This message gets sent by `entityList.js` when it shouldn't!
if (message.type === "removeEntities") {
return;
}
switch (message.method) {
case "updateUI":
if (message.isFirstRun) {
document.getElementById("firstRun").style.display = "block";
}
document.getElementById("appreciateSwitch").checked = message.appreciateEnabled;
document.getElementById("neverWhistleCheckbox").checked = message.neverWhistleEnabled;
var showAppreciationEntityCheckbox = document.getElementById("showAppreciationEntityCheckbox");
showAppreciationEntityCheckbox.checked = message.showAppreciationEntity;
if (showAppreciationEntityCheckbox.checked) {
document.getElementById("colorPickerContainer").style.visibility = "visible";
} else {
document.getElementById("colorPickerContainer").style.visibility = "hidden";
}
if (message.neverWhistleEnabled) {
var crosshatch = document.getElementById("crosshatch");
crosshatch.style.display = "inline-block";
}
document.getElementById("loadingContainer").style.display = "none";
var color = document.getElementById("colorPicker").jscolor;
color.fromRGB(message.entityColor.red, message.entityColor.green, message.entityColor.blue);
var startColor = {
"red": Math.floor(color.rgb[0] * START_COLOR_MULTIPLIER),
"green": Math.floor(color.rgb[1] * START_COLOR_MULTIPLIER),
"blue": Math.floor(color.rgb[2] * START_COLOR_MULTIPLIER)
};
var currentIntensityDisplayWidth = document.getElementById("currentIntensityDisplay").offsetWidth;
document.getElementById("currentIntensity").style.backgroundImage =
"linear-gradient(to right, rgb(" + startColor.red + ", " +
startColor.green + ", " + startColor.blue + ") 0, " +
color.toHEXString() + " " + currentIntensityDisplayWidth + "px)";
break;
case "updateCurrentIntensityUI":
document.getElementById("currentIntensity").style.width = message.currentIntensity * 100 + "%";
break;
default:
console.log("Unknown message received from appreciate_app.js! " + JSON.stringify(message));
break;
}
}
// This function detects a keydown on the document, which enables the app
// to forward these keypress events to the app JS.
function onKeyDown(e) {
var key = e.key.toUpperCase();
if (key === "Z") {
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "zKeyDown",
repeat: e.repeat
}));
}
}
// This function detects a keyup on the document, which enables the app
// to forward these keypress events to the app JS.
function onKeyUp(e) {
var key = e.key.toUpperCase();
if (key === "Z") {
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "zKeyUp"
}));
}
}
// This delay is necessary to allow for the JS EventBridge to become active.
// The delay is still necessary for HTML apps in RC78+.
var EVENTBRIDGE_SETUP_DELAY = 500;
function onLoad() {
setTimeout(function() {
EventBridge.scriptEventReceived.connect(onScriptEventReceived);
EventBridge.emitWebEvent(JSON.stringify({
app: "appreciate",
method: "eventBridgeReady"
}));
}, EVENTBRIDGE_SETUP_DELAY);
document.addEventListener("keydown", onKeyDown);
document.addEventListener("keyup", onKeyUp);
}
onLoad();

File diff suppressed because it is too large Load diff

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