Merge branch 'master' into fix/spinbox

This commit is contained in:
David Rowe 2021-04-05 10:41:31 +12:00
commit 7088fc9d6c
357 changed files with 10444 additions and 4447 deletions

17
.github/stale.yml vendored Normal file
View file

@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 60
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had recent activity.
It will be closed if no further activity occurs.
Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View file

@ -83,15 +83,15 @@ jobs:
shell: bash
run: |
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
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
if [ "${{ matrix.build_type }}" = "full" ]; then
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
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
- name: Clear working directory
if: startsWith(matrix.os, 'windows')

View file

@ -86,9 +86,9 @@ jobs:
echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
if [[ "${{ matrix.build_type }}" != "android" ]]; then
echo "ARTIFACT_PATTERN=Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV
echo "ARTIFACT_PATTERN=Vircadia-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV
# 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
echo "ARTIFACT_PATTERN=*.$INSTALLER_EXT" >> $GITHUB_ENV
fi

1
.gitignore vendored
View file

@ -111,3 +111,4 @@ tools/unity-avatar-exporter
server-console/package-lock.json
vcpkg/
/tools/nitpick/compiledResources
qt/

101
BUILD.md
View file

@ -1,6 +1,6 @@
# General Build Information
*Last Updated on August 26, 2020*
*Last Updated on December 21, 2020*
### OS Specific Build Guides
@ -11,7 +11,7 @@
### Dependencies
- [git](https://git-scm.com/downloads): >= 1.6
- [cmake](https://cmake.org/download/): 3.9
- [CMake](https://cmake.org/download/): 3.9
- [Python](https://www.python.org/downloads/): 3.6 or higher
- [Node.JS](https://nodejs.org/en/): >= 12.13.1 LTS
- Used to build the Screen Sharing executable.
@ -41,15 +41,18 @@ These are not placed in your normal build tree when doing an out of source build
Vircadia uses CMake to generate build files and project files for your platform.
#### Qt
CMake will download Qt 5.12.3 using vcpkg.
To override this (i.e. use an installed Qt configuration - you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt **lib/cmake** folder.
CMake will download Qt 5.12.3 using vcpkg.
To override this (i.e. use an installed Qt configuration - you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt **lib/cmake** folder.
This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
export QT_CMAKE_PREFIX_PATH=/usr/local/Qt5.12.3/gcc_64/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.12.3/clang_64/lib/cmake/
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.12.3/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
```bash
export QT_CMAKE_PREFIX_PATH=/usr/local/Qt5.12.3/gcc_64/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.12.3/clang_64/lib/cmake/
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.12.3/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
```
#### VCPKG
@ -59,56 +62,68 @@ 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.
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:
```bash
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
##### Possible Environment Variables
// The URL to post the dump to.
CMAKE_BACKTRACE_URL
// The identifying tag of the release.
CMAKE_BACKTRACE_TOKEN
// The release version.
RELEASE_NUMBER
// The build commit.
BUILD_NUMBER
```text
// The URL to post the dump to.
CMAKE_BACKTRACE_URL
// The identifying tag of the release.
CMAKE_BACKTRACE_TOKEN
// The release version, e.g., 2021.3.2.
RELEASE_NUMBER
// The release name, e.g., Eos.
RELEASE_NAME
// The build commit, e.g., use a Git hash for the most recent commit in the branch - fd6973b.
BUILD_NUMBER
// The type of release.
RELEASE_TYPE=PRODUCTION|PR|DEV
// The Interface will have a custom default home and startup location.
PRELOADED_STARTUP_LOCATION=Location/IP/URL
// The Interface will have a custom default script whitelist, comma separated, no spaces.
// This will also activate the whitelist on Interface's first run.
PRELOADED_SCRIPT_WHITELIST=ListOfEntries
// Code-signing environment variables must be set during runtime of CMake AND globally when the signing takes place.
HF_PFX_FILE=Path to certificate
HF_PFX_PASSPHRASE=Passphrase for certificate
// Determine the build type
PRODUCTION_BUILD=0|1
PR_BUILD=0|1
STABLE_BUILD=0|1
// Determine if to utilize testing or stable Metaverse URLs
USE_STABLE_GLOBAL_SERVICES=1
BUILD_GLOBAL_SERVICES=STABLE
```
// The type of release.
RELEASE_TYPE=PRODUCTION|PR|DEV
// The Interface will have a custom default home and startup location.
INITIAL_STARTUP_LOCATION=Location/IP/URL
// Code-signing environment variables must be set during runtime of CMake AND globally when the signing takes place.
HF_PFX_FILE=Path to certificate
HF_PFX_PASSPHRASE=Passphrase for certificate
// Determine the build type
PRODUCTION_BUILD=0|1
PR_BUILD=0|1
STABLE_BUILD=0|1
// Determine if to utilize testing or stable Metaverse URLs
USE_STABLE_GLOBAL_SERVICES=1
BUILD_GLOBAL_SERVICES=STABLE
##### Generate Files
Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
mkdir build
cd build
cmake ..
```bash
mkdir build
cd build
cmake ..
```
If CMake gives you the same error message repeatedly after the build fails, try removing `CMakeCache.txt`.
##### Generating a release/debug only vcpkg build
In order to generate a release or debug only vcpkg package, you could use the use the `VCPKG_BUILD_TYPE` define in your cmake generate command. Building a release only vcpkg can drastically decrease the total build time.
In order to generate a release or debug only vcpkg package, you could use the use the `VCPKG_BUILD_TYPE` define in your CMake generate command. Building a release only vcpkg can drastically decrease the total build time.
For release only vcpkg:
@ -124,7 +139,9 @@ Any variables that need to be set for CMake to find dependencies can be set as E
For example, to pass the QT_CMAKE_PREFIX_PATH variable (if not using the vcpkg'ed version) during build file generation:
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.12.3/lib/cmake
```bash
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.12.3/lib/cmake
```
#### Finding Dependencies

View file

@ -1,8 +1,8 @@
# Build Android
*Last Updated on December 21, 2019*
*Last Updated on December 15, 2020*
Please read the [general build guide](BUILD.md) for information on building other platforms. Only Android specific instructions are found in this file. **Note that these instructions apply to building for Oculus Quest.**
Please read the [general build guide](BUILD.md) for information on building other platforms. Only Android specific instructions are found in this file. **Note that these instructions apply to building for the Oculus Quest 1.**
## Dependencies
@ -14,7 +14,7 @@ Please install the dependencies for your OS using the [Windows](BUILD_WIN.md), [
### Android Studio
Download the [Android Studio](https://developer.android.com/studio/index.html) installer and run it. Once installed, at the welcome screen, click _Configure_ in the lower right corner and select _SDK Manager_.
Download the [Android Studio](https://developer.android.com/studio/index.html) installer and run it. Once installed, click _File_ then _Settings_, expand _Appearance & Behavior_ then expand _System Settings_ and select _Android SDK_.
From the _SDK Platforms_ tab, select API levels 26 and 28.
@ -27,24 +27,28 @@ From the _SDK Tools_ tab, select the following
* Android SDK Tools
* NDK (even if you have the NDK installed separately)
Still in the _SDK Tools_ tab, click _Show Package Details_. Select CMake 3.6.4. Do this even if you have a separate CMake installation.
Still in the _SDK Tools_ tab, check off _Show Package Details_ at the bottom. Select CMake 3.6.4. Do this even if you have a separate CMake installation. Also, make sure the NDK installed version is 18 (or higher).
Also, make sure the NDK installed version is 18 (or higher).
Now go back to _File_ then _Project Structure_ then under _Project_ set the Android Gradle Plugin Version to `3.2.1` and Gradle Version to `4.10.1`.
If Android Studio pops open the "Plugin Update Recommeded" dialog, do not click update, just click X on the top right to close. Later versions of the Gradle plugin have known issues with cz.malohlava.
## Environment
### Create a keystore in Android Studio
Follow the directions [here](https://developer.android.com/studio/publish/app-signing#generate-key) to create a keystore file. You can save it anywhere (preferably not in the `hifi` folder).
Follow the directions [here](https://developer.android.com/studio/publish/app-signing#generate-key) to create a keystore file. You can save it anywhere (preferably not in the `vircadia` folder).
### Set up machine specific Gradle properties
Create a `gradle.properties` file in the `.gradle` folder (`$HOME/.gradle` on Unix, `Users/<yourname>/.gradle` on Windows). Edit the file to contain the following
HIFI_ANDROID_PRECOMPILED=<your_home_directory>/Android/hifi_externals
HIFI_ANDROID_KEYSTORE=<key_store_directory>/<keystore_name>.jks
HIFI_ANDROID_KEYSTORE_PASSWORD=<password>
HIFI_ANDROID_KEY_ALIAS=<key_alias>
HIFI_ANDROID_KEY_PASSWORD=<key_passwords>
```properties
HIFI_ANDROID_PRECOMPILED=<your_home_directory>/Android/hifi_externals
HIFI_ANDROID_KEYSTORE=<key_store_directory>/<keystore_name>.jks
HIFI_ANDROID_KEYSTORE_PASSWORD=<password>
HIFI_ANDROID_KEY_ALIAS=<key_alias>
HIFI_ANDROID_KEY_PASSWORD=<key_passwords>
```
Note, do not use $HOME for the path. It must be a fully qualified path name. Also, be sure to use forward slashes in your path.
@ -52,17 +56,26 @@ Note, do not use $HOME for the path. It must be a fully qualified path name. Als
Add these lines to `gradle.properties`
SUPPRESS_QUEST_INTERFACE
SUPPRESS_QUEST_FRAME_PLAYER
```properties
SUPPRESS_QUEST_INTERFACE
SUPPRESS_QUEST_FRAME_PLAYER
```
#### If you are building for an Oculus Quest
Add these lines to `gradle.properties`
SUPPRESS_INTERFACE
SUPPRESS_FRAME_PLAYER
```properties
SUPPRESS_INTERFACE
SUPPRESS_FRAME_PLAYER
```
The above code to suppress modules is not necessary, but will speed up the build process.
#### The Frame Player for both Android Phone and Oculus Quest is optional, so if you encounter problems with these during your build, you can skip them by adding these lines to `gradle.properties`
```properties
SUPPRESS_FRAME_PLAYER
SUPPRESS_QUEST_FRAME_PLAYER
```
### Clone the repository
@ -74,12 +87,17 @@ The above code to suppress modules is not necessary, but will speed up the build
* Open Android Studio
* Choose _Open an existing Android Studio project_
* Navigate to the `hifi` repository and choose the `android` folder and select _OK_
* Navigate to the `vircadia` repository that had you cloned and choose the `android` folder and select _OK_
* Wait for Gradle to sync (this should take around 20 minutes the first time)
* From the _Build_ menu select _Make Project_
* If a dialog pops open saying "Plugin Update Recommeded" dialog, do not click update, just click X on the top right to close.
* In the _Project_ window click on the project you wish to build (i.e. "questInterface") then click _Build_ in the top menu and choose _Make Module 'questInterface'_
* By default this will build the "debug" apk, you can change this by opening the _Build Variants_ window along the left side and select other build types such as "release".
* Your newly build APK should reside in `vircadia\android\apps\questInterface\release` (if you chose release).
### Running a Module
You are free to use the "adb" command line or other development tools to install (sideload on Quest) your newly built APK, or you can follow the instructions below to load the APK via Android Studio.
* In the toolbar at the top of Android Studio, next to the green hammer icon, you should see a dropdown menu.
* You may already see a configuration for the module you are trying to build. If so, select it.
* Otherwise, select _Edit Configurations_.
@ -112,9 +130,17 @@ To view a more complete debug log,
* Click the icon with the two overlapping squares in the upper left corner of the tab where the sync is running (hover text says _Toggle view_)
* To change verbosity, click _File > Settings_. Under _Build, Execution, Deployment > Compiler_ you can add command-line flags, as per Gradle documentation
If you encounter CMake issues, try adding the following system environment variable:
With your start menu, search for 'Edit the System Environment Variables' and open it.
* Click on 'Advanced' tab, then 'Environment Variables'
* Select 'New' under System variables
* Set "Variable name" to QT_CMAKE_PREFIX_PATH
* Set "Variable value" to the directory that your android build placed the CMake 3.6.4 library CMake directory (i.e. android\qt\lib\cmake).
Some things you can try if you want to do a clean build
* Delete the `build` and `.externalNativeBuild` folders from the folder for each module you're building (for example, `hifi/android/apps/interface`)
* Delete the `build` and `.externalNativeBuild` folders from the folder for each module you're building (for example, `vircadia/android/apps/interface`)
* If you have set your `HIFI_VCPKG_ROOT` environment variable, delete the contents of that directory; otherwise, delete `AppData/Local/Temp/hifi`
* In Android Studio, click _File > Invalidate Caches / Restart_ and select _Invalidate and Restart_

View file

@ -168,39 +168,3 @@ If your goal is to set up a development environment, it is desirable to set the
directory that vcpkg builds into with the `HIFI_VCPKG_BASE` environment variable.
For example, you might set `HIFI_VCPKG_BASE` to `/home/$USER/vcpkg`.
By default, vcpkg will build in the system `/tmp` directory.
##### Ubuntu 18.04 only
In Ubuntu 18.04 there is a problem related with NVidia driver library version.
It can be worked around following these steps:
1. Uninstall incompatible nvtt libraries:
`sudo apt-get remove libnvtt2 libnvtt-dev`
1. Install libssl1.0-dev:
`sudo apt-get -y install libssl1.0-dev`
1. Clone castano nvidia-texture-tools:
`git clone https://github.com/castano/nvidia-texture-tools`
`cd nvidia-texture-tools/`
1. Make these changes in repo:
* In file **VERSION** set `2.2.1`
* In file **configure**:
* set `build="release"`
* set `-DNVTT_SHARED=1`
1. Configure, build and install:
`./configure`
`make`
`sudo make install`
1. Link compiled files:
`sudo ln -s /usr/local/lib/libnvcore.so /usr/lib/libnvcore.so`
`sudo ln -s /usr/local/lib/libnvimage.so /usr/lib/libnvimage.so`
`sudo ln -s /usr/local/lib/libnvmath.so /usr/lib/libnvmath.so`
`sudo ln -s /usr/local/lib/libnvtt.so /usr/lib/libnvtt.so`
1. After running these steps you can run interface:
`interface/interface`

View file

@ -8,22 +8,26 @@ Please read the [general build guide](BUILD.md) for information on dependencies
[Homebrew](https://brew.sh/) is an excellent package manager for macOS. It makes install of some Vircadia dependencies very simple.
brew install cmake openssl npm
```bash
brew install cmake openssl npm
```
### Python 3
Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/).
Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/).
Execute the `Update Shell Profile.command` script that is provided with the installer.
### OSX SDK
You will need version `10.12` of the OSX SDK for building, otherwise you may have crashing or other unintended issues due to the deprecation of OpenGL on OSX. You can get that SDK from [here](https://github.com/phracker/MacOSX-SDKs). You must copy it in to your Xcode SDK directory, e.g.
cp -rp ~/Downloads/MacOSX10.12.sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
```bash
cp -rp ~/Downloads/MacOSX10.12.sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
```
### OpenSSL
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR via
`export OPENSSL_ROOT_DIR=/usr/local/opt/openssl`
or by appending `-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl` to `cmake`
@ -31,12 +35,14 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR via
### Xcode
You can ask CMake to generate Xcode project files instead of Unix Makefiles using the `-G Xcode` parameter after CMake. You will need to select the Xcode installation in the terminal first if you have not done so already.
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
cmake ../ -DCMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DOSX_SDK=10.12 ..
If `cmake` complains about Python 3 being missing, you may need to update your CMake binary with command `brew upgrade cmake`, or by downloading and running the latest CMake installer, depending on how you originally instaled CMake
```bash
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
cmake ../ -DCMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DOSX_SDK=10.12 ..
```
If `cmake` complains about Python 3 being missing, you may need to update your CMake binary with command `brew upgrade cmake`, or by downloading and running the latest CMake installer, depending on how you originally installed CMake.
After running CMake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run.

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.
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
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
* 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 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.
### 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.
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 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`
If you do not use an administrator command-line, you will get errors.
### Step 3. Installing CMake
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.
### 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.
To create this variable:
@ -65,7 +72,7 @@ To create this variable:
* Set "Variable name" to `HIFI_VCPKG_BOOTSTRAP`
* 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:
`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.
### Step 6. Making a Build
### Step 7. Making a Build
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`.
### Step 7. Testing Interface
### Step 8. Testing Interface
Create another environment variable (see Step #3)
* 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
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
* Restart your computer
* 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

View file

@ -1,6 +1,6 @@
# Creating an Installer
*Last Updated on August 24, 2020*
*Last Updated on March 4, 2021*
Follow the [build guide](BUILD.md) to figure out how to build Vircadia for your platform.
@ -9,13 +9,13 @@ During generation, CMake should produce an `install` target and a `package` targ
The `install` target will copy the Vircadia targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/Program Files/hifi` and stored in `build/CMakeCache.txt`
### Packaging
## Packaging
To produce an installer, run the `package` target. However you will want to follow the steps specific to your platform below.
#### Windows
### Windows
##### Prerequisites
#### Prerequisites
To produce an executable installer on Windows, the following are required:
@ -60,14 +60,14 @@ 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 `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [Node.JS and NPM](<https://www.npmjs.com/get-npm>)
1. Install version 10.15.0 LTS
1. [Node.JS and NPM](<https://nodejs.org/en/download/>)
1. Install version 10.15.0 LTS (or greater)
##### Code Signing (optional)
#### Code Signing (optional)
For code signing to work, you will need to set the `HF_PFX_FILE` and `HF_PFX_PASSPHRASE` environment variables to be present during CMake runtime and globally as we proceed to package the installer.
##### Creating the Installer
#### Creating the Installer
1. Perform a clean cmake from a new terminal.
1. Open the `vircadia.sln` solution with elevated (administrator) permissions on Visual Studio and select the **Release** configuration.
@ -79,7 +79,14 @@ For code signing to work, you will need to set the `HF_PFX_FILE` and `HF_PFX_PAS
1. Build CMakeTargets->PACKAGE
The installer is now available in `build\_CPack_Packages\win64\NSIS`
#### OS X
#### FAQ
1. **Problem:** Failure to open a file. ```File: failed opening file "\FOLDERSHARE\XYZSRelease\...\Credits.rtf" Error in script "C:\TFS\XYZProject\Releases\NullsoftInstaller\XYZWin7Installer.nsi" on line 77 -- aborting creation process```
1. **Cause:** The complete path (current directory + relative path) has to be < 260 characters to any of the relevant files.
1. **Solution:** Move your build and packaging folder as high up in the drive as possible to prevent an overage.
### OS X
1. [npm](<https://www.npmjs.com/get-npm>)
Install version 12.16.3 LTS
@ -91,8 +98,156 @@ For code signing to work, you will need to set the `HF_PFX_FILE` and `HF_PFX_PAS
1. Perform a Release build of `package`
Installer is now available in `build/_CPack_Packages/Darwin/DragNDrop
### FAQ
### Linux
1. **Problem:** Failure to open a file. ```File: failed opening file "\FOLDERSHARE\XYZSRelease\...\Credits.rtf" Error in script "C:\TFS\XYZProject\Releases\NullsoftInstaller\XYZWin7Installer.nsi" on line 77 -- aborting creation process```
1. **Cause:** The complete path (current directory + relative path) has to be < 260 characters to any of the relevant files.
1. **Solution:** Move your build and packaging folder as high up in the drive as possible to prevent an overage.
#### Server
##### Ubuntu 18.04 | .deb
1. Ensure you are using an Ubuntu 18.04 system. There is no required minimum to the amount of CPU cores needed, however it's recommended that you use as many as you have available in order to have an efficient experience.
```text
Recommended CPU Cores: 16
Minimum Disk Space: 40GB
```
3. Get and bootstrap Vircadia Builder.
```bash
git clone https://github.com/vircadia/vircadia-builder.git
cd vircadia-builder
```
3. Run Vircadia Builder.
```bash
./vircadia-builder --build server
```
4. If Vircadia Builder needed to install dependencies and asks you to run it again then do so. Otherwise, skip to the next step.
```bash
./vircadia-builder --build server
```
5. Vircadia Builder will ask you to configure it to build the server. The values will be prefilled with defaults, the following steps will explain what they are and what you might want to put. *Advanced users: See [here](BUILD.md#possible-environment-variables) for possible environment variables and settings.*
6. This value is the Git repository of Vircadia. You can set this URL to your fork of the Vircadia repository if you need to.
```text
Git repository: https://github.com/vircadia/vircadia/
# OR, for example
Git repository: https://github.com/digisomni/vircadia/
```
7. This value is the tag on the repository. If you would like to use a specific version of Vircadia, typically tags will be named like this: "v2021.1.0-rc"
```text
Git tag: master
# OR, for example
Git tag: v2021.1.0-rc
```
8. This value is the release type. For example, the options are `production`, `pr`, or `dev`. If you are making a build for yourself and others to use then use `production`.
```text
Release type: DEV
# OR, for example we recommend you use
Release type: PRODUCTION
```
9. This value is the release version. Release numbers should be in a format of `YEAR-MAJORVERSION-MINORVERSION` which might look like this: `2021.1.0`.
```text
Release number: 2021.1.0
```
10. This value is the build number. We typically use the hash of the most recent commit on that Git tag which might look like this: `fd6973b`.
```text
Build number: fd6973b
```
11. This value is the directory that Vircadia will get installed to. You should leave this as the default value unless you are an advanced user.
```text
Installation dir: /home/ubuntu/Vircadia
```
12. This value is the number of CPU cores that the Vircadia Builder will use to compile the Vircadia server. By default it will use all cores available on your build server. You should leave this as the default value it gives you for your build server.
```text
CPU cores to use for Vircadia: 16
```
13. This value is the number of CPU cores that the Vircadia Builder will use to compile Qt5 (a required component for Vircadia). By default it will use all cores available on your build server. You should leave this as the default value it gives you for your build server.
```text
CPU cores to use for Qt5: 16
```
14. It will ask you if you would like to proceed with the specified values. If you're happy with the configuration, type `yes`, otherwise enter `no` and press enter to start over. You can press `Ctrl` + `C` simultaneously on your keyboard to exit.
15. Vircadia Builder will now run, it may take a while. See this [table](https://github.com/vircadia/vircadia-builder#how-long-does-it-take) for estimated times.
16. Navigate to the `pkg-scripts` directory.
```bash
cd ../Vircadia/source/pkg-scripts/
```
17. Generate the .rpm package. Set `RPMVERSION` to the same version you entered for the `Release number` on Vircadia Builder. *Advanced users: the version cannot begin with a letter and cannot include underscores or dashes in it.*
```bash
DEBVERSION="2021.1.0" DEBEMAIL="your-email@somewhere.com" DEBFULLNAME="Your Full Name" ./make-deb-server
```
18. If successful, the generated .deb package will be in the `pkg-scripts` folder.
##### Amazon Linux 2 | .rpm
1. Ensure you are using an Amazon Linux 2 system. You will need many CPU cores to complete this process within a reasonable time. As an alternative to AWS EC2, you may use a [virtual machine](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/amazon-linux-2-virtual-machine.html). Here are the recommended specs:
```text
AWS EC2 Instance Type: C5a.4xlarge
Recommended CPU Cores: 16
Minimum Disk Space: 40GB
```
2. Update the system and install dependencies.
```bash
sudo yum update -y
sudo yum install git -y
sudo yum install rpm-build
```
3. Get and bootstrap Vircadia Builder.
```bash
git clone https://github.com/vircadia/vircadia-builder.git
cd vircadia-builder
sudo ./install_amazon_linux_deps.sh
```
4. Run Vircadia Builder.
```bash
./vircadia-builder --build server
```
5. If Vircadia Builder needed to install dependencies and asks you to run it again then do so. Otherwise, skip to the next step.
```bash
./vircadia-builder --build server
```
6. Vircadia Builder will ask you to configure it to build the server. The values will be prefilled with defaults, the following steps will explain what they are and what you might want to put. *Advanced users: See [here](BUILD.md#possible-environment-variables) for possible environment variables and settings.*
7. This value is the Git repository of Vircadia. You can set this URL to your fork of the Vircadia repository if you need to.
```text
Git repository: https://github.com/vircadia/vircadia/
# OR, for example
Git repository: https://github.com/digisomni/vircadia/
```
8. This value is the tag on the repository. If you would like to use a specific version of Vircadia, typically tags will be named like this: "v2021.1.0-rc".
```text
Git tag: master
# OR, for example
Git tag: v2021.1.0-rc
```
9. This value is the release type. For example, the options are `production`, `pr`, or `dev`. If you are making a build for yourself and others to use then use `production`.
```text
Release type: DEV
# OR, for example we recommend you use
Release type: PRODUCTION
```
10. This value is the release version. Release numbers typically should be in a format of `YEAR-MAJORVERSION-MINORVERSION` which might look like this: `2021.1.0`.
```text
Release number: 2021.1.0
```
11. This value is the build number. We typically use the hash of the most recent commit on that Git tag which might look like this: `fd6973b`.
```text
Build number: fd6973b
```
12. This value is the directory that Vircadia will get installed to. You should leave this as the default value unless you are an advanced user.
```text
Installation dir: /root/Vircadia
```
13. This value is the number of CPU cores that the Vircadia Builder will use to compile the Vircadia server. By default it will use all cores available on your build server given you have enough memory. You should leave this as the default value it gives you for your build server.
```text
CPU cores to use for Vircadia: 16
```
14. This value is the number of CPU cores that the Vircadia Builder will use to compile Qt5 (a required component for Vircadia). By default it will use all cores available on your build server given you have enough memory. You should leave this as the default value it gives you for your build server.
```text
CPU cores to use for Qt5: 16
```
15. It will ask you if you would like to proceed with the specified values. If you're happy with the configuration, type `yes`, otherwise enter `no` and press enter to start over. You can press `Ctrl` + `C` simultaneously on your keyboard to exit.
16. Vircadia Builder will now run, it may take a while. See this [table](https://github.com/vircadia/vircadia-builder#how-long-does-it-take) for estimated times.
17. Navigate to the `pkg-scripts` directory.
```bash
cd ../Vircadia/source/pkg-scripts/
```
18. Generate the .rpm package. Set `RPMVERSION` to the same version you entered for the `Release number` on Vircadia Builder. *Advanced users: the version cannot begin with a letter and cannot include underscores or dashes in it.*
```bash
RPMVERSION="2021.1.0" ./make-rpm-server
```
19. If successful, the generated .rpm package will be in the `pkg-scripts` folder of the Vircadia source files.

View file

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

View file

@ -1,10 +1,10 @@
# Vircadia
# Vircadia (Codename Athena)
### 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
@ -12,50 +12,48 @@ Vircadia is a 3D social software project seeking to incrementally bring about a
### How to build the Interface
[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 Linux](https://github.com/vircadia/vircadia/blob/master/BUILD_LINUX.md)
[For Linux - Vircadia Builder](https://github.com/vircadia/vircadia-builder)
- [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 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
[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
[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
[For Windows](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md)
- [For Windows - Interface & Server](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md)
- [For Mac - Interface](https://github.com/vircadia/vircadia/blob/master/INSTALLER.md#os-x)
- [For Linux - Server .deb - Vircadia Builder](INSTALLER.md#ubuntu-1804--deb)
- [For Linux - Server .rpm - Vircadia Builder](INSTALLER.md#amazon-linux-2--rpm)
- [For Linux - Interface 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
### Boot to Metaverse: [The Goal](https://vircadia.com/vision/)
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
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?
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.
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!
#### Child Projects
- [Vircadia Builder for Linux](https://github.com/vircadia/vircadia-builder/)
- [General Documentation](https://github.com/vircadia/vircadia-docs-sphinx/)
### Contribution
A special thanks to the contributors of Vircadia.
[Contribution](CONTRIBUTING.md)
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)!

View file

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

View file

@ -33,6 +33,7 @@
#include <Trace.h>
#include <StatTracker.h>
#include <ThreadHelpers.h>
#include "AssignmentClientLogging.h"
#include "AssignmentFactory.h"
@ -235,10 +236,13 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
qCDebug(assignment_client) << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
// start the deployed assignment
QThread* workerThread = new QThread;
QThread* workerThread = new QThread();
workerThread->setObjectName("ThreadedAssignment Worker");
connect(workerThread, &QThread::started, _currentAssignment.data(), &ThreadedAssignment::run);
connect(workerThread, &QThread::started, _currentAssignment.data(), [this] {
setThreadName("ThreadedAssignment Worker");
_currentAssignment->run();
});
// Once the ThreadedAssignment says it is finished - we ask it to deleteLater
// This is a queued connection so that it is put into the event loop to be processed by the worker

View file

@ -11,9 +11,13 @@
#include "AudioMixerSlavePool.h"
#include <QObject>
#include <assert.h>
#include <algorithm>
#include <ThreadHelpers.h>
void AudioMixerSlaveThread::run() {
while (true) {
wait();
@ -157,6 +161,7 @@ void AudioMixerSlavePool::resize(int numThreads) {
// start new slaves
for (int i = 0; i < numThreads - _numThreads; ++i) {
auto slave = new AudioMixerSlaveThread(*this, _workerSharedData);
QObject::connect(slave, &QThread::started, [] { setThreadName("AudioMixerSlaveThread"); });
slave->start();
_slaves.emplace_back(slave);
}

View file

@ -269,7 +269,13 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
// the avatar mixer uses the negative value of the sent version
instanceVersionRef = -packetTraitVersion;
} else {
_avatar->processTraitInstance(traitType, instanceID, message.read(traitSize));
// Don't accept avatar entity data for distribution unless sender has rez permissions on the domain.
// The sender shouldn't be sending avatar entity data, however this provides a back-up.
auto trait = message.read(traitSize);
if (sendingNode.getCanRezAvatarEntities()) {
_avatar->processTraitInstance(traitType, instanceID, trait);
}
instanceVersionRef = packetTraitVersion;
}
@ -290,6 +296,29 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
}
}
void AvatarMixerClientData::emulateDeleteEntitiesTraitsMessage(const QList<QUuid>& avatarEntityIDs) {
// Emulates processSetTraitsMessage() actions on behalf of an avatar whose canRezAvatarEntities permission has been removed.
// The source avatar should be removing its avatar entities. However, using this method provides a back-up.
auto traitType = AvatarTraits::AvatarEntity;
for (const auto& entityID : avatarEntityIDs) {
auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, entityID);
_avatar->processDeletedTraitInstance(traitType, entityID);
// Mixer doesn't need deleted IDs.
_avatar->getAndClearRecentlyRemovedIDs();
// to track a deleted instance but keep version information
// the avatar mixer uses the negative value of the sent version
// Because there is no originating message from an avatar we enlarge the magnitude by 1.
// If a user subsequently has canRezAvatarEntities permission granted, they will have to relog in order for their
// avatar entities to be visible to others.
instanceVersionRef = -instanceVersionRef - 1;
}
_lastReceivedTraitsChange = std::chrono::steady_clock::now();
}
void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& message) {
// Avatar Traits flow control marks each outgoing avatar traits packet with a
// sequence number. The mixer caches the traits sent in the traits packet.

View file

@ -132,6 +132,7 @@ public:
int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed
void processSetTraitsMessage(ReceivedMessage& message, const SlaveSharedData& slaveSharedData, Node& sendingNode);
void emulateDeleteEntitiesTraitsMessage(const QList<QUuid>& avatarEntityIDs);
void processBulkAvatarTraitsAckMessage(ReceivedMessage& message);
void checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, Node& sendingNode,
AvatarTraits::TraitVersion traitVersion);

View file

@ -432,6 +432,17 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
}
}
// The source avatar should be removing its avatar entities. However, provide a back-up.
if (sendAvatar) {
if (!sourceAvatarNode->getCanRezAvatarEntities()) {
auto sourceAvatarNodeData = reinterpret_cast<AvatarMixerClientData*>(sourceAvatarNode->getLinkedData());
auto avatarEntityIDs = sourceAvatarNodeData->getAvatar().getAvatarEntityIDs();
if (avatarEntityIDs.count() > 0) {
sourceAvatarNodeData->emulateDeleteEntitiesTraitsMessage(avatarEntityIDs);
}
}
}
if (sendAvatar) {
AvatarDataSequenceNumber lastSeqToReceiver = destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID());
AvatarDataSequenceNumber lastSeqFromSender = sourceAvatarNodeData->getLastReceivedSequenceNumber();

View file

@ -398,7 +398,7 @@ void ScriptableAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityDa
// clear deleted traits
for (const auto& id : idsToClear) {
clearAvatarEntity(id);
clearAvatarEntityInternal(id);
}
}
@ -408,7 +408,7 @@ void ScriptableAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArra
std::map<QUuid, EntityItemPointer>::iterator itr = _entities.find(entityID);
if (itr != _entities.end()) {
_entities.erase(itr);
clearAvatarEntity(entityID);
clearAvatarEntityInternal(entityID);
}
return;
}

View file

@ -20,6 +20,7 @@
#include <udt/PacketHeaders.h>
const QString MESSAGES_MIXER_LOGGING_NAME = "messages-mixer";
const int MESSAGES_MIXER_RATE_LIMITER_INTERVAL = 1000; // 1 second
MessagesMixer::MessagesMixer(ReceivedMessage& message) : ThreadedAssignment(message)
{
@ -44,10 +45,20 @@ void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessa
QByteArray data;
QUuid senderID;
bool isText;
auto senderUUID = senderNode->getUUID();
MessagesClient::decodeMessagesPacket(receivedMessage, channel, isText, message, data, senderID);
auto nodeList = DependencyManager::get<NodeList>();
auto itr = _allSubscribers.find(senderUUID);
if (itr == _allSubscribers.end()) {
_allSubscribers[senderUUID] = 1;
} else if (*itr >= _maxMessagesPerSecond) {
return;
} else {
*itr += 1;
}
nodeList->eachMatchingNode(
[&](const SharedNodePointer& node)->bool {
return node->getActiveSocket() && _channelSubscribers[channel].contains(node->getUUID());
@ -60,14 +71,18 @@ void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessa
}
void MessagesMixer::handleMessagesSubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
auto senderUUID = senderNode->getUUID();
QString channel = QString::fromUtf8(message->getMessage());
_channelSubscribers[channel] << senderNode->getUUID();
_channelSubscribers[channel] << senderUUID;
}
void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
auto senderUUID = senderNode->getUUID();
QString channel = QString::fromUtf8(message->getMessage());
if (_channelSubscribers.contains(channel)) {
_channelSubscribers[channel].remove(senderNode->getUUID());
_channelSubscribers[channel].remove(senderUUID);
}
}
@ -88,7 +103,48 @@ void MessagesMixer::sendStatsPacket() {
}
void MessagesMixer::run() {
ThreadedAssignment::commonInit(MESSAGES_MIXER_LOGGING_NAME, NodeType::MessagesMixer);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &MessagesMixer::domainSettingsRequestComplete);
ThreadedAssignment::commonInit(MESSAGES_MIXER_LOGGING_NAME, NodeType::MessagesMixer);
startMaxMessagesProcessor();
}
void MessagesMixer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
// parse the settings to pull out the values we need
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
}
void MessagesMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
const QString MESSAGES_MIXER_SETTINGS_KEY = "messages_mixer";
QJsonObject messagesMixerGroupObject = domainSettings[MESSAGES_MIXER_SETTINGS_KEY].toObject();
const QString NODE_MESSAGES_PER_SECOND_KEY = "max_node_messages_per_second";
QJsonValue maxMessagesPerSecondValue = messagesMixerGroupObject.value(NODE_MESSAGES_PER_SECOND_KEY);
_maxMessagesPerSecond = maxMessagesPerSecondValue.toInt(DEFAULT_NODE_MESSAGES_PER_SECOND);
}
void MessagesMixer::processMaxMessagesContainer() {
_allSubscribers.clear();
}
void MessagesMixer::startMaxMessagesProcessor() {
if (_maxMessagesTimer) {
stopMaxMessagesProcessor();
}
_maxMessagesTimer = new QTimer();
connect(_maxMessagesTimer, &QTimer::timeout, this, &MessagesMixer::processMaxMessagesContainer);
_maxMessagesTimer->start(MESSAGES_MIXER_RATE_LIMITER_INTERVAL); // Clear the container every second.
}
void MessagesMixer::stopMaxMessagesProcessor() {
_maxMessagesTimer->stop();
_maxMessagesTimer->deleteLater();
_maxMessagesTimer = nullptr;
}

View file

@ -32,9 +32,21 @@ private slots:
void handleMessages(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleMessagesSubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleMessagesUnsubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void parseDomainServerSettings(const QJsonObject& domainSettings);
void domainSettingsRequestComplete();
void startMaxMessagesProcessor();
void stopMaxMessagesProcessor();
void processMaxMessagesContainer();
private:
QHash<QString,QSet<QUuid>> _channelSubscribers;
QHash<QString, QSet<QUuid>> _channelSubscribers;
QHash<QUuid, int> _allSubscribers;
const int DEFAULT_NODE_MESSAGES_PER_SECOND = 1000;
int _maxMessagesPerSecond { 0 };
QTimer* _maxMessagesTimer { nullptr };
};
#endif // hifi_MessagesMixer_h

View file

@ -34,6 +34,7 @@
#include <QtCore/QDir>
#include <OctreeDataUtils.h>
#include <ThreadHelpers.h>
Q_LOGGING_CATEGORY(octree_server, "hifi.octree-server")
@ -1192,7 +1193,10 @@ void OctreeServer::domainSettingsRequestComplete() {
_persistAsFileType);
_persistManager->moveToThread(&_persistThread);
connect(&_persistThread, &QThread::finished, _persistManager, &QObject::deleteLater);
connect(&_persistThread, &QThread::started, _persistManager, &OctreePersistThread::start);
connect(&_persistThread, &QThread::started, _persistManager, [this] {
setThreadName("OctreePersistThread");
_persistManager->start();
});
connect(_persistManager, &OctreePersistThread::loadCompleted, this, [this]() {
beginRunning();
});

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

@ -4,6 +4,7 @@
#
# Created by Leonardo Murillo on 12/16/2015.
# Copyright 2015 High Fidelity, Inc.
# Copyright 2021 Vircadia contributors.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -20,9 +21,9 @@ macro(GENERATE_INSTALLERS)
set(INSTALLER_TYPE "client_only")
string(REGEX REPLACE "Vircadia" "Vircadia Interface" _DISPLAY_NAME ${BUILD_ORGANIZATION})
elseif (SERVER_ONLY)
set(_PACKAGE_NAME_EXTRA "-Sandbox")
set(_PACKAGE_NAME_EXTRA "-Server")
set(INSTALLER_TYPE "server_only")
string(REGEX REPLACE "Vircadia" "Vircadia Sandbox" _DISPLAY_NAME ${BUILD_ORGANIZATION})
string(REGEX REPLACE "Vircadia" "Vircadia Server" _DISPLAY_NAME ${BUILD_ORGANIZATION})
else ()
set(_DISPLAY_NAME ${BUILD_ORGANIZATION})
set(INSTALLER_TYPE "full")
@ -31,7 +32,7 @@ macro(GENERATE_INSTALLERS)
set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME})
set(CPACK_PACKAGE_VENDOR "Vircadia")
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}-${RELEASE_NAME}")
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
if (PR_BUILD)
@ -122,7 +123,7 @@ macro(GENERATE_INSTALLERS)
endif ()
if (BUILD_SERVER)
cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "Vircadia Sandbox")
cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "Vircadia Server")
endif ()
include(CPack)

View file

@ -8,23 +8,30 @@
#
macro(SETUP_MEMORY_DEBUGGER)
if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
SET( HIFI_MEMORY_DEBUGGING true )
if ("$ENV{VIRCADIA_MEMORY_DEBUGGING}")
SET( VIRCADIA_MEMORY_DEBUGGING true )
endif ()
if (HIFI_MEMORY_DEBUGGING)
if (VIRCADIA_MEMORY_DEBUGGING)
if (UNIX)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# for clang on Linux
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -shared-libasan -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -shared-libasan -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -shared-libasan -fsanitize=undefined -fsanitize=address -fsanitize-recover=address")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize=leak -fsanitize-recover=address")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -fsanitize-recover=address")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -fsanitize-recover=address")
else ()
# for gcc on Linux
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address")
# For some reason, using -fstack-protector results in this error:
# usr/bin/ld: ../../libraries/audio/libaudio.so: undefined reference to `FIR_1x4_AVX512(float*, float*, float*, float*, float*, float (*) [64], int)'
# The '-DSTACK_PROTECTOR' argument below disables the usage of this function in the code. This should be fine as it only works on the latest Intel hardware,
# and is an optimization that should make no functional difference.
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -U_FORTIFY_SOURCE -DSTACK_PROTECTOR -fstack-protector-strong -fno-omit-frame-pointer")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak ")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak")
endif()
endif (UNIX)
else()
message(FATAL_ERROR "Memory debugging is not supported on this platform." )
endif()
endif ()
endmacro(SETUP_MEMORY_DEBUGGER)

View file

@ -23,8 +23,12 @@ macro(SET_PACKAGING_PARAMETERS)
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
set_from_env(RELEASE_NAME RELEASE_NAME "")
set_from_env(STABLE_BUILD STABLE_BUILD 0)
set_from_env(INITIAL_STARTUP_LOCATION INITIAL_STARTUP_LOCATION "")
set_from_env(PRELOADED_STARTUP_LOCATION PRELOADED_STARTUP_LOCATION "")
set_from_env(PRELOADED_SCRIPT_WHITELIST PRELOADED_SCRIPT_WHITELIST "")
set_from_env(BYPASS_SIGNING BYPASS_SIGNING 0)
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
@ -176,12 +180,12 @@ macro(SET_PACKAGING_PARAMETERS)
if (PRODUCTION_BUILD)
set(INTERFACE_SHORTCUT_NAME "Vircadia")
set(CONSOLE_SHORTCUT_NAME "Console")
set(SANDBOX_SHORTCUT_NAME "Sandbox")
set(SANDBOX_SHORTCUT_NAME "Server")
set(APP_USER_MODEL_ID "com.vircadia.console")
else ()
set(INTERFACE_SHORTCUT_NAME "Vircadia - ${BUILD_VERSION_NO_SHA}")
set(CONSOLE_SHORTCUT_NAME "Console - ${BUILD_VERSION_NO_SHA}")
set(SANDBOX_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}")
set(SANDBOX_SHORTCUT_NAME "Server - ${BUILD_VERSION_NO_SHA}")
endif ()
set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}")

View file

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

View file

@ -251,8 +251,8 @@ Var substringResult
!macro InitSection SecName
; This macro reads component installed flag from the registry and
;changes checked state of the section on the components page.
;Input: section index constant name specified in Section command.
; changes checked state of the section on the components page.
; Input: section index constant name specified in Section command.
ClearErrors
;Reading component status from registry
@ -718,7 +718,7 @@ Function InstallTypesPage
StrCpy $OffsetUnits u
StrCpy $Express "0"
${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls Vircadia Interface and Vircadia Sandbox"
${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls Vircadia Interface"
pop $ExpressInstallRadioButton
${NSD_OnClick} $ExpressInstallRadioButton ChangeExpressLabel
IntOp $CurrentOffset $CurrentOffset + 15
@ -1346,6 +1346,7 @@ Section "-Core installation"
${EndIf}
${If} @SERVER_COMPONENT_CONDITIONAL@
${AndIf} $Express != "1"
; handling for server console shortcut
Delete "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk"
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@SANDBOX_HF_SHORTCUT_NAME@.lnk" \

View file

@ -1,5 +1,5 @@
{
"version": 2.4,
"version": 2.5,
"settings": [
{
"name": "metaverse",
@ -295,10 +295,10 @@
},
{
"name": "approved_safe_urls",
"label": "Approved Script and QML URLs",
"label": "Approved Script and QML URLs (Not Enabled)",
"help": "These URLs will be sent to the Interface as safe URLs to allow through the whitelist if the Interface has this security option enabled.",
"placeholder": "0",
"default": "1",
"placeholder": "",
"default": "",
"advanced": false
},
{
@ -338,7 +338,7 @@
"name": "standard_permissions",
"type": "table",
"label": "Domain-Wide User Permissions",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether a user can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"caption": "Standard Permissions",
"can_add_new_rows": false,
"groups": [
@ -347,8 +347,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether a user can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -363,6 +363,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -439,17 +446,20 @@
"default": [
{
"id_can_connect": true,
"id_can_rez_avatar_entities": true,
"id_can_rez_tmp_certified": true,
"permissions_id": "anonymous"
},
{
"id_can_connect": true,
"id_can_rez_avatar_entities": true,
"id_can_rez_tmp_certified": true,
"permissions_id": "friends"
},
{
"id_can_adjust_locks": true,
"id_can_connect": true,
"id_can_rez_avatar_entities": true,
"id_can_adjust_locks": true,
"id_can_connect_past_max_capacity": true,
"id_can_kick": true,
"id_can_replace_content": true,
@ -463,6 +473,7 @@
},
{
"id_can_connect": true,
"id_can_rez_avatar_entities": true,
"id_can_rez_tmp_certified": true,
"permissions_id": "logged-in"
}
@ -484,8 +495,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether users in specific groups can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users in specific groups can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -525,6 +536,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -613,8 +631,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether users in specific groups can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users in specific groups can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -651,6 +669,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -734,8 +759,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether a user can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -750,6 +775,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -833,8 +865,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether users from specific IPs can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -849,6 +881,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -932,8 +971,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether users with specific MACs can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -948,6 +987,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -1031,8 +1077,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Avatar Entities</strong><br />Sets whether users with specific Machine Fingerprints can use avatar entities on the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 12
}
],
"columns": [
@ -1047,6 +1093,13 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_avatar_entities",
"label": "Avatar Entities",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_adjust_locks",
"label": "Lock / Unlock",
@ -1488,6 +1541,24 @@
}
]
},
{
"name": "messages_mixer",
"label": "Messages Mixer",
"assignment-types": [
4
],
"settings": [
{
"name": "max_node_messages_per_second",
"type": "int",
"label": "Maximum Message Rate",
"help": "Maximum message send rate (messages per second) per node",
"placeholder": 1000,
"default": 1000,
"advanced": true
}
]
},
{
"name": "entity_server_settings",
"label": "Entities",
@ -2013,6 +2084,23 @@
}
]
},
{
"name": "domain_server",
"label": "Setup Domain Server",
"restart": false,
"hidden": true,
"settings": [
{
"name": "network_address",
"default": ""
},
{
"name": "network_port",
"type": "int",
"default": 0
}
]
},
{
"name": "installed_content",
"label": "Installed Content",

View file

@ -129,9 +129,10 @@ function getCurrentDomainIDType() {
return DOMAIN_ID_TYPE_UNKNOWN;
}
if (DomainInfo !== null) {
if (DomainInfo.name !== undefined) {
return DOMAIN_ID_TYPE_TEMP;
}
// Disabled because detecting as temp domain... and we're not even using temp domains right now.
// if (DomainInfo.name !== undefined) {
// return DOMAIN_ID_TYPE_TEMP;
// }
return DOMAIN_ID_TYPE_FULL;
}
return DOMAIN_ID_TYPE_UNKNOWN;
@ -504,7 +505,7 @@ function createDomainIDPrompt(callback) {
swal({
title: 'Finish Registering Domain',
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,
confirmButtonText: "Create",
closeOnConfirm: false,

View file

@ -193,7 +193,11 @@ function promptToCreateDomainID() {
var formJSON = {
"metaverse": {
"automatic_networking": "full",
"id": domainID
},
"descriptors": {
"world_name": label
}
};
@ -461,6 +465,7 @@ function savePermissions() {
"standard_permissions": [
{
"id_can_connect": anonymousCanConnect,
"id_can_rez_avatar_entities": anonymousCanConnect,
"id_can_rez": anonymousCanRez,
"id_can_rez_certified": anonymousCanRez,
"id_can_rez_tmp": anonymousCanRez,
@ -469,6 +474,7 @@ function savePermissions() {
},
{
"id_can_connect": friendsCanConnect,
"id_can_rez_avatar_entities": friendsCanConnect,
"id_can_rez": friendsCanRez,
"id_can_rez_certified": friendsCanRez,
"id_can_rez_tmp": friendsCanRez,
@ -477,6 +483,7 @@ function savePermissions() {
},
{
"id_can_connect": loggedInCanConnect,
"id_can_rez_avatar_entities": loggedInCanConnect,
"id_can_rez": loggedInCanRez,
"id_can_rez_certified": loggedInCanRez,
"id_can_rez_tmp": loggedInCanRez,
@ -486,6 +493,7 @@ function savePermissions() {
{
"id_can_adjust_locks": localhostPermissions,
"id_can_connect": localhostPermissions,
"id_can_rez_avatar_entities": localhostPermissions,
"id_can_connect_past_max_capacity": localhostPermissions,
"id_can_kick": localhostPermissions,
"id_can_replace_content": localhostPermissions,

View file

@ -353,6 +353,7 @@ void DomainGatekeeper::updateNodePermissions() {
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
userPerms.permissions |= NodePermissions::Permission::canGetAndSetPrivateUserData;
userPerms.permissions |= NodePermissions::Permission::canRezAvatarEntities;
} else {
// at this point we don't have a sending socket for packets from this node - assume it is the active socket
// or the public socket if we haven't activated a socket for the node yet
@ -448,6 +449,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
userPerms.permissions |= NodePermissions::Permission::canGetAndSetPrivateUserData;
userPerms.permissions |= NodePermissions::Permission::canRezAvatarEntities;
newNode->setPermissions(userPerms);
return newNode;
}

View file

@ -60,6 +60,7 @@
#include <Gzip.h>
#include <OctreeDataUtils.h>
#include <ThreadHelpers.h>
using namespace std::chrono;
@ -68,6 +69,9 @@ Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";
const QString PUBLIC_SOCKET_ADDRESS_KEY = "network_address";
const QString PUBLIC_SOCKET_PORT_KEY = "network_port";
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
const int MIN_PORT = 1;
const int MAX_PORT = 65535;
@ -827,9 +831,11 @@ void DomainServer::setupNodeListAndAssignments() {
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
_assetClientThread.setObjectName("AssetClient Thread");
QString name = "AssetClient Thread";
_assetClientThread.setObjectName(name);
auto assetClient = DependencyManager::set<AssetClient>();
assetClient->moveToThread(&_assetClientThread);
connect(&_assetClientThread, &QThread::started, [name] { setThreadName(name.toStdString()); });
_assetClientThread.start();
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
@ -901,14 +907,13 @@ void DomainServer::setupAutomaticNetworking() {
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
auto nodeList = DependencyManager::get<LimitedNodeList>();
// send any public socket changes to the data server so nodes can find us at our new IP
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this,
&DomainServer::performIPAddressPortUpdate);
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
auto nodeList = DependencyManager::get<LimitedNodeList>();
// send any public socket changes to the data server so nodes can find us at our new IP
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
this, &DomainServer::performIPAddressUpdate);
// have the LNL enable public socket updating via STUN
nodeList->startSTUNPublicSocketUpdate();
}
@ -1504,13 +1509,23 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
return socketObject;
}
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
void DomainServer::performIPAddressPortUpdate(const HifiSockAddr& newPublicSockAddr) {
const QString& DOMAIN_SERVER_SETTINGS_KEY = "domain_server";
const QString& publicSocketAddress = newPublicSockAddr.getAddress().toString();
const int publicSocketPort = newPublicSockAddr.getPort();
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString());
sendHeartbeatToMetaverse(publicSocketAddress, publicSocketPort);
QJsonObject rootObject;
QJsonObject domainServerObject;
domainServerObject.insert(PUBLIC_SOCKET_ADDRESS_KEY, publicSocketAddress);
domainServerObject.insert(PUBLIC_SOCKET_PORT_KEY, publicSocketPort);
rootObject.insert(DOMAIN_SERVER_SETTINGS_KEY, domainServerObject);
QJsonDocument doc(rootObject);
_settingsManager.recurseJSONObjectAndOverwriteSettings(rootObject, DomainSettings);
}
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress, const int port) {
// Setup the domain object to send to the data server
QJsonObject domainObject;
@ -1520,10 +1535,20 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
static const QString PROTOCOL_VERSION_KEY = "protocol";
domainObject[PROTOCOL_VERSION_KEY] = protocolVersionsSignatureBase64();
// add networking
static const QString NETWORK_ADDRESS_SETTINGS_KEY = "domain_server." + PUBLIC_SOCKET_ADDRESS_KEY;
const QString networkAddressFromSettings = _settingsManager.valueForKeyPath(NETWORK_ADDRESS_SETTINGS_KEY).toString();
if (!networkAddress.isEmpty()) {
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
domainObject[PUBLIC_SOCKET_ADDRESS_KEY] = networkAddress;
} else if (!networkAddressFromSettings.isEmpty()) {
domainObject[PUBLIC_SOCKET_ADDRESS_KEY] = networkAddressFromSettings;
}
static const QString PORT_SETTINGS_KEY = "domain_server." + PUBLIC_SOCKET_PORT_KEY;
const int portFromSettings = _settingsManager.valueForKeyPath(PORT_SETTINGS_KEY).toInt();
if (port != 0) {
domainObject[PUBLIC_SOCKET_PORT_KEY] = port;
} else if (portFromSettings != 0) {
domainObject[PUBLIC_SOCKET_PORT_KEY] = portFromSettings;
}
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";

View file

@ -112,8 +112,8 @@ private slots:
void setupPendingAssignmentCredits();
void sendPendingTransactionsToServer();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
void performIPAddressPortUpdate(const HifiSockAddr& newPublicSockAddr);
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString(), int()); }
void sendHeartbeatToIceServer();
void nodePingMonitor();
@ -176,7 +176,7 @@ private:
void setupAutomaticNetworking();
void setupICEHeartbeatForFullNetworking();
void setupHeartbeatToMetaverse();
void sendHeartbeatToMetaverse(const QString& networkAddress);
void sendHeartbeatToMetaverse(const QString& networkAddress, const int port);
void randomizeICEServerAddress(bool shouldTriggerHostLookup);

View file

@ -37,6 +37,7 @@
#include <SettingHandle.h>
#include <SettingHelpers.h>
#include <FingerprintUtils.h>
#include <ModerationFlags.h>
#include "DomainServerNodeData.h"
@ -527,6 +528,28 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
*newAdminRoles = adminRoles;
}
if (oldVersion < 2.5) {
// Default values for new canRezAvatarEntities permission.
unpackPermissions();
std::list<std::unordered_map<NodePermissionsKey, NodePermissionsPointer>> permissionsSets{
_standardAgentPermissions.get(),
_agentPermissions.get(),
_ipPermissions.get(),
_macPermissions.get(),
_machineFingerprintPermissions.get(),
_groupPermissions.get(),
_groupForbiddens.get()
};
foreach (auto permissionsSet, permissionsSets) {
for (auto entry : permissionsSet) {
const auto& userKey = entry.first;
if (permissionsSet[userKey]->can(NodePermissions::Permission::canConnectToDomain)) {
permissionsSet[userKey]->set(NodePermissions::Permission::canRezAvatarEntities);
}
}
}
packPermissions();
}
// write the current description version to our settings
*versionVariant = _descriptionVersion;
@ -863,6 +886,20 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
// pull the UUID being kicked from the packet
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
bool hasOptionalBanParameters = false;
int banParameters;
bool banByUsername;
bool banByFingerprint;
bool banByIP;
// pull optional ban parameters from the packet
if (message.data()->getSize() == (NUM_BYTES_RFC4122_UUID + sizeof(int))) {
hasOptionalBanParameters = true;
message->readPrimitive(&banParameters);
banByUsername = banParameters & ModerationFlags::BanFlags::BAN_BY_USERNAME;
banByFingerprint = banParameters & ModerationFlags::BanFlags::BAN_BY_FINGERPRINT;
banByIP = banParameters & ModerationFlags::BanFlags::BAN_BY_IP;
}
if (!nodeUUID.isNull() && nodeUUID != sendingNode->getUUID()) {
// make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
@ -881,16 +918,20 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
if (!verifiedUsername.isEmpty()) {
// if we have a verified user name for this user, we first apply the kick to the username
// check if there were already permissions
bool hadPermissions = havePermissionsForName(verifiedUsername);
// if we have optional ban parameters, we should ban the username based on the parameter
if (!hasOptionalBanParameters || banByUsername) {
// check if there were already permissions
bool hadPermissions = havePermissionsForName(verifiedUsername);
// grab or create permissions for the given username
auto userPermissions = _agentPermissions[matchingNode->getPermissions().getKey()];
// grab or create permissions for the given username
auto userPermissions = _agentPermissions[matchingNode->getPermissions().getKey()];
newPermissions = !hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain);
newPermissions =
!hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain);
// ensure that the connect permission is clear
userPermissions->clear(NodePermissions::Permission::canConnectToDomain);
// ensure that the connect permission is clear
userPermissions->clear(NodePermissions::Permission::canConnectToDomain);
}
}
// if we didn't have a username, or this domain-server uses the "multi-kick" setting to
@ -898,7 +939,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
// then we remove connect permissions for the machine fingerprint (or IP as fallback)
const QString MULTI_KICK_SETTINGS_KEYPATH = "security.multi_kick_logged_in";
if (verifiedUsername.isEmpty() || valueOrDefaultValueForKeyPath(MULTI_KICK_SETTINGS_KEYPATH).toBool()) {
if (banByFingerprint || verifiedUsername.isEmpty() || valueOrDefaultValueForKeyPath(MULTI_KICK_SETTINGS_KEYPATH).toBool()) {
// remove connect permissions for the machine fingerprint
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
if (nodeData) {
@ -923,36 +964,39 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
fingerprintPermissions->clear(NodePermissions::Permission::canConnectToDomain);
}
} else {
// if no node data, all we can do is IP address
auto& kickAddress = matchingNode->getActiveSocket()
? matchingNode->getActiveSocket()->getAddress()
: matchingNode->getPublicSocket().getAddress();
// if no node data, all we can do is ban by IP address
banByIP = true;
}
}
if (banByIP) {
auto& kickAddress = matchingNode->getActiveSocket()
? matchingNode->getActiveSocket()->getAddress()
: matchingNode->getPublicSocket().getAddress();
// probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server
// it is always 127.0.0.1, but looking at the public and local addresses just to be sure
// TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we
// do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it.
if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() ||
kickAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
kickAddress.isLoopback() ) {
qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest";
return;
}
// probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server
// it is always 127.0.0.1, but looking at the public and local addresses just to be sure
// TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we
// do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it.
if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() ||
kickAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
kickAddress.isLoopback() ) {
qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest";
return;
}
NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid());
NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid());
// check if there were already permissions for the IP
bool hadIPPermissions = hasPermissionsForIP(kickAddress);
// check if there were already permissions for the IP
bool hadIPPermissions = hasPermissionsForIP(kickAddress);
// grab or create permissions for the given IP address
auto ipPermissions = _ipPermissions[ipAddressKey];
// grab or create permissions for the given IP address
auto ipPermissions = _ipPermissions[ipAddressKey];
if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
newPermissions = true;
if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
newPermissions = true;
ipPermissions->clear(NodePermissions::Permission::canConnectToDomain);
}
ipPermissions->clear(NodePermissions::Permission::canConnectToDomain);
}
}
@ -1448,6 +1492,8 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
SettingsBackupFlag settingsBackupFlag) {
QJsonObject responseObject;
responseObject["version"] = _descriptionVersion; // Domain settings version number.
if (!typeValue.isEmpty() || authentication == Authenticated) {
// convert the string type value to a QJsonValue
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());

View file

@ -90,8 +90,8 @@ endif()
if 'Windows' == system:
self.exe = os.path.join(self.path, 'vcpkg.exe')
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.vcpkgHash = 'a650db47a63ccdc9904b68ddd16af74772e7e78170b513ea8de5a3b47d032751a3b73dcc7526d88bcb500753ea3dd9880639ca842bb176e2bddb1710f9a58cd3'
self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/vcpkg-win32-client-20210122.zip'
self.vcpkgHash = '3df86b7d58c827bf08b3b7744f456f414b86a6d9bd58a507924103bc5a88f01ee495ce1f0fbf2f5b27f1ef6bfb1526e580ec13d3b9f87a89a462b3c50589fd6a'
self.hostTriplet = 'x64-windows'
if usePrebuilt:
self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx"

View file

@ -19,16 +19,17 @@ Item {
property string url: ""
property string scriptUrl: null
property bool useBackground: true
property string userAgent: ""
onUrlChanged: {
load(root.url, root.scriptUrl, root.useBackground);
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
onScriptUrlChanged: {
if (root.item) {
root.item.scriptUrl = root.scriptUrl;
} else {
load(root.url, root.scriptUrl, root.useBackground);
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
}
@ -36,13 +37,21 @@ Item {
if (root.item) {
root.item.useBackground = root.useBackground;
} else {
load(root.url, root.scriptUrl, root.useBackground);
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
}
onUserAgentChanged: {
if (root.item) {
root.item.userAgent = root.userAgent;
} else {
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
}
property var item: null
function load(url, scriptUrl, useBackground) {
function load(url, scriptUrl, useBackground, userAgent) {
// Ensure we reset any existing item to "about:blank" to ensure web audio stops: DEV-2375
if (root.item != null) {
root.item.url = "about:blank"
@ -54,11 +63,12 @@ Item {
root.item.url = url
root.item.scriptUrl = scriptUrl
root.item.useBackground = useBackground
root.item.userAgent = userAgent
})
}
Component.onCompleted: {
load(root.url, root.scriptUrl, root.useBackground);
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
signal sendToScript(var message);

View file

@ -13,10 +13,13 @@ Item {
property alias url: webViewCore.url
property alias canGoBack: webViewCore.canGoBack
property alias webViewCore: webViewCore
property alias webViewCoreProfile: webViewCore.profile
// FIXME - This was commented out to allow for manual setting of the userAgent.
//
// property alias webViewCoreProfile: webViewCore.profile
property string webViewCoreUserAgent
property bool useBackground: webViewCore.useBackground
property string userAgent: webViewCore.profile.httpUserAgent
property string userScriptUrl: ""
property string urlTag: "noDownload=false";
@ -34,6 +37,10 @@ Item {
permissionPopupBackground.visible = false;
}
onUserAgentChanged: {
webViewCore.profile.httpUserAgent = flick.userAgent;
}
StylesUIt.HifiConstants {
id: hifi
}
@ -74,7 +81,7 @@ Item {
function onLoadingChanged(loadRequest) {
if (WebEngineView.LoadStartedStatus === loadRequest.status) {
webViewCore.profile.httpUserAgent = flick.userAgent;
// Required to support clicking on "hifi://" links
var url = loadRequest.url.toString();
url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
@ -101,7 +108,6 @@ Item {
height: parent.height
backgroundColor: (flick.useBackground) ? "white" : "transparent"
profile: HFWebEngineProfile;
settings.pluginsEnabled: true
settings.touchIconsEnabled: true
settings.allowRunningInsecureContent: true
@ -136,8 +142,10 @@ Item {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
if (webViewCoreUserAgent !== undefined) {
webViewCore.profile.httpUserAgent = webViewCoreUserAgent
if (flick.userAgent !== undefined) {
webViewCore.profile.httpUserAgent = flick.userAgent;
webViewCore.profile.offTheRecord = false;
webViewCore.profile.storageName = "qmlWebEngine";
} else {
webViewCore.profile.httpUserAgent += " (VircadiaInterface)";
}

View file

@ -11,12 +11,14 @@ Item {
property alias url: webViewCore.url
property alias canGoBack: webViewCore.canGoBack
property alias webViewCore: webViewCore
property alias webViewCoreProfile: webViewCore.profile
property string webViewCoreUserAgent
// FIXME - This was commented out to allow for manual setting of the userAgent.
//
// property alias webViewCoreProfile: webViewCore.profile
property bool useBackground: webViewCore.useBackground
property alias useBackground: webViewCore.useBackground
property alias userAgent: webViewCore.userAgent
property string userScriptUrl: ""
property string urlTag: "noDownload=false";
property string urlTag: "noDownload=false"
signal newViewRequestedCallback(var request)
signal loadingChangedCallback(var loadRequest)

View file

@ -27,7 +27,11 @@ Item {
}
*/
property alias viewProfile: webroot.webViewCoreProfile
// FIXME - Reimplement profiles for... why? Was it so that new windows opened share the same profile?
// Are profiles written to by the webengine during the session?
// Removed in PR Feature/web entity user agent #988
//
// property alias viewProfile: webroot.webViewCoreProfile
FlickableWebViewCore {
id: webroot

View file

@ -25,7 +25,11 @@ Item {
property bool isDesktop: false
property alias url: web.url
property alias webView: web.webViewCore
property alias profile: web.webViewCoreProfile
// FIXME - Reimplement profiles for... why? Was it so that new windows opened share the same profile?
// Are profiles written to by the webengine during the session?
// Removed in PR Feature/web entity user agent #988
//
// property alias profile: web.webViewCoreProfile
property bool remove: false
property bool closeButtonVisible: true

View file

@ -24,6 +24,7 @@ Item {
property alias flickable: webroot.interactive
property alias blurOnCtrlShift: webroot.blurOnCtrlShift
property alias useBackground: webroot.useBackground
property alias userAgent: webroot.userAgent
function stop() {
webroot.stop();
@ -37,7 +38,11 @@ Item {
}
*/
property alias viewProfile: webroot.webViewCoreProfile
// FIXME - Reimplement profiles for... why? Was it so that new windows opened share the same profile?
// Are profiles written to by the webengine during the session?
// Removed in PR Feature/web entity user agent #988
//
// property alias viewProfile: webroot.webViewCoreProfile
FlickableWebViewCore {
id: webroot

View file

@ -518,7 +518,12 @@ Rectangle {
glyphText: "\ue02e"
onClicked: {
adjustWearables.open(currentAvatar);
if (!AddressManager.isConnected || Entities.canRezAvatarEntities()) {
adjustWearables.open(currentAvatar);
} else {
Window.alert("You cannot use wearables on this domain.")
}
}
}
@ -529,7 +534,11 @@ Rectangle {
glyphText: wearablesFrozen ? hifi.glyphs.lock : hifi.glyphs.unlock;
onClicked: {
emitSendToScript({'method' : 'toggleWearablesFrozen'});
if (!AddressManager.isConnected || Entities.canRezAvatarEntities()) {
emitSendToScript({'method' : 'toggleWearablesFrozen'});
} else {
Window.alert("You cannot use wearables on this domain.")
}
}
}
}

View file

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

View file

@ -506,6 +506,7 @@ Rectangle {
id: itemCell;
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore";
property bool isButton: styleData.role === "mute" || styleData.role === "kick";
property bool isBan: styleData.role === "kick";
property bool isAvgAudio: styleData.role === "avgAudioLevel";
opacity: !isButton ? (model && model.isPresent ? 1.0 : 0.4) : 1.0; // Admin actions shouldn't turn gray
@ -605,7 +606,9 @@ Rectangle {
color: 2; // Red
visible: isButton;
enabled: !nameCard.isReplicated;
anchors.centerIn: parent;
anchors.verticalCenter: itemCell.verticalCenter;
anchors.left: parent.left;
anchors.leftMargin: styleData.role === "kick" ? 4 : 18;
width: 32;
height: 32;
onClicked: {
@ -620,7 +623,39 @@ Rectangle {
HiFiGlyphs {
text: (styleData.role === "kick") ? hifi.glyphs.error : hifi.glyphs.muted;
// Size
size: parent.height*1.3;
size: parent.height * 1.3;
// Anchors
anchors.fill: parent;
// Style
horizontalAlignment: Text.AlignHCenter;
color: enabled ? hifi.buttons.textColor[actionButton.color]
: hifi.buttons.disabledTextColor[actionButton.colorScheme];
}
}
HifiControlsUit.Button {
id: hardBanButton;
color: 2; // Red
visible: isBan;
enabled: !nameCard.isReplicated;
anchors.verticalCenter: itemCell.verticalCenter;
anchors.left: parent.left;
anchors.leftMargin: actionButton.width + 14;
width: 32;
height: 32;
onClicked: {
Users[styleData.role](model.sessionId, Users.BAN_BY_USERNAME | Users.BAN_BY_FINGERPRINT | Users.BAN_BY_IP);
UserActivityLogger["palAction"](styleData.role, model.sessionId);
if (styleData.role === "kick") {
nearbyUserModelData.splice(model.userIndex, 1);
nearbyUserModel.remove(model.userIndex); // after changing nearbyUserModelData, b/c ListModel can frob the data
}
}
// muted/error glyphs
HiFiGlyphs {
text: hifi.glyphs.alert;
// Size
size: parent.height * 1.3;
// Anchors
anchors.fill: parent;
// Style

View file

@ -15,6 +15,7 @@
import QtQuick 2.10
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
@ -104,6 +105,11 @@ Rectangle {
AudioScriptingInterface.setSystemInjectorGain(sliderValue);
}
}
function updateNoiseReductionThresholdFromQML(sliderValue) {
if (AudioScriptingInterface.getNoiseReductionThreshold() !== sliderValue) {
AudioScriptingInterface.setNoiseReductionThreshold(sliderValue);
}
}
Component.onCompleted: {
enablePeakValues();
@ -164,7 +170,7 @@ Rectangle {
x: 2 * margins.paddings;
width: parent.width;
// switch heights + 2 * top margins
height: (root.switchHeight) * 6 + 48;
height: (bar.currentIndex === 0) ? (root.switchHeight) * 2 + 48 : (root.switchHeight) * 4 + 48;
anchors.top: firstSeparator.bottom;
anchors.topMargin: 10;
@ -175,6 +181,7 @@ Rectangle {
height: parent.height;
anchors.top: parent.top
anchors.left: parent.left;
HifiControlsUit.Switch {
id: muteMic;
height: root.switchHeight;
@ -193,45 +200,11 @@ Rectangle {
}
}
HifiControlsUit.Switch {
id: noiseReductionSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: muteMic.bottom;
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Noise Reduction";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.noiseReduction;
onCheckedChanged: {
AudioScriptingInterface.noiseReduction = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding
}
}
HifiControlsUit.Switch {
id: acousticEchoCancellationSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: noiseReductionSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Echo Cancellation";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.acousticEchoCancellation;
onCheckedChanged: {
AudioScriptingInterface.acousticEchoCancellation = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.acousticEchoCancellation; });
}
}
HifiControlsUit.Switch {
id: pttSwitch
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: acousticEchoCancellationSwitch.bottom;
anchors.top: muteMic.bottom;
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk");
@ -254,6 +227,7 @@ Rectangle {
height: parent.height;
anchors.top: parent.top
anchors.left: switchContainer.right;
HifiControlsUit.Switch {
id: warnMutedSwitch
height: root.switchHeight;
@ -271,7 +245,6 @@ Rectangle {
}
}
HifiControlsUit.Switch {
id: audioLevelSwitch
height: root.switchHeight;
@ -519,13 +492,282 @@ Rectangle {
anchors.top: systemInjectorGainContainer.bottom;
anchors.topMargin: 10;
}
Item {
id: noiseReductionHeader
x: margins.paddings;
width: parent.width - margins.paddings * 2;
height: 36;
anchors.top: secondSeparator.bottom;
anchors.topMargin: 10;
HiFiGlyphs {
width: margins.sizeCheckBox;
text: hifi.glyphs.mic;
color: hifi.colors.white;
anchors.left: parent.left;
anchors.leftMargin: -size / 4; // The glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
size: 30;
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeText + margins.sizeLevel;
anchors.left: parent.left;
anchors.leftMargin: margins.sizeCheckBox;
size: 22;
color: hifi.colors.white;
text: qsTr("Noise Reduction");
}
}
Item {
id: noiseReductionSwitches;
x: 2 * margins.paddings;
width: parent.width;
// switch heights + 2 * top margins
height: (root.switchHeight) * 5 + 48;
anchors.top: noiseReductionHeader.bottom;
anchors.topMargin: 0;
Item {
id: noiseReductionSwitchContainer;
x: margins.paddings;
width: parent.width / 2;
height: parent.height;
anchors.top: parent.top;
anchors.left: parent.left;
HifiControlsUit.Switch {
id: acousticEchoCancellationSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: noiseReductionSwitchContainer.top
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Echo Cancellation";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.acousticEchoCancellation;
onCheckedChanged: {
AudioScriptingInterface.acousticEchoCancellation = checked;
checked = Qt.binding(function () { return AudioScriptingInterface.acousticEchoCancellation; });
}
}
HifiControlsUit.Switch {
id: noiseReductionSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: acousticEchoCancellationSwitch.bottom;
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Noise Reduction";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.noiseReduction;
onCheckedChanged: {
AudioScriptingInterface.noiseReduction = checked;
checked = Qt.binding(function () { return AudioScriptingInterface.noiseReduction; }); // restore binding
}
}
HifiControlsUit.Switch {
id: noiseReductionAutomaticSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: noiseReductionSwitch.bottom;
anchors.topMargin: 24;
anchors.left: parent.left;
labelTextOn: "Manual Noise Reduction";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: !AudioScriptingInterface.noiseReductionAutomatic;
visible: AudioScriptingInterface.noiseReduction;
onCheckedChanged: {
AudioScriptingInterface.noiseReductionAutomatic = !checked;
checked = Qt.binding(function () { return !AudioScriptingInterface.noiseReductionAutomatic; }); // restore binding
}
}
}
}
Item {
id: noiseReductionThresholdContainer
x: margins.paddings;
anchors.top: noiseReductionSwitches.bottom;
anchors.topMargin: 16;
width: parent.width - margins.paddings * 2;
height: avatarGainSliderTextMetrics.height + 10;
visible: AudioScriptingInterface.noiseReduction && !AudioScriptingInterface.noiseReductionAutomatic;
HifiControlsUit.Slider {
id: noiseReductionThresholdSlider
anchors.right: parent.right
height: noiseReductionThresholdSliderTextMetrics.height
width: 200
minimumValue: 0.0
maximumValue: 1.0
stepSize: 0.05
value: AudioScriptingInterface.getNoiseReductionThreshold()
onValueChanged: {
updateNoiseReductionThresholdFromQML(value);
}
onPressedChanged: {
if (!pressed) {
updateNoiseReductionThresholdFromQML(value);
}
}
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
noiseReductionThresholdSlider.value = 0.0;
}
onPressed: {
// Pass through to Slider
mouse.accepted = false;
}
onReleased: {
// the above mouse.accepted seems to make this
// never get called, nonetheless...
mouse.accepted = false;
}
}
}
TextMetrics {
id: noiseReductionThresholdSliderTextMetrics
text: noiseReductionThresholdSliderText.text
font: noiseReductionThresholdSliderText.font
}
RalewayRegular {
// The slider for my card is special, it controls the master gain
id: noiseReductionThresholdSliderText;
text: "Audio input threshold";
size: 16;
anchors.left: parent.left;
color: hifi.colors.white;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
Item {
id: noisePeak
anchors.right: parent.right
anchors.rightMargin: 5;
anchors.top: noiseReductionThresholdSlider.bottom;
width: noiseReductionThresholdSlider.width - 10;
height: 8;
Text {
id: status;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
visible: AudioScriptingInterface.muted;
color: "#E2334D";
text: "MUTED";
font.pointSize: 10;
}
Item {
id: noiseBar;
property bool gated: false;
property var level: AudioScriptingInterface.inputLevel;
width: parent.width;
height: parent.height;
anchors { fill: parent }
visible: !status.visible;
function onNoiseGateOpened() {
noiseBar.gated = false;
}
function onNoiseGateClosed() {
noiseBar.gated = true;
}
function onInputLevelChanged(level) {
noiseBar.level = level;
}
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(onNoiseGateOpened);
AudioScriptingInterface.noiseGateClosed.connect(onNoiseGateClosed);
AudioScriptingInterface.inputLevelChanged.connect(onInputLevelChanged);
}
Component.onDestruction: {
AudioScriptingInterface.noiseGateOpened.disconnect(onNoiseGateOpened);
AudioScriptingInterface.noiseGateClosed.disconnect(onNoiseGateClosed);
AudioScriptingInterface.inputLevelChanged.disconnect(onInputLevelChanged);
}
Rectangle { // base
radius: 4;
anchors { fill: parent }
color: colors.gutter;
}
Rectangle { // noiseMask
id: noiseMask;
width: parent.width * noiseBar.level;
radius: 5;
anchors {
bottom: parent.bottom;
bottomMargin: 0;
top: parent.top;
topMargin: 0;
left: parent.left;
leftMargin: 0;
}
}
LinearGradient {
anchors { fill: noiseMask }
source: noiseMask
start: Qt.point(0, 0);
end: Qt.point(noiseBar.width, 0);
gradient: Gradient {
GradientStop {
position: 0;
color: noiseBar.gated ? "#E2334D" : "#39A38F";
}
GradientStop {
position: 1;
color: noiseBar.gated ? "#E2334D" : "#39A38F";
}
}
}
}
}
}
Separator {
id: thirdSeparator;
anchors.top: noiseReductionThresholdContainer.bottom;
anchors.topMargin: 14;
}
Item {
id: inputDeviceHeader
x: margins.paddings;
width: parent.width - margins.paddings*2;
height: 36;
anchors.top: secondSeparator.bottom;
anchors.top: thirdSeparator.bottom;
anchors.topMargin: 10;
HiFiGlyphs {
@ -597,19 +839,19 @@ Rectangle {
}
}
AudioControls.InputPeak {
id: inputLevel
anchors.right: parent.right
peak: model.peak;
anchors.verticalCenter: parent.verticalCenter
visible: ((bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR)) &&
AudioScriptingInterface.devices.input.peakValuesAvailable;
id: inputLevel
anchors.right: parent.right
peak: model.peak;
anchors.verticalCenter: parent.verticalCenter
visible: ((bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR)) &&
AudioScriptingInterface.devices.input.peakValuesAvailable;
}
}
}
Separator {
id: thirdSeparator;
id: fourthSeparator;
anchors.top: inputView.bottom;
anchors.topMargin: 10;
}
@ -617,7 +859,7 @@ Rectangle {
Item {
id: outputDeviceHeader;
anchors.topMargin: 10;
anchors.top: thirdSeparator.bottom;
anchors.top: fourthSeparator.bottom;
x: margins.paddings;
width: parent.width - margins.paddings*2
height: 36
@ -684,4 +926,4 @@ Rectangle {
}
}
}
}
}

View file

@ -112,8 +112,7 @@ MessageBox {
popup.button1text = 'CANCEL'
popup.titleText = 'Get Avatars'
popup.bodyText = 'Get avatars from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' +
'Wear avatars in <b><a href="app://purchases">Inventory.</a></b>'
popup.bodyText = 'Get avatars from the Community Bazaar. (Coming soon!)'
popup.onLinkClicked = function(link) {
popup.close();

View file

@ -28,13 +28,18 @@ Rectangle {
fillMode: Image.PreserveAspectFit
source: "../../../images/vircadia-banner.svg"
}
Item { height: 30; width: 1 }
Item { height: 25; width: 1 }
Column {
id: buildColumm
id: buildColumn
anchors.left: parent.left
anchors.leftMargin: 70
anchors.leftMargin: 0
RalewayRegular {
text: "Build " + About.buildVersion
text: "Interface"
size: 16
color: "white"
}
RalewayRegular {
text: "Build " + About.buildVersion + " " + About.releaseName
size: 16
color: "white"
}
@ -54,14 +59,25 @@ Rectangle {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "<a href=\"https://github.com/vircadia/vircadia\">Vircadia Github</a>."
text: "<a href=\"https://vircadia.com\">Website</a>"
size: 20
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 {
spacing: 5
Image {
@ -117,7 +133,7 @@ Rectangle {
Item { height: 20; width: 1 }
RalewayRegular {
color: "white"
text: "© 2019-2020 Vircadia contributors."
text: "© 2019 - 2021 Vircadia contributors."
size: 14
}
RalewayRegular {
@ -135,5 +151,23 @@ Rectangle {
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,56 @@
"/": "/0.155245,-0.941538,23.9289/0,0.791589,0,0.611053"
},
"Entities": [
{
"id": "{0a199807-4a83-4286-b09c-f21124627c3e}",
"type": "Box",
"name": "Config Wizard Loader",
"lastEdited": 1613737207915514,
"visible": false,
"position": {
"x": -1.2722,
"y": 0.4266,
"z": 24.2307
},
"dimensions": {
"x": 0.20000000298023224,
"y": 0.20000000298023224,
"z": 0.20000000298023224
},
"rotation": {
"x": 0,
"y": -0.7660443782806396,
"z": 0,
"w": -0.6427876949310303
},
"created": 1613736996738696,
"lastEditedBy": "{ff9b500e-e450-4127-b41f-1c42be16f71b}",
"queryAACube": {
"x": -0.17320507764816284,
"y": -0.17320507764816284,
"z": -0.17320507764816284,
"scale": 0.3464101552963257
},
"grab": {
"grabbable": false
},
"damping": 0,
"angularDamping": 0,
"collisionless": true,
"ignoreForCollisions": true,
"script": "https://cdn-1.vircadia.com/us-e-1/DomainContent/Tutorial/Apps/configWizard/dist/wizardLoader.js",
"color": {
"red": 0,
"green": 180,
"blue": 239
},
"shape": "Cube",
"clientOnly": false,
"avatarEntity": false,
"localEntity": false,
"faceCamera": false,
"isFacingAvatar": false
},
{
"id": "{eb485a2d-2040-42f6-a960-51c88e2434b9}",
"type": "Box",

View file

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

View file

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

View file

@ -254,6 +254,7 @@
#include "AboutUtil.h"
#include "ExternalResource.h"
#include <ThreadHelpers.h>
#if defined(Q_OS_WIN)
#include <VersionHelpers.h>
@ -1168,6 +1169,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (!DISABLE_WATCHDOG) {
auto deadlockWatchdogThread = new DeadlockWatchdogThread();
deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId());
connect(deadlockWatchdogThread, &QThread::started, [] { setThreadName("DeadlockWatchdogThread"); });
deadlockWatchdogThread->start();
// Pause the deadlock watchdog when we sleep, or it might
@ -1304,6 +1306,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_entityServerConnectionTimer.setSingleShot(true);
connect(&_entityServerConnectionTimer, &QTimer::timeout, this, &Application::setFailedToConnectToEntityServer);
connect(&domainHandler, &DomainHandler::confirmConnectWithoutAvatarEntities,
this, &Application::confirmConnectWithoutAvatarEntities);
connect(&domainHandler, &DomainHandler::connectedToDomain, this, [this]() {
if (!isServerlessMode()) {
_entityServerConnectionTimer.setInterval(ENTITY_SERVER_ADDED_TIMEOUT);
@ -2455,13 +2460,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
});
EntityItem::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
BillboardModeHelpers::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation,
BillboardMode billboardMode, const glm::vec3& frustumPos, bool rotate90x) {
const glm::quat ROTATE_90X = glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT);
if (billboardMode == BillboardMode::YAW) {
//rotate about vertical to face the camera
glm::vec3 dPosition = frustumPos - position;
// If x and z are 0, atan(x, z) is undefined, so default to 0 degrees
float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z);
return glm::quat(glm::vec3(0.0f, yawRotation, 0.0f));
glm::quat result = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)) * rotation;
if (rotate90x) {
result *= ROTATE_90X;
}
return result;
} else if (billboardMode == BillboardMode::FULL) {
// use the referencial from the avatar, y isn't always up
glm::vec3 avatarUP = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
@ -2470,18 +2481,22 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// make sure s is not NaN for any component
if (glm::length2(s) > 0.0f) {
return glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP)));
glm::quat result = glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP))) * rotation;
if (rotate90x) {
result *= ROTATE_90X;
}
return result;
}
}
return rotation;
});
EntityItem::setPrimaryViewFrustumPositionOperator([this]() {
BillboardModeHelpers::setPrimaryViewFrustumPositionOperator([this]() {
ViewFrustum viewFrustum;
copyViewFrustum(viewFrustum);
return viewFrustum.getPosition();
});
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); });
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID, unsigned int banFlags) { userKickConfirmation(nodeID, banFlags); });
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([=](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
@ -2977,6 +2992,8 @@ Application::~Application() {
qInstallMessageHandler(LogHandler::verboseMessageHandler);
#ifdef Q_OS_MAC
// 26 Feb 2021 - Tried re-enabling this call but OSX still crashes on exit.
//
// 10/16/2019 - Disabling this call. This causes known crashes (A), and it is not
// fully understood whether it might cause other unknown crashes (B).
//
@ -3561,7 +3578,7 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
_desktopRootItemCreated = true;
}
void Application::userKickConfirmation(const QUuid& nodeID) {
void Application::userKickConfirmation(const QUuid& nodeID, unsigned int banFlags) {
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
auto avatar = avatarHashMap->getAvatarBySessionID(nodeID);
@ -3586,7 +3603,7 @@ void Application::userKickConfirmation(const QUuid& nodeID) {
// ask the NodeList to kick the user with the given session ID
if (yes) {
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID, banFlags);
}
DependencyManager::get<UsersScriptingInterface>()->setWaitForKickResponse(false);
@ -4001,7 +4018,7 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
// If this is a first run we short-circuit the address passed in
if (_firstRun.get()) {
if (!BuildInfo::INITIAL_STARTUP_LOCATION.isEmpty()) {
if (!BuildInfo::PRELOADED_STARTUP_LOCATION.isEmpty()) {
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(NetworkingConstants::DEFAULT_VIRCADIA_ADDRESS);
Menu::getInstance()->triggerOption(MenuOption::HomeLocation);
}
@ -5196,6 +5213,7 @@ void getCpuUsage(vec3& systemAndUser) {
void setupCpuMonitorThread() {
initCpuUsage();
auto cpuMonitorThread = QThread::currentThread();
setThreadName("CPU Monitor Thread");
QTimer* timer = new QTimer();
timer->setInterval(50);
@ -7180,6 +7198,10 @@ void Application::updateWindowTitle() const {
QString buildVersion = " - Vircadia - "
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
+ " " + applicationVersion();
if (BuildInfo::RELEASE_NAME != "") {
buildVersion += " - " + BuildInfo::RELEASE_NAME;
}
QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" :
nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
@ -9155,6 +9177,32 @@ void Application::setShowBulletConstraintLimits(bool value) {
_physicsEngine->setShowBulletConstraintLimits(value);
}
void Application::confirmConnectWithoutAvatarEntities() {
if (_confirmConnectWithoutAvatarEntitiesDialog) {
// Dialog is already displayed.
return;
}
if (!getMyAvatar()->hasAvatarEntities()) {
// No avatar entities so continue with login.
DependencyManager::get<NodeList>()->getDomainHandler().setCanConnectWithoutAvatarEntities(true);
return;
}
QString continueMessage = "Your wearables will not display on this domain. Continue?";
_confirmConnectWithoutAvatarEntitiesDialog = OffscreenUi::asyncQuestion("Continue Without Wearables", continueMessage,
QMessageBox::Yes | QMessageBox::No);
if (_confirmConnectWithoutAvatarEntitiesDialog->getDialogItem()) {
QObject::connect(_confirmConnectWithoutAvatarEntitiesDialog, &ModalDialogListener::response, this, [=](QVariant answer) {
QObject::disconnect(_confirmConnectWithoutAvatarEntitiesDialog, &ModalDialogListener::response, this, nullptr);
_confirmConnectWithoutAvatarEntitiesDialog = nullptr;
bool shouldConnect = (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes);
DependencyManager::get<NodeList>()->getDomainHandler().setCanConnectWithoutAvatarEntities(shouldConnect);
});
}
}
void Application::createLoginDialog() {
const glm::vec3 LOGIN_DIMENSIONS { 0.89f, 0.5f, 0.01f };
const auto OFFSET = glm::vec2(0.7f, -0.1f);

View file

@ -50,6 +50,8 @@
#include <shared/ConicalViewFrustum.h>
#include <shared/FileLogger.h>
#include <RunningMarker.h>
#include <ModerationFlags.h>
#include <OffscreenUi.h>
#include "avatar/MyAvatar.h"
#include "FancyCamera.h"
@ -325,6 +327,8 @@ public:
int getOtherAvatarsReplicaCount() { return DependencyManager::get<AvatarHashMap>()->getReplicaCount(); }
void setOtherAvatarsReplicaCount(int count) { DependencyManager::get<AvatarHashMap>()->setReplicaCount(count); }
void confirmConnectWithoutAvatarEntities();
bool getLoginDialogPoppedUp() const { return _loginDialogPoppedUp; }
void createLoginDialog();
void updateLoginDialogPosition();
@ -608,7 +612,7 @@ private:
void toggleTabletUI(bool shouldOpen = false) const;
bool shouldCaptureMouse() const;
void userKickConfirmation(const QUuid& nodeID);
void userKickConfirmation(const QUuid& nodeID, unsigned int banFlags = ModerationFlags::getDefaultBanFlags());
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;
@ -723,6 +727,8 @@ private:
bool _loginDialogPoppedUp{ false };
bool _desktopRootItemCreated{ false };
ModalDialogListener* _confirmConnectWithoutAvatarEntitiesDialog { nullptr };
bool _developerMenuVisible{ false };
QString _previousAvatarSkeletonModel;
float _previousAvatarTargetScale;

View file

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

View file

@ -35,6 +35,7 @@ void ConnectionMonitor::init() {
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::confirmConnectWithoutAvatarEntities, this, &ConnectionMonitor::stopTimer);
connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::loginComplete, this, &ConnectionMonitor::startTimer);

View file

@ -509,7 +509,7 @@ Menu::Menu() {
action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::MaterialProceduralShaders, 0, false);
connect(action, &QAction::triggered, [action] {
MeshPartPayload::enableMaterialProceduralShaders = action->isChecked();
ModelMeshPartPayload::enableMaterialProceduralShaders = action->isChecked();
});
{
@ -556,8 +556,6 @@ Menu::Menu() {
});
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ToggleHipsFollowing, 0, false,
avatar.get(), SLOT(setToggleHips(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBaseOfSupport, 0, false,
avatar.get(), SLOT(setEnableDebugDrawBaseOfSupport(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,

View file

@ -211,7 +211,6 @@ namespace MenuOption {
const QString ThirdPerson = "Third Person Legacy";
const QString ThreePointCalibration = "3 Point Calibration";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
const QString ToggleHipsFollowing = "Toggle Hips Following";
const QString ToolWindow = "Tool Window";
const QString TransmitterDrive = "Transmitter Drive";
const QString TurnWithHead = "Turn using Head";

View file

@ -544,7 +544,7 @@ void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities)
QUuid entityOwnerID = entity->getOwningAvatarID();
AvatarSharedPointer avatar = getAvatarBySessionID(entityOwnerID);
if (avatar) {
avatar->clearAvatarEntity(entity->getID());
avatar->clearAvatarEntityInternal(entity->getID());
}
}
}
@ -767,6 +767,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
glm::vec3 rayDirectionInv = { rayDirection.x != 0.0f ? 1.0f / rayDirection.x : INFINITY,
rayDirection.y != 0.0f ? 1.0f / rayDirection.y : INFINITY,
rayDirection.z != 0.0f ? 1.0f / rayDirection.z : INFINITY };
glm::vec3 viewFrustumPos = BillboardModeHelpers::getPrimaryViewFrustumPosition();
for (auto &hit : physicsResults) {
auto avatarID = hit._intersectWithAvatar;
@ -842,7 +843,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
BoxFace subMeshFace = BoxFace::UNKNOWN_FACE;
glm::vec3 subMeshSurfaceNormal;
QVariantMap subMeshExtraInfo;
if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, subMeshDistance, subMeshFace, subMeshSurfaceNormal, subMeshExtraInfo, true, false)) {
if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, viewFrustumPos, subMeshDistance,
subMeshFace, subMeshSurfaceNormal, subMeshExtraInfo, true, false)) {
rayAvatarResult._distance = subMeshDistance;
rayAvatarResult._intersectionPoint = ray.origin + subMeshDistance * rayDirection;
rayAvatarResult._intersectionNormal = subMeshSurfaceNormal;
@ -932,6 +934,7 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector
std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator);
}
glm::vec3 viewFrustumPos = BillboardModeHelpers::getPrimaryViewFrustumPosition();
for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) {
const SortedAvatar& sortedAvatar = *it;
// We can exit once avatarCapsuleDistance > bestDistance
@ -944,7 +947,7 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel();
if (avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal, extraInfo, true)) {
if (avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, viewFrustumPos, parabolicDistance, face, surfaceNormal, extraInfo, true)) {
if (parabolicDistance < result.parabolicDistance) {
result.intersects = true;
result.avatarID = sortedAvatar.second->getID();

View file

@ -58,7 +58,9 @@ bool AvatarPackager::open() {
if (tablet->getToolbarMode()) {
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;
}

File diff suppressed because it is too large Load diff

View file

@ -283,15 +283,16 @@ class MyAvatar : public Avatar {
* the value.</p>
* @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 {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far).
* If <code>userRecenterModel == 2</code> (i.e., "auto") the property value automatically updates as the user sits
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
* sitting / standing state, which is updated when the user next sits or stands unless
* <code>isSitStandStateLocked == true</code>.
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* @property {boolean} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting;
* <code>false</code> if the user wearing the HMD is determined to be standing. This can affect whether the avatar
* is allowed to stand, lean or recenter its footing, depending on user preferences.
* 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.
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
* 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,
* <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 sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged);
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState);
Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel);
Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked);
Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); // Deprecated
Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); // Deprecated
Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
const QString DOMINANT_LEFT_HAND = "left";
@ -519,6 +520,7 @@ public:
/**jsdoc
* <p>Specifies different avatar leaning and recentering behaviors.</p>
* <p class="important">Deprecated: This type is deprecated and will be removed.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Name</th><th>Description</th></tr>
@ -549,6 +551,29 @@ public:
};
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);
virtual ~MyAvatar();
@ -1417,7 +1442,6 @@ public:
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
glm::quat getOffHandRotation() const;
bool hasDriveInput() const;
@ -1430,6 +1454,7 @@ public:
void removeWornAvatarEntity(const EntityItemID& entityID);
void clearWornAvatarEntities();
bool hasAvatarEntities() const;
/**jsdoc
* Checks whether your avatar is flying.
@ -1596,7 +1621,7 @@ public:
* @function MyAvatar.getAvatarScale
* @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
* Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
@ -1709,7 +1734,7 @@ public:
// derive avatar body position and orientation from the current HMD Sensor location.
// results are in sensor frame (-z forward)
glm::mat4 deriveBodyFromHMDSensor() const;
glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
glm::mat4 getSpine2RotationRigSpace() const;
@ -1753,10 +1778,14 @@ public:
bool getIsInWalkingState() const;
void setIsInSittingState(bool isSitting);
bool getIsInSittingState() const;
void setUserRecenterModel(MyAvatar::SitStandModelType modelName);
MyAvatar::SitStandModelType getUserRecenterModel() const;
void setIsSitStandStateLocked(bool isLocked);
bool getIsSitStandStateLocked() const;
void setUserRecenterModel(MyAvatar::SitStandModelType modelName); // Deprecated, will be removed.
MyAvatar::SitStandModelType getUserRecenterModel() const; // Deprecated, will be removed.
void setIsSitStandStateLocked(bool isLocked); // Deprecated, will be removed.
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);
float getWalkSpeed() const;
void setWalkBackwardSpeed(float value);
@ -1772,7 +1801,7 @@ public:
void setAnalogPlusSprintSpeed(float value);
float getAnalogPlusSprintSpeed() const;
void setSitStandStateChange(bool stateChanged);
float getSitStandStateChange() const;
bool getSitStandStateChange() const;
void updateSitStandState(float newHeightReading, float dt);
QVector<QString> getScriptUrls();
@ -1911,6 +1940,8 @@ public:
void avatarEntityDataToJson(QJsonObject& root) const override;
void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override;
/**jsdoc
* @comment Uses the base class's JSDoc.
*/
@ -1989,6 +2020,10 @@ public:
glm::vec3 getLookAtPivotPoint();
glm::vec3 getCameraEyesPosition(float deltaTime);
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:
@ -2245,12 +2280,6 @@ public slots:
*/
bool getEnableMeshVisible() const override;
/**jsdoc
* @function MyAvatar.storeAvatarEntityDataPayload
* @deprecated This function is deprecated and will be removed.
*/
void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override;
/**jsdoc
* @comment Uses the base class's JSDoc.
*/
@ -2624,6 +2653,7 @@ private slots:
protected:
void handleChangedAvatarEntityData();
void handleCanRezAvatarEntitiesChanged(bool canRezAvatarEntities);
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
virtual void recalculateChildCauterization() const override;
@ -2678,6 +2708,10 @@ private:
void attachmentDataToEntityProperties(const AttachmentData& data, EntityItemProperties& properties);
AttachmentData entityPropertiesToAttachmentData(const EntityItemProperties& properties) const;
bool findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID);
void addAvatarEntitiesToTree();
// FIXME: Rename to clearAvatarEntity() once the API call is removed.
void clearAvatarEntityInternal(const QUuid& entityID) override;
bool cameraInsideHead(const glm::vec3& cameraPosition) const;
@ -2709,6 +2743,16 @@ private:
bool _isBraking { 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 _yawSpeed; // degrees/sec
float _pitchSpeed; // degrees/sec
@ -2791,6 +2835,7 @@ private:
void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation);
void resetPointAt();
static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation);
void centerBodyInternal(const bool forceFollowYPos = false);
// Avatar Preferences
QUrl _fullAvatarURLFromPreferences;
@ -2841,26 +2886,21 @@ private:
struct FollowHelper {
FollowHelper();
enum FollowType {
Rotation = 0,
Horizontal,
Vertical,
NumFollowTypes
};
float _timeRemaining[NumFollowTypes];
CharacterController::FollowTimePerType _timeRemaining;
void deactivate();
void deactivate(FollowType type);
void activate();
void activate(FollowType type);
void deactivate(CharacterController::FollowType type);
void activate(CharacterController::FollowType type, const bool snapFollow);
bool isActive() const;
bool isActive(FollowType followType) const;
float getMaxTimeRemaining() const;
bool isActive(CharacterController::FollowType followType) const;
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 shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
bool shouldActivateHorizontal(const MyAvatar& myAvatar,
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);
glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const;
@ -2869,18 +2909,23 @@ private:
void setForceActivateVertical(bool val);
bool getForceActivateHorizontal() const;
void setForceActivateHorizontal(bool val);
bool getToggleHipsFollowing() const;
void setToggleHipsFollowing(bool followHead);
bool _squatDetected { false };
std::atomic<bool> _forceActivateRotation { false };
std::atomic<bool> _forceActivateVertical { false };
std::atomic<bool> _forceActivateHorizontal { false };
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;
bool isFollowActive(FollowHelper::FollowType followType) const;
bool isFollowActive(CharacterController::FollowType followType) const;
bool _goToPending { false };
bool _physicsSafetyPending { false };
@ -2922,6 +2967,9 @@ private:
bool _centerOfGravityModelEnabled { 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 };
AnimPose _prePhysicsRoomPose;
@ -2953,7 +3001,6 @@ private:
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
float _averageUserHeightSensorSpace { _userHeight.get() };
bool _sitStandStateChange { false };
ThreadSafeValueCache<bool> _lockSitStandState { false };
// max unscaled forward movement speed
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
@ -2969,9 +3016,13 @@ private:
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
bool _isInWalkingState { 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 _squatTimer { 0.0f };
float _tippingPoint { _userHeight.get() };
// load avatar scripts once when rig is ready
@ -3012,7 +3063,8 @@ private:
Setting::Handle<int> _controlSchemeIndexSetting;
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
Setting::Handle<QString> _userRecenterModelSetting;
Setting::Handle<QString> _allowAvatarStandingPreferenceSetting;
Setting::Handle<QString> _allowAvatarLeaningPreferenceSetting;
// AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute
@ -3057,6 +3109,8 @@ private:
glm::vec3 _cameraEyesOffset;
float _landingAfterJumpTime { 0.0f };
QTimer _addAvatarEntitiesToTreeTimer;
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

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

View file

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

View file

@ -65,13 +65,21 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
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;
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) {
if (useCenterOfGravityModel) {
// then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel();
} else {
// otherwise use the default of putting the hips under the head
hipsMat = myAvatar->deriveBodyFromHMDSensor();
hipsMat = myAvatar->deriveBodyFromHMDSensor(true);
}
glm::vec3 hipsPos = extractTranslation(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
// 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;
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
}

View file

@ -16,7 +16,7 @@ render::ItemID WorldBoxRenderData::_item{ render::Item::INVALID_ITEM_ID };
namespace render {
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1); }
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { return Item::Bound(); }
template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
PerformanceTimer perfTimer("worldBox");

View file

@ -32,7 +32,7 @@ public:
namespace render {
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff);
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff);
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args);
template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args);
}

View file

@ -415,7 +415,7 @@ gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabo
for (auto& key : keys) {
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)) {
PrepareStencil::testMask(*state);
} else {
@ -462,9 +462,9 @@ namespace render {
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
return payload->getKey();
}
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) {
if (payload) {
return payload->getBound();
return payload->getBound(args);
}
return Item::Bound();
}

View file

@ -31,7 +31,7 @@ public:
void render(RenderArgs* args);
render::Item::Bound& editBound() { return _bound; }
const render::Item::Bound& getBound() { return _bound; }
const render::Item::Bound& getBound(RenderArgs* args) { return _bound; }
render::ItemKey getKey() const { return _key; }
void setVisible(bool visible);
@ -128,7 +128,7 @@ private:
namespace render {
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args);
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
}

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. "
"Specifying a new folder name will automatically create that folder for you.";
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path",
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
completedCallback.call({ -1 });
return;
}
auto result = offscreenUI->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path",
dropEvent ? dropHelpText : helpText, mapping);
if (!result.isValid() || result.toString() == "") {
@ -94,7 +99,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping,
// Check for override
if (isKnownMapping(mapping)) {
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);
if (button == QMessageBox::No) {
completedCallback.call({ -1 });

View file

@ -25,6 +25,8 @@ QString Audio::DESKTOP { "Desktop" };
QString Audio::HMD { "VR" };
Setting::Handle<bool> enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
Setting::Handle<bool> enableNoiseReductionAutomaticSetting { QStringList { Audio::AUDIO, "NoiseReductionAutomatic" }, true };
Setting::Handle<float> setNoiseReductionThresholdSetting { QStringList { Audio::AUDIO, "NoiseReductionThreshold" }, 0.1f };
Setting::Handle<bool> enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true };
Setting::Handle<bool> enableAcousticEchoCancellationSetting { QStringList { Audio::AUDIO, "AcousticEchoCancellation" }, true };
@ -40,6 +42,8 @@ Audio::Audio() : _devices(_contextIsHMD) {
auto client = DependencyManager::get<AudioClient>().data();
connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
connect(client, &AudioClient::noiseReductionAutomaticChanged, this, &Audio::enableNoiseReductionAutomatic);
connect(client, &AudioClient::noiseReductionThresholdChanged, this, &Audio::setNoiseReductionThreshold);
connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted);
connect(client, &AudioClient::acousticEchoCancellationChanged, this, &Audio::enableAcousticEchoCancellation);
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
@ -47,6 +51,8 @@ Audio::Audio() : _devices(_contextIsHMD) {
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk);
enableNoiseReduction(enableNoiseReductionSetting.get());
enableNoiseReductionAutomatic(enableNoiseReductionAutomaticSetting.get());
setNoiseReductionThreshold(setNoiseReductionThresholdSetting.get());
enableWarnWhenMuted(enableWarnWhenMutedSetting.get());
enableAcousticEchoCancellation(enableAcousticEchoCancellationSetting.get());
onContextChanged();
@ -286,6 +292,50 @@ void Audio::enableNoiseReduction(bool enable) {
}
}
bool Audio::noiseReductionAutomatic() const {
return resultWithReadLock<bool>([&] {
return _noiseReductionAutomatic;
});
}
void Audio::enableNoiseReductionAutomatic(bool enable) {
bool changed = false;
withWriteLock([&] {
if (_noiseReductionAutomatic != enable) {
_noiseReductionAutomatic = enable;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setNoiseReductionAutomatic", Q_ARG(bool, enable), Q_ARG(bool, false));
enableNoiseReductionAutomaticSetting.set(enable);
changed = true;
}
});
if (changed) {
emit noiseReductionAutomaticChanged(enable);
}
}
float Audio::getNoiseReductionThreshold() {
return resultWithReadLock<float>([&] {
return _noiseReductionThreshold;
});
}
void Audio::setNoiseReductionThreshold(float threshold) {
bool changed = false;
withWriteLock([&] {
if (_noiseReductionThreshold != threshold) {
_noiseReductionThreshold = threshold;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setNoiseReductionThreshold", Q_ARG(float, threshold), Q_ARG(bool, false));
setNoiseReductionThresholdSetting.set(threshold);
changed = true;
}
});
if (changed) {
emit noiseReductionThresholdChanged(threshold);
}
}
bool Audio::warnWhenMutedEnabled() const {
return resultWithReadLock<bool>([&] {
return _enableWarnWhenMuted;

View file

@ -50,6 +50,11 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
* @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
* enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
* above the noise floor.
* @property {boolean} noiseReductionAutomatic - <code>true</code> if audio input noise reduction automatic mode is enabled,
* <code>false</code> if in manual mode. Manual mode allows you to use <code>Audio.noiseReductionThreshold</code>
* to set a manual sensitivity for the noise gate.
* @property {number} noiseReductionThreshold - Sets the noise gate threshold before your mic audio is transmitted.
* (Applies only if <code>Audio.noiseReductionAutomatic</code> is <code>false</code>.)
* @property {number} inputVolume - Adjusts the volume of the input audio, range <code>0.0</code> &ndash; <code>1.0</code>.
* If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
* devices, and others might only support values of <code>0.0</code> and <code>1.0</code>.
@ -92,6 +97,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
Q_PROPERTY(bool noiseReductionAutomatic READ noiseReductionAutomatic WRITE enableNoiseReductionAutomatic NOTIFY noiseReductionAutomaticChanged)
Q_PROPERTY(float noiseReductionThreshold READ getNoiseReductionThreshold WRITE setNoiseReductionThreshold NOTIFY noiseReductionThresholdChanged)
Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
Q_PROPERTY(bool acousticEchoCancellation
READ acousticEchoCancellationEnabled WRITE enableAcousticEchoCancellation NOTIFY acousticEchoCancellationChanged)
@ -124,6 +131,7 @@ public:
bool isMuted() const;
bool noiseReductionEnabled() const;
bool noiseReductionAutomatic() const;
bool warnWhenMutedEnabled() const;
bool acousticEchoCancellationEnabled() const;
float getInputVolume() const;
@ -270,6 +278,20 @@ public:
* @returns {number} The injector gain (dB) in the client.
*/
Q_INVOKABLE float getSystemInjectorGain();
/**jsdoc
* Sets the noise gate threshold before your mic audio is transmitted. (Applies only if <code>Audio.noiseReductionAutomatic</code> is <code>false</code>.)
* @function Audio.setNoiseReductionThreshold
* @param {number} threshold - The level that your input must surpass to be transmitted. <code>0.0</code> (open the gate completely) &ndash; <code>1.0</code>
*/
Q_INVOKABLE void setNoiseReductionThreshold(float threshold);
/**jsdoc
* Gets the noise reduction threshold.
* @function Audio.getNoiseReductionThreshold
* @returns {number} The noise reduction threshold. <code>0.0</code> &ndash; <code>1.0</code>
*/
Q_INVOKABLE float getNoiseReductionThreshold();
/**jsdoc
* Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
@ -398,6 +420,23 @@ signals:
* @returns {Signal}
*/
void noiseReductionChanged(bool isEnabled);
/**jsdoc
* Triggered when the audio input noise reduction mode is changed.
* @function Audio.noiseReductionAutomaticChanged
* @param {boolean} isEnabled - <code>true</code> if audio input noise reduction automatic mode is enabled, <code>false</code> if in manual mode.
* @returns {Signal}
*/
void noiseReductionAutomaticChanged(bool isEnabled);
/**jsdoc
* Triggered when the audio input noise reduction threshold is changed.
* @function Audio.noiseReductionThresholdChanged
* @param {number} threshold - The threshold for the audio input noise reduction, range <code>0.0</code> (open the gate completely) &ndash; <code>1.0</code>
* (close the gate completely).
* @returns {Signal}
*/
void noiseReductionThresholdChanged(float threshold);
/**jsdoc
* Triggered when "warn when muted" is enabled or disabled.
@ -512,6 +551,7 @@ public slots:
private slots:
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
void enableNoiseReductionAutomatic(bool enable);
void enableWarnWhenMuted(bool enable);
void enableAcousticEchoCancellation(bool enable);
void setInputVolume(float volume);
@ -533,8 +573,10 @@ private:
float _localInjectorGain { 0.0f }; // in dB
float _systemInjectorGain { 0.0f }; // in dB
float _pttOutputGainDesktop { 0.0f }; // in dB
float _noiseReductionThreshold { 0.1f }; // Match default value of AudioClient::_noiseReductionThreshold.
bool _isClipping { false };
bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled.
bool _noiseReductionAutomatic { true }; // Match default value of AudioClient::_noiseReductionAutomatic.
bool _enableWarnWhenMuted { true };
bool _enableAcousticEchoCancellation { true }; // AudioClient::_isAECEnabled
bool _contextIsHMD { false };

View file

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

View file

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

View file

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

View file

@ -199,9 +199,9 @@ void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitia
DependencyManager::get<NodeList>()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode);
}
bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->isPointOnDesktopWindow(point);
bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
auto offscreenUI = DependencyManager::get<OffscreenUi>();
return offscreenUI ? offscreenUI->isPointOnDesktopWindow(point) : false;
}
/// 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
*/
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
static_cast<QFlags<QMessageBox::StandardButton>>(buttons), static_cast<QMessageBox::StandardButton>(defaultButton));
connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int)));
if (auto offscreenUI = DependencyManager::get<OffscreenUi>()) {
auto messageBox = offscreenUI->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
static_cast<QFlags<QMessageBox::StandardButton>>(buttons), static_cast<QMessageBox::StandardButton>(defaultButton));
connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int)));
_lastMessageBoxID += 1;
_messageBoxes.insert(_lastMessageBoxID, messageBox);
_lastMessageBoxID += 1;
_messageBoxes.insert(_lastMessageBoxID, messageBox);
}
return _lastMessageBoxID;
}
@ -646,13 +648,17 @@ void WindowScriptingInterface::setActiveDisplayPlugin(int index) {
}
void WindowScriptingInterface::openWebBrowser(const QString& url) {
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "openWebBrowser", Q_ARG(const QString&, url));
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()) {
newObject->setProperty("url", url);
}

View file

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

View file

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

View file

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

View file

@ -250,9 +250,9 @@ void setupPreferences() {
{
auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); };
auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); };
preferences->addPreference(new CheckPreference("Privacy", "Send data - High Fidelity uses information provided by your "
preferences->addPreference(new CheckPreference("Privacy", "Send data - Vircadia uses information provided by your "
"client to improve the product through the logging of errors, tracking of usage patterns, "
"installation and system details. By allowing High Fidelity to collect this information "
"installation and system details. By allowing Vircadia to collect this information "
"you are helping to improve the product. ", getter, setter));
}
@ -364,7 +364,7 @@ void setupPreferences() {
auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Camera Sensitivity", getter, setter);
preference->setMin(0.01f);
preference->setMax(5.0f);
preference->setStep(0.1);
preference->setStep(0.1f);
preference->setDecimals(2);
preferences->addPreference(preference);
}
@ -422,40 +422,40 @@ void setupPreferences() {
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->int {
switch (myAvatar->getUserRecenterModel()) {
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;
}
IntPreference::Getter getter = [myAvatar]() -> int {
return static_cast<int>(myAvatar->getAllowAvatarStandingPreference());
};
auto setter = [myAvatar](int value) {
switch (value) {
case 0:
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;
}
IntPreference::Setter setter = [myAvatar](const int& value) {
myAvatar->setAllowAvatarStandingPreference(static_cast<MyAvatar::AllowAvatarStandingPreference>(value));
};
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;
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]";
preference->setHeading("Avatar leaning behavior");
items << "When I'm standing"
<< "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);
preferences->addPreference(preference);
}

View file

@ -248,9 +248,9 @@ private: \
* <em>Read-only.</em>
* @property {string} lodStatus - Description of the current LOD.
* <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>
* @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>
* @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
@ -547,8 +547,8 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, lodAngle, 0)
STATS_PROPERTY(int, lodTargetFramerate, 0)
STATS_PROPERTY(QString, lodStatus, QString())
STATS_PROPERTY(int, numEntityUpdates, 0)
STATS_PROPERTY(int, numNeededEntityUpdates, 0)
STATS_PROPERTY(quint64, numEntityUpdates, 0)
STATS_PROPERTY(quint64, numNeededEntityUpdates, 0)
STATS_PROPERTY(QString, timingStats, QString())
STATS_PROPERTY(QString, gameUpdateStats, QString())
STATS_PROPERTY(int, serverElements, 0)

View file

@ -66,7 +66,7 @@ private:
namespace render {
template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay);
template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay);
template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay, RenderArgs* args);
template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay);
template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems);

View file

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

View file

@ -14,7 +14,7 @@ namespace render {
template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) {
return overlay->getKey();
}
template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay) {
template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay, RenderArgs* args) {
return overlay->getBounds();
}
template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) {

View file

@ -24,13 +24,17 @@ QmlOverlay::QmlOverlay(const QUrl& url, const QmlOverlay* overlay)
}
void QmlOverlay::buildQmlElement(const QUrl& url) {
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "buildQmlElement", Q_ARG(QUrl, url));
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);
connect(_qmlElement, &QObject::destroyed, this, &QmlOverlay::qmlElementDestroyed);
});

View file

@ -82,9 +82,9 @@ namespace render {
template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload) {
return payload->getKey();
}
template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload) {
template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args) {
if (payload) {
return payload->getBound();
return payload->getBound(args);
}
return Item::Bound();
}

View file

@ -57,7 +57,7 @@ public:
void render(RenderArgs* args);
render::Item::Bound& editBound() { return _bound; }
const render::Item::Bound& getBound() { return _bound; }
const render::Item::Bound& getBound(RenderArgs* args) { return _bound; }
void setVisible(bool visible);
void showProxies(bool show);
@ -96,7 +96,7 @@ protected:
namespace render {
template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload);
template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload);
template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args);
template <> void payloadRender(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const GameWorkloadRenderItem::Pointer& payload);
}

View file

@ -22,6 +22,7 @@
#include "AnimationLogging.h"
#include "CubicHermiteSpline.h"
#include "AnimUtil.h"
#include "AnimSkeleton.h"
static const int MAX_TARGET_MARKERS = 30;
static const float JOINT_CHAIN_INTERP_TIME = 0.5f;
@ -66,7 +67,7 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, cons
poleVectorVar(poleVectorVarIn),
weight(weightIn),
numFlexCoefficients(flexCoefficientsIn.size()),
jointIndex(-1)
jointIndex(AnimSkeleton::INVALID_JOINT_INDEX)
{
numFlexCoefficients = std::min(numFlexCoefficients, (size_t)MAX_FLEX_COEFFICIENTS);
for (size_t i = 0; i < numFlexCoefficients; i++) {
@ -172,14 +173,14 @@ bool debounceJointWarnings() {
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses) {
_hipsTargetIndex = -1;
_hipsTargetIndex = AnimSkeleton::INVALID_JOINT_INDEX;
targets.reserve(_targetVarVec.size());
for (auto& targetVar : _targetVarVec) {
// update targetVar jointIndex cache
if (targetVar.jointIndex == -1) {
if (targetVar.jointIndex == AnimSkeleton::INVALID_JOINT_INDEX) {
int jointIndex = _skeleton->nameToJointIndex(targetVar.jointName);
if (jointIndex >= 0) {
// this targetVar has a valid joint --> cache the indices
@ -190,7 +191,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
}
IKTarget target;
if (targetVar.jointIndex != -1) {
if (targetVar.jointIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
target.setType(animVars.lookup(targetVar.typeVar, (int)IKTarget::Type::RotationAndPosition));
target.setIndex(targetVar.jointIndex);
if (target.getType() != IKTarget::Type::Unknown) {
@ -329,7 +330,7 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<
// update the absolutePoses
for (int i = 0; i < (int)_relativePoses.size(); ++i) {
auto parentIndex = _skeleton->getParentIndex((int)i);
if (parentIndex != -1) {
if (parentIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
}
}
@ -351,12 +352,12 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<
// finally set the relative rotation of each tip to agree with absolute target rotation
for (auto& target: targets) {
int tipIndex = target.getIndex();
int parentIndex = (tipIndex >= 0) ? _skeleton->getParentIndex(tipIndex) : -1;
int parentIndex = (tipIndex >= 0) ? _skeleton->getParentIndex(tipIndex) : AnimSkeleton::INVALID_JOINT_INDEX;
int chainIndex = targetToChainMap[tipIndex];
bool needsInterpolation = _prevJointChainInfoVec[chainIndex].timer > 0.0f;
float alpha = needsInterpolation ? getInterpolationAlpha(_prevJointChainInfoVec[chainIndex].timer) : 0.0f;
// update rotationOnly targets that don't lie on the ik chain of other ik targets.
if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() &&
if (parentIndex != AnimSkeleton::INVALID_JOINT_INDEX && !_rotationAccumulators[tipIndex].isDirty() &&
(target.getType() == IKTarget::Type::RotationOnly || target.getType() == IKTarget::Type::Unknown)) {
if (target.getType() == IKTarget::Type::RotationOnly) {
const glm::quat& targetRotation = target.getRotation();
@ -434,11 +435,11 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
int tipIndex = target.getIndex();
int pivotIndex = _skeleton->getParentIndex(tipIndex);
if (pivotIndex == -1 || pivotIndex == _hipsIndex) {
if (pivotIndex == AnimSkeleton::INVALID_JOINT_INDEX || pivotIndex == _hipsIndex) {
return;
}
int pivotsParentIndex = _skeleton->getParentIndex(pivotIndex);
if (pivotsParentIndex == -1) {
if (pivotsParentIndex == AnimSkeleton::INVALID_JOINT_INDEX) {
// TODO?: handle case where tip's parent is root?
return;
}
@ -485,7 +486,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
chainDepth++;
// descend toward root, pivoting each joint to get tip closer to target position
while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) {
while (pivotIndex != _hipsIndex && pivotsParentIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
assert(chainDepth < jointChainInfoOut.jointInfoVec.size());
@ -579,12 +580,12 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
if (target.getPoleVectorEnabled()) {
int topJointIndex = target.getIndex();
int midJointIndex = _skeleton->getParentIndex(topJointIndex);
if (midJointIndex != -1) {
if (midJointIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
int baseJointIndex = _skeleton->getParentIndex(midJointIndex);
if (baseJointIndex != -1) {
if (baseJointIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
int baseParentJointIndex = _skeleton->getParentIndex(baseJointIndex);
AnimPose topPose, midPose, basePose;
int topChainIndex = -1, baseChainIndex = -1;
int topChainIndex = AnimSkeleton::INVALID_JOINT_INDEX, baseChainIndex = AnimSkeleton::INVALID_JOINT_INDEX;
const size_t MAX_CHAIN_DEPTH = 30;
AnimPose postAbsPoses[MAX_CHAIN_DEPTH];
AnimPose accum = absolutePoses[_hipsIndex];
@ -756,7 +757,7 @@ void AnimInverseKinematics::computeAndCacheSplineJointInfosForIKTarget(const Ani
int index = target.getIndex();
int endIndex = _skeleton->getParentIndex(_hipsIndex);
while (index != endIndex) {
while (index != endIndex && index != AnimSkeleton::INVALID_JOINT_INDEX) {
AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength;
@ -1460,7 +1461,7 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
// invalidate all targetVars
for (auto& targetVar: _targetVarVec) {
targetVar.jointIndex = -1;
targetVar.jointIndex = AnimSkeleton::INVALID_JOINT_INDEX;
}
for (auto& accumulator: _rotationAccumulators) {
@ -1480,18 +1481,18 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
if (_hipsIndex >= 0) {
_hipsParentIndex = _skeleton->getParentIndex(_hipsIndex);
} else {
_hipsParentIndex = -1;
_hipsParentIndex = AnimSkeleton::INVALID_JOINT_INDEX;
}
_leftHandIndex = _skeleton->nameToJointIndex("LeftHand");
_rightHandIndex = _skeleton->nameToJointIndex("RightHand");
} else {
clearConstraints();
_headIndex = -1;
_hipsIndex = -1;
_hipsParentIndex = -1;
_leftHandIndex = -1;
_rightHandIndex = -1;
_headIndex = AnimSkeleton::INVALID_JOINT_INDEX;
_hipsIndex = AnimSkeleton::INVALID_JOINT_INDEX;
_hipsParentIndex = AnimSkeleton::INVALID_JOINT_INDEX;
_leftHandIndex = AnimSkeleton::INVALID_JOINT_INDEX;
_rightHandIndex = AnimSkeleton::INVALID_JOINT_INDEX;
}
}
@ -1531,7 +1532,7 @@ void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) c
// draw line to parent
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex != -1) {
if (parentIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
DebugDraw::getInstance().drawRay(pos, parentPos, GRAY);
}
@ -1580,7 +1581,7 @@ void AnimInverseKinematics::debugDrawIKChain(const JointChainInfo& jointChainInf
DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE);
// draw line to parent
if (parentIndex != -1) {
if (parentIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
glm::vec4 color = GRAY;
@ -1629,13 +1630,13 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con
// draw line to parent
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex != -1) {
if (parentIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
DebugDraw::getInstance().drawRay(pos, parentPos, GRAY);
}
glm::quat parentAbsRot;
if (parentIndex != -1) {
if (parentIndex != AnimSkeleton::INVALID_JOINT_INDEX) {
parentAbsRot = poses[parentIndex].rot();
}
@ -1733,7 +1734,7 @@ void AnimInverseKinematics::preconditionRelativePosesToAvoidLimbLock(const AnimC
const float MIN_AXIS_LENGTH = 1.0e-4f;
for (auto& target : targets) {
if (target.getIndex() != -1 && target.getType() == IKTarget::Type::RotationAndPosition) {
if (target.getIndex() != AnimSkeleton::INVALID_JOINT_INDEX && target.getType() == IKTarget::Type::RotationAndPosition) {
for (int i = 0; i < NUM_LIMBS; i++) {
if (limbs[i].first == target.getIndex()) {
int tipIndex = limbs[i].first;

View file

@ -66,7 +66,7 @@ int AnimSkeleton::nameToJointIndex(const QString& jointName) const {
if (_jointIndicesByName.end() != itr) {
return itr.value();
}
return -1;
return INVALID_JOINT_INDEX;
}
int AnimSkeleton::getNumJoints() const {
@ -80,7 +80,7 @@ int AnimSkeleton::getChainDepth(int jointIndex) const {
do {
chainDepth++;
index = _parentIndices[index];
} while (index != -1);
} while (index != INVALID_JOINT_INDEX);
return chainDepth;
} else {
return 0;
@ -108,7 +108,7 @@ const AnimPose& AnimSkeleton::getPostRotationPose(int jointIndex) const {
std::vector<int> AnimSkeleton::getChildrenOfJoint(int jointIndex) const {
// Children and grandchildren, etc.
std::vector<int> result;
if (jointIndex != -1) {
if (jointIndex != INVALID_JOINT_INDEX) {
for (int i = jointIndex + 1; i < (int)_parentIndices.size(); i++) {
if (_parentIndices[i] == jointIndex || (std::find(result.begin(), result.end(), _parentIndices[i]) != result.end())) {
result.push_back(i);
@ -135,7 +135,7 @@ void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const {
int lastIndex = std::min((int)poses.size(), _jointsSize);
for (int i = 0; i < lastIndex; ++i) {
int parentIndex = _parentIndices[i];
if (parentIndex != -1) {
if (parentIndex != INVALID_JOINT_INDEX) {
poses[i] = poses[parentIndex] * poses[i];
}
}
@ -146,7 +146,7 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const {
int lastIndex = std::min((int)poses.size(), _jointsSize);
for (int i = lastIndex - 1; i >= 0; --i) {
int parentIndex = _parentIndices[i];
if (parentIndex != -1) {
if (parentIndex != INVALID_JOINT_INDEX) {
poses[i] = poses[parentIndex].inverse() * poses[i];
}
}
@ -157,7 +157,7 @@ void AnimSkeleton::convertRelativeRotationsToAbsolute(std::vector<glm::quat>& ro
int lastIndex = std::min((int)rotations.size(), _jointsSize);
for (int i = 0; i < lastIndex; ++i) {
int parentIndex = _parentIndices[i];
if (parentIndex != -1) {
if (parentIndex != INVALID_JOINT_INDEX) {
rotations[i] = rotations[parentIndex] * rotations[i];
}
}
@ -168,7 +168,7 @@ void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector<glm::quat>& ro
int lastIndex = std::min((int)rotations.size(), _jointsSize);
for (int i = lastIndex - 1; i >= 0; --i) {
int parentIndex = _parentIndices[i];
if (parentIndex != -1) {
if (parentIndex != INVALID_JOINT_INDEX) {
rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i];
}
}
@ -280,7 +280,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints,
// so we can restore them after a future mirror operation
_nonMirroredIndices.push_back(i);
}
int mirrorJointIndex = -1;
int mirrorJointIndex = INVALID_JOINT_INDEX;
if (_joints[i].name.startsWith("Left")) {
QString mirrorJointName = QString(_joints[i].name).replace(0, 4, "Right");
mirrorJointIndex = nameToJointIndex(mirrorJointName);
@ -350,7 +350,7 @@ std::vector<int> AnimSkeleton::lookUpJointIndices(const std::vector<QString>& jo
result.reserve(jointNames.size());
for (auto& name : jointNames) {
int index = nameToJointIndex(name);
if (index == -1) {
if (index == INVALID_JOINT_INDEX) {
qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with name " << name;
}
result.push_back(index);

View file

@ -30,6 +30,8 @@ public:
const QString& getJointName(int jointIndex) const;
int getNumJoints() const;
int getChainDepth(int jointIndex) const;
static const int INVALID_JOINT_INDEX { -1 };
// the default poses are the orientations of the joints on frame 0.
const AnimPose& getRelativeDefaultPose(int jointIndex) const;

View file

@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin
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,
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule(
Extents totalExtents;
totalExtents.reset();
// HACK by convention our Avatars are always modeled such that y=0 is the ground plane.
// add the zero point so that our avatars will always have bounding volumes that are flush with the ground
// HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane.
// 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)
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
// 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 {
// 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();
// This factor can be used 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 = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
// Factor to scale distances in the geometry frame into the unscaled rig frame.
float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
int headTopJoint = indexOfJoint("HeadTop_End");
int headJoint = indexOfJoint("Head");
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
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.
auto skeleton = getAnimSkeleton();
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;
return scaleFactor * eyeHeight;
} else if (eyeJoint >= 0) {
// Measure Eye joint to y = 0 plane.
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
// Measure Eye joint to ground plane.
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * eyeHeight;
} else if (headTopJoint >= 0 && toeJoint >= 0) {
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const {
} else if (headTopJoint >= 0) {
// 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;
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * (headHeight - headHeight * ratio);
} else if (headJoint >= 0) {
// 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 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);
} else {
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) {
_animVars.set(targetName, blendingTarget);
_animVars.set(alphaName, alpha);

View file

@ -251,6 +251,7 @@ public:
Flow& getFlow() { return _internalFlow; }
float getUnscaledEyeHeight() const;
float getUnscaledHipsHeight() const;
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const;
int getOverrideJointCount() const;
@ -287,6 +288,11 @@ protected:
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
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 _geometryOffset; // geometry to model space (includes unit offset & fst offsets)

View file

@ -1266,7 +1266,7 @@ void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numC
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
if ((_muted && !_shouldEchoLocally) || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) {
if ((_muted && !_shouldEchoLocally) || !_audioOutput || (!_shouldEchoLocally && !hasReverb) || !_audioGateOpen) {
return;
}
@ -1342,6 +1342,13 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
}
}
float AudioClient::loudnessToLevel(float loudness) {
float level = loudness * (1 / 32768.0f); // level in [0, 1]
level = 6.02059991f * fastLog2f(level); // convert to dBFS
level = (level + 48.0f) * (1 / 42.0f); // map [-48, -6] dBFS to [0, 1]
return glm::clamp(level, 0.0f, 1.0f);
}
void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
if (!_audioPaused) {
@ -1352,9 +1359,14 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE;
int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO);
if (_isNoiseGateEnabled) {
if (_isNoiseGateEnabled && _isNoiseReductionAutomatic) {
// The audio gate includes DC removal
audioGateOpen = _audioGate->render(samples, samples, numFrames);
} else if (_isNoiseGateEnabled && !_isNoiseReductionAutomatic &&
loudnessToLevel(_lastSmoothedRawInputLoudness) >= _noiseReductionThreshold) {
audioGateOpen = _audioGate->removeDC(samples, samples, numFrames);
} else if (_isNoiseGateEnabled && !_isNoiseReductionAutomatic) {
audioGateOpen = false;
} else {
audioGateOpen = _audioGate->removeDC(samples, samples, numFrames);
}
@ -1750,6 +1762,24 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
}
}
void AudioClient::setNoiseReductionAutomatic(bool enable, bool emitSignal) {
if (_isNoiseReductionAutomatic != enable) {
_isNoiseReductionAutomatic = enable;
if (emitSignal) {
emit noiseReductionAutomaticChanged(_isNoiseReductionAutomatic);
}
}
}
void AudioClient::setNoiseReductionThreshold(float threshold, bool emitSignal) {
if (_noiseReductionThreshold != threshold) {
_noiseReductionThreshold = threshold;
if (emitSignal) {
emit noiseReductionThresholdChanged(_noiseReductionThreshold);
}
}
}
void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
if (_warnWhenMuted != enable) {
_warnWhenMuted = enable;

View file

@ -217,6 +217,12 @@ public slots:
void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
void setNoiseReductionAutomatic(bool isNoiseGateAutomatic, bool emitSignal = true);
bool isNoiseReductionAutomatic() const { return _isNoiseReductionAutomatic; }
void setNoiseReductionThreshold(float noiseReductionThreshold, bool emitSignal = true);
float noiseReductionThreshold() const { return _noiseReductionThreshold; }
void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
@ -265,6 +271,8 @@ signals:
void inputVolumeChanged(float volume);
void muteToggled(bool muted);
void noiseReductionChanged(bool noiseReductionEnabled);
void noiseReductionAutomaticChanged(bool noiseReductionAutomatic);
void noiseReductionThresholdChanged(bool noiseReductionThreshold);
void warnWhenMutedChanged(bool warnWhenMutedEnabled);
void acousticEchoCancellationChanged(bool acousticEchoCancellationEnabled);
void mutedByMixer();
@ -310,6 +318,8 @@ private:
friend class CheckDevicesThread;
friend class LocalInjectorsThread;
float loudnessToLevel(float loudness);
// background tasks
void checkDevices();
void checkPeakValues();
@ -397,6 +407,8 @@ private:
bool _shouldEchoLocally{ false };
bool _shouldEchoToServer{ false };
bool _isNoiseGateEnabled{ true };
bool _isNoiseReductionAutomatic{ true };
float _noiseReductionThreshold{ 0.1f };
bool _warnWhenMuted;
bool _isAECEnabled{ true };

View file

@ -91,8 +91,8 @@ static const float azimuthTable[NAZIMUTH][3] = {
// A first-order shelving filter is used to minimize the disturbance in ITD.
//
// Loosely based on data from S. Spagnol, "Distance rendering and perception of nearby virtual sound sources
// with a near-field filter model, Applied Acoustics (2017)
//
// with a near-field filter model," Applied Acoustics (2017)
//
static const int NNEARFIELD = 9;
static const float nearFieldTable[NNEARFIELD][3] = { // { b0, b1, a1 }
{ 0.008410604f, -0.000262748f, -0.991852144f }, // gain = 1/256
@ -388,7 +388,12 @@ void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames)
void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain);
static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) {
#ifndef STACK_PROTECTOR
// Enabling -fstack-protector on gcc causes an undefined reference to FIR_1x4_AVX512 here
static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE);
#else
static auto f = cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE;
#endif
(*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch
}

View file

@ -15,6 +15,7 @@
#include <SharedUtil.h>
#include <shared/QtHelpers.h>
#include <ThreadHelpers.h>
#include "AudioConstants.h"
#include "AudioInjector.h"
@ -54,11 +55,14 @@ AudioInjectorManager::~AudioInjectorManager() {
}
void AudioInjectorManager::createThread() {
_thread = new QThread;
_thread = new QThread();
_thread->setObjectName("Audio Injector Thread");
// when the thread is started, have it call our run to handle injection of audio
connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection);
connect(_thread, &QThread::started, this, [this] {
setThreadName("AudioInjectorManager");
run();
}, Qt::DirectConnection);
moveToThread(_thread);

View file

@ -63,7 +63,7 @@ namespace render {
}
return keyBuilder.build();
}
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar, RenderArgs* args) {
auto avatarPtr = static_pointer_cast<Avatar>(avatar);
if (avatarPtr) {
return avatarPtr->getRenderBounds();
@ -1444,9 +1444,7 @@ int Avatar::getJointIndex(const QString& name) const {
}
withValidJointIndicesCache([&]() {
if (_modelJointIndicesCache.contains(name)) {
result = _modelJointIndicesCache.value(name) - 1;
}
result = _modelJointIndicesCache.value(name, result + 1) - 1;
});
return result;
}

View file

@ -39,7 +39,7 @@
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar);
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar);
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar, RenderArgs* args);
template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args);
template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems);
}

View file

@ -156,17 +156,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
updateAttitude(_owningAvatar->getWorldOrientation());
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
Parent::simulate(deltaTime, fullUpdate);
if (fullUpdate) {
Parent::simulate(deltaTime, fullUpdate);
// let rig compute the model offset
glm::vec3 registrationPoint;
if (_rig.getModelRegistrationPoint(registrationPoint)) {
setOffset(registrationPoint);
}
} else {
Parent::simulate(deltaTime, fullUpdate);
}
// FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem,

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