Merge remote-tracking branch 'upstream/master' into update

This commit is contained in:
HifiExperiments 2020-11-09 19:33:38 -08:00
commit 7bfbf3c99c
161 changed files with 2256 additions and 900 deletions

View file

@ -3,114 +3,60 @@ name: Master CI Build
on:
push:
branches:
- master
- gha-master-ci
# FIXME: Change target branch to "master" before merging into "master" branch.
env:
#APP_NAME: gpu-frame-player
APP_NAME: interface
BUILD_TYPE: Release
BUCKET_NAME: hifi-gh-builds
BUILD_NUMBER: ${{ github.run_number }}
CI_BUILD: Github
CMAKE_BACKTRACE_URL: https://highfidelity.sp.backtrace.io:6098
CMAKE_BACKTRACE_TOKEN: ${{ secrets.backtrace_token }}
CMAKE_BACKTRACE_SYMBOLS_TOKEN: ${{ secrets.backtrace_symbols_token }}
GIT_COMMIT: ${{ github.sha }}
HIFI_VCPKG_BOOTSTRAP: true
LAUNCHER_HMAC_SECRET: ${{ secrets.launcher_hmac_secret }}
OCULUS_APP_ID: '${{ secrets.oculus_app_id }}'
# VCPKG did not build well on OSX disabling HIFI_VCPKG_BOOTSTRAP, which invokes a download to a working version of vcpkg
# HIFI_VCPKG_BOOTSTRAP: true
RELEASE_TYPE: PRODUCTION
RELEASE_DYNAMODB_V2: ReleaseManager2-ReleaseQueue-prod
RELEASE_NUMBER: ${{ github.run_number }}
STABLE_BUILD: 0
UPLOAD_BUCKET: athena-public
# OSX specific variables
# OSX-specific variables
DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer
MACOSX_DEPLOYMENT_TARGET: '10.11'
# WIN32 specific variables
# WIN-specific variables
PreferredToolArchitecture: X64
# Mac OS
#PLATFORM_CMAKE_GENERATOR=Xcode
#PLATFORM_BUILD_ARGUMENTS=--config Release --target package
#ARTIFACT_EXPRESSION=build/*.dmg,build/*.zip
# Windows
#PLATFORM_CMAKE_GENERATOR=Visual Studio 15 2017 Win64
#PLATFORM_BUILD_ARGUMENTS=--target package --config release
#ARTIFACT_EXPRESSION=build/*.exe,build/*.zip,*-symbols.zip
# Ubuntu
#PLATFORM_CMAKE_GENERATOR=Unix Makefiles
#PLATFORM_BUILD_ARGUMENTS=--target all -- -j4
#ARTIFACT_EXPRESSION=build/assignment-client/**,build/domain-server/**,build/ice-server/ice-server,build/tools/ice-client/ice-client,build/tools/ac-client/ac-client,build/tools/oven,build/ext/makefiles/nvtt/project/lib/**,build/ext/makefiles/quazip/project/lib/**
# Android
# branch: master
# GA_TRACKING_ID: ${{ secrets.ga_tracking_id }}
# ANDROID_OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_NIGHTLY}
# ANDROID_OAUTH_CLIENT_ID=6c7d2349c0614640150db37457a1f75dce98a28ffe8f14d47f6cfae4de5b262a
# ANDROID_OAUTH_REDIRECT_URI=https://dev-android-interface.highfidelity.com/auth
# branch: !master
# GA_TRACKING_ID=UA-39558647-11
# ANDROID_OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_RELEASE}
# ANDROID_OAUTH_CLIENT_ID= c1063ea5d0b0c405e0c9cd77351328e211a91496a3f25985a99e861f1661db1d
# ANDROID_OAUTH_REDIRECT_URI=https://android-interface.highfidelity.com/auth
# ARTIFACT_EXPRESSION=android/*.apk
# ANDROID_APK_NAME=HighFidelity-Beta-PR${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk
# ANDROID_BUILT_APK_NAME=interface-debug.apk
# ANDROID_APP=interface
# ANDROID_BUILD_DIR=debug
# ANDROID_BUILD_TARGET=assembleDebug
# STABLE_BUILD=0
jobs:
generate_build_number:
runs-on: ubuntu-latest
steps:
- name: Generate build number
id: buildnumber
uses: highfidelity/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Upload build number
uses: actions/upload-artifact@v1
with:
name: BUILD_NUMBER
path: BUILD_NUMBER
build:
strategy:
matrix:
os: [windows-latest, macOS-latest]
build_type: [full, client]
#os: [windows-latest, macOS-latest, ubuntu-latest]
# exclude:
# - os: ubuntu-latest
# build_type: client
os: [windows-latest, macOS-latest, ubuntu-18.04]
# build_type: [full, client]
build_type: [full]
include:
- os: ubuntu-18.04
build_type: full
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
fail-fast: false
runs-on: ${{matrix.os}}
needs: generate_build_number
steps:
- name: Download build number
uses: actions/download-artifact@v1
with:
name: BUILD_NUMBER
- name: Restore build number
id: buildnumber
uses: highfidelity/build-number@v3
with:
output_name: RELEASE_NUMBER
- name: Configure Build Environment 1
- name: Report Build Number
shell: bash
run: |
echo "Build number: $BUILD_NUMBER"
- name: Configure build environment 1
shell: bash
id: buildenv1
run: |
echo ::set-env name=UPLOAD_PREFIX::master
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
# Linux build variables
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=INSTALLER_EXT::tgz
echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)"
fi
# Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
@ -118,8 +64,8 @@ jobs:
echo ::set-env name=ZIP_COMMAND::zip
echo ::set-env name=ZIP_ARGS::-r
echo ::set-env name=INSTALLER_EXT::dmg
echo ::set-env name=SYMBOL_REGEX::dSYM
echo "::set-output name=symbols_archive::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-mac-symbols.zip"
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-mac-symbols.zip"
fi
# Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then
@ -127,40 +73,28 @@ jobs:
echo ::set-env name=ZIP_COMMAND::7z
echo ::set-env name=ZIP_ARGS::a
echo ::set-env name=INSTALLER_EXT::exe
echo ::set-env name=CMAKE_EXTRA::"-A x64"
echo "::set-env name=SYMBOL_REGEX::\(exe\|dll\|pdb\)"
echo "::set-output name=symbols_archive::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-win-symbols.zip"
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-win-symbols.zip"
# echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}}
# echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx"
fi
# Configuration is broken into two steps because you can't set an env var and also reference it in the same step
- name: Configure Build Environment 2
- name: Configure build environment 2
shell: bash
run: |
echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo ::set-env name=ARTIFACT_PATTERN::HighFidelity-Beta-*.$INSTALLER_EXT
echo ::set-env name=ARTIFACT_PATTERN::Vircadia-Alpha-*.$INSTALLER_EXT
# Build type variables
if [ "${{ matrix.build_type }}" = "full" ]; then
echo ::set-env name=CLIENT_ONLY::FALSE
echo ::set-env name=INSTALLER::HighFidelity-Beta-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo ::set-env name=INSTALLER::Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
else
echo ::set-env name=CLIENT_ONLY::TRUE
echo ::set-env name=INSTALLER::HighFidelity-Beta-Interface-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo ::set-env name=INSTALLER::Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
fi
# Linux build variables
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=CMAKE_EXTRA::""
fi
# Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
fi
# Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then
echo ::set-env name=CMAKE_EXTRA::"-A x64"
echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}}
echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx"
fi
- name: Clear Working Directory
if: matrix.os == 'windows-latest'
- name: Clear working directory
if: startsWith(matrix.os, 'windows')
shell: bash
working-directory: ${{runner.workspace}}
run: rm -rf ./*
@ -168,89 +102,119 @@ jobs:
with:
submodules: true
fetch-depth: 1
- name: Create Build Directory
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Decrypt Signing Key (Windows)
if: matrix.os == 'windows-latest'
working-directory: ${{runner.workspace}}/build
- name: Install dependencies
if: startsWith(matrix.os, 'ubuntu')
shell: bash
run: gpg --batch --yes -o codesign.pfx --passphrase "${{secrets.gpg_symmetric_key}}" --decrypt $GITHUB_WORKSPACE/tools/ci-scripts/codesign.pfx.gpg
- name: Import Signing Key (Windows)
if: matrix.os == 'windows-latest'
working-directory: ${{runner.workspace}}/build
shell: powershell
run: |
$mypwd=ConvertTo-SecureString -String ${{ secrets.pfx_key }} -Force -AsPlainText
Import-PfxCertificate -Password $mypwd -CertStoreLocation Cert:\CurrentUser\My -FilePath ${{runner.workspace}}\build\codesign.pfx
Import-PfxCertificate -Password $mypwd -CertStoreLocation Cert:\LocalMachine\My -FilePath ${{runner.workspace}}\build\codesign.pfx
run: |
echo "Installing Python Modules:"
pip3 install distro || exit 1
echo "Updating apt repository index"
sudo apt update || exit 1
echo "Installing apt packages"
sudo apt install -y ${{ matrix.apt-dependencies }} || exit 1
- name: Install Python modules
if: matrix.os != 'ubuntu-latest'
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
shell: bash
run: $PYTHON_EXEC -m pip install boto3 PyGithub
- name: Create build environment
shell: bash
run: cmake -E make_directory "${{runner.workspace}}/build"
- name: Configure CMake
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY $CMAKE_EXTRA
- name: Build Application
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY -DBYPASS_SIGNING:BOOLEAN=TRUE $CMAKE_EXTRA
- name: Build application
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME
- name: Build Console
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
- name: Build domain server
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console
- name: Build Domain Server (FullBuild)
if: matrix.build_type == 'full'
shell: bash
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config $BUILD_TYPE --target domain-server
- name: Build Assignment Client (FullBuild)
if: matrix.build_type == 'full'
shell: bash
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config $BUILD_TYPE --target assignment-client
- name: Build Installer
run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
- name: Build assignment client
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target package
- name: Sign Installer (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
working-directory: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64
run: .\signtool.exe sign /fd sha256 /f ${{runner.workspace}}\build\codesign.pfx /p ${{secrets.pfx_key}} /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 ${{runner.workspace}}\build\${env:INSTALLER}
- name: Upload Artifact
if: matrix.os != 'ubuntu-latest'
run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
- name: Build console
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
- name: Build installer
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
echo "Retry code from https://unix.stackexchange.com/a/137639"
function fail {
echo $1 >&2
exit 1
}
function retry {
local n=1
local max=5
local delay=15
while true; do
"$@" && break || {
if [[ $n -lt $max ]]; then
((n++))
echo "Command failed. Attempt $n/$max:"
sleep $delay;
else
fail "The command has failed after $n attempts."
fi
}
done
}
retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
#- name: Sign installer (Windows)
# if: startsWith(matrix.os, 'windows')
# shell: powershell
# working-directory: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64
# run: .\signtool.exe sign /fd sha256 /f ${{runner.workspace}}\build\codesign.pfx /p ${{secrets.pfx_key}} /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 ${{runner.workspace}}\build\${env:INSTALLER}
- name: Output system stats
if: ${{ always() }}
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
echo "Disk usage:"
df -h
- name: Output installer logs
if: failure() && startsWith(matrix.os, 'windows')
shell: bash
working-directory: ${{runner.workspace}}/build
run: cat ./_CPack_Packages/win64/NSIS/NSISOutput.log
- name: Upload artifact
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
shell: bash
working-directory: ${{runner.workspace}}/build
env:
AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.aws_secret_access_key }}
run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py
- name: Archive Symbols
if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
working-directory: ${{runner.workspace}}
shell: bash
run: |
SYMBOLS_TEMP="symbols-temp"
mkdir $SYMBOLS_TEMP
find "./build" \( -path '*/tools/gpu-frame-player/*' -or -path '*/interface/*' -or -path '*/plugins/*' \) -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \;
cd $SYMBOLS_TEMP
$ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} .
- name: Upload Symbols
if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
working-directory: ${{runner.workspace}}
shell: bash
run: |
curl --data-binary @${{ steps.buildenv1.outputs.symbols_archive }} "$CMAKE_BACKTRACE_URL/post?format=symbols&token=$CMAKE_BACKTRACE_SYMBOLS_TOKEN&upload_file=${{steps.buildenv1.outputs.symbols_archive}}&tag=$RELEASE_NUMBER"
# - name: Debug List Symbols
# if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}"
# - name: Debug Upload Symbols Artifact
# if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
# uses: actions/upload-artifact@v1
# with:
# name: symbols
# path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}
#- name: Archive symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# SYMBOLS_TEMP="symbols-temp"
# mkdir $SYMBOLS_TEMP
# find "./build" \( -path '*/tools/gpu-frame-player/*' -or -path '*/interface/*' -or -path '*/plugins/*' \) -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \;
# cd $SYMBOLS_TEMP
# $ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} .
#- name: Upload symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# curl --data-binary @${{ steps.buildenv1.outputs.symbols_archive }} "$CMAKE_BACKTRACE_URL/post?format=symbols&token=$CMAKE_BACKTRACE_SYMBOLS_TOKEN&upload_file=${{steps.buildenv1.outputs.symbols_archive}}&tag=$BUILD_NUMBER"
#- name: Debug list symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}"
#- name: Upload debug list symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# uses: actions/upload-artifact@v1
# with:
# name: symbols
# path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}

View file

@ -112,11 +112,12 @@ Agent::Agent(ReceivedMessage& message) :
packetReceiver.registerListenerForTypes(
{ PacketType::MixedAudio, PacketType::SilentAudioFrame },
this, "handleAudioPacket");
PacketReceiver::makeUnsourcedListenerReference<Agent>(this, &Agent::handleAudioPacket));
packetReceiver.registerListenerForTypes(
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
PacketReceiver::makeSourcedListenerReference<Agent>(this, &Agent::handleOctreePacket));
packetReceiver.registerListener(PacketType::SelectedAudioFormat,
PacketReceiver::makeUnsourcedListenerReference<Agent>(this, &Agent::handleSelectedAudioFormat));
// 100Hz timer for audio
const int TARGET_INTERVAL_MSEC = 10; // 10ms

View file

@ -118,8 +118,10 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
setUpStatusToMonitor();
}
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::CreateAssignment, this, "handleCreateAssignmentPacket");
packetReceiver.registerListener(PacketType::StopNode, this, "handleStopNodePacket");
packetReceiver.registerListener(PacketType::CreateAssignment,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleCreateAssignmentPacket));
packetReceiver.registerListener(PacketType::StopNode,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleStopNodePacket));
}
void AssignmentClient::stopAssignmentClient() {

View file

@ -72,7 +72,8 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
auto nodeList = DependencyManager::set<LimitedNodeList>(listenPort);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssignmentClientStatus, this, "handleChildStatusPacket");
packetReceiver.registerListener(PacketType::AssignmentClientStatus,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClientMonitor>(this, &AssignmentClientMonitor::handleChildStatusPacket));
adjustOSResources(std::max(_numAssignmentClientForks, _maxAssignmentClientForks));
// use QProcess to fork off a process for each of the child assignment clients

View file

@ -308,7 +308,8 @@ AssetServer::AssetServer(ReceivedMessage& message) :
// Queue all requests until the Asset Server is fully setup
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation }, this, "queueRequests");
packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation },
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::queueRequests));
#ifdef Q_OS_WIN
updateConsumedCores();
@ -464,10 +465,14 @@ void AssetServer::completeSetup() {
qCDebug(asset_server) << "Overriding temporary queuing packet handler.";
// We're fully setup, override the request queueing handler and replay all requests
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet");
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
packetReceiver.registerListener(PacketType::AssetGet,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetGet));
packetReceiver.registerListener(PacketType::AssetGetInfo,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetGetInfo));
packetReceiver.registerListener(PacketType::AssetUpload,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetUpload));
packetReceiver.registerListener(PacketType::AssetMappingOperation,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetMappingOperation));
replayRequests();
}

View file

@ -101,20 +101,23 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
PacketType::InjectorGainSet,
PacketType::AudioSoloRequest,
PacketType::StopInjector },
this, "queueAudioPacket");
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::queueAudioPacket)
);
// packets whose consequences are global should be processed on the main thread
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::MuteEnvironment,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleMuteEnvironmentPacket));
packetReceiver.registerListener(PacketType::NodeMuteRequest,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleNodeMuteRequestPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleKillAvatarPacket));
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedMicrophoneAudioNoEcho,
PacketType::ReplicatedMicrophoneAudioWithEcho,
PacketType::ReplicatedInjectAudio,
PacketType::ReplicatedSilentAudioFrame
},
this, "queueReplicatedAudioPacket"
PacketType::ReplicatedSilentAudioFrame },
PacketReceiver::makeUnsourcedListenerReference<AudioMixer>(this, &AudioMixer::queueReplicatedAudioPacket)
);
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);

View file

@ -71,26 +71,38 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::AdjustAvatarSorting, this, "handleAdjustAvatarSorting");
packetReceiver.registerListener(PacketType::AvatarQuery, this, "handleAvatarQueryPacket");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::AvatarData,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListener(PacketType::AdjustAvatarSorting,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAdjustAvatarSorting));
packetReceiver.registerListener(PacketType::AvatarQuery,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAvatarQueryPacket));
packetReceiver.registerListener(PacketType::AvatarIdentity,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAvatarIdentityPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleKillAvatarPacket));
packetReceiver.registerListener(PacketType::NodeIgnoreRequest,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleNodeIgnoreRequestPacket));
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleRadiusIgnoreRequestPacket));
packetReceiver.registerListener(PacketType::RequestsDomainListData,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleRequestsDomainListDataPacket));
packetReceiver.registerListener(PacketType::SetAvatarTraits,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket");
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleOctreePacket));
packetReceiver.registerListener(PacketType::ChallengeOwnership,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity,
PacketType::ReplicatedKillAvatar
}, this, "handleReplicatedPacket");
}, PacketReceiver::makeUnsourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleReplicatedPacket));
packetReceiver.registerListener(PacketType::ReplicatedBulkAvatarData, this, "handleReplicatedBulkAvatarPacket");
packetReceiver.registerListener(PacketType::ReplicatedBulkAvatarData,
PacketReceiver::makeUnsourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleReplicatedBulkAvatarPacket));
auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);

View file

@ -59,8 +59,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
PacketType::ChallengeOwnership,
PacketType::ChallengeOwnershipRequest,
PacketType::ChallengeOwnershipReply },
this,
"handleEntityPacket");
PacketReceiver::makeSourcedListenerReference<EntityServer>(this, &EntityServer::handleEntityPacket));
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
_dynamicDomainVerificationTimer.setSingleShot(true);

View file

@ -25,9 +25,12 @@ MessagesMixer::MessagesMixer(ReceivedMessage& message) : ThreadedAssignment(mess
{
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessages");
packetReceiver.registerListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe");
packetReceiver.registerListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe");
packetReceiver.registerListener(PacketType::MessagesData,
PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessages));
packetReceiver.registerListener(PacketType::MessagesSubscribe,
PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessagesSubscribe));
packetReceiver.registerListener(PacketType::MessagesUnsubscribe,
PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessagesUnsubscribe));
}
void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {

View file

@ -1122,8 +1122,10 @@ void OctreeServer::run() {
void OctreeServer::domainSettingsRequestComplete() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
packetReceiver.registerListener(PacketType::OctreeDataNack,
PacketReceiver::makeSourcedListenerReference<OctreeServer>(this, &OctreeServer::handleOctreeDataNackPacket));
packetReceiver.registerListener(getMyQueryMessageType(),
PacketReceiver::makeSourcedListenerReference<OctreeServer>(this, &OctreeServer::handleOctreeQueryPacket));
qDebug(octree_server) << "Received domain settings";

View file

@ -83,13 +83,18 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleOctreePacket));
packetReceiver.registerListener(PacketType::SelectedAudioFormat,
PacketReceiver::makeUnsourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleSelectedAudioFormat));
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
packetReceiver.registerListener(PacketType::ReloadEntityServerScript,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleReloadEntityServerScriptPacket));
packetReceiver.registerListener(PacketType::EntityScriptGetStatus,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptGetStatusPacket));
packetReceiver.registerListener(PacketType::EntityServerScriptLog,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityServerScriptLogPacket));
packetReceiver.registerListener(PacketType::EntityScriptCallMethod,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptCallMethodPacket));
static const int LOG_INTERVAL = MSECS_PER_SECOND / 10;
auto timer = new QTimer(this);

View file

@ -202,9 +202,9 @@
; The Inner invocation has written an uninstaller binary for us.
; We need to sign it if it's a production or PR build.
!if @PRODUCTION_BUILD@ == 1
!if @BYPASS_SIGNING@ == 1
!if @BYPASS_SIGNING@ == TRUE
!warning "BYPASS_SIGNING set - installer will not be signed"
!else
!else
!system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://timestamp.comodoca.com?td=sha256 /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0
!endif
!endif

2
debian/rules vendored
View file

@ -6,7 +6,7 @@
override_dh_auto_configure:
mkdir obj-$(DEB_TARGET_MULTIARCH)
(cd obj-$(DEB_TARGET_MULTIARCH) && cmake .. -DCMAKE_INSTALL_PREFIX=/opt/hifi -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON "-GUnix Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCLIENT_ONLY=ON -DDOWNLOAD_SERVERLESS_CONTENT=ON -DCMAKE_CXX_COMPILER=/usr/lib/llvm-7/bin/clang\+\+ -DOpenGL_GL_PREFERENCE=GLVND)
(cd obj-$(DEB_TARGET_MULTIARCH) && cmake .. -DCMAKE_INSTALL_PREFIX=/opt/hifi -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON "-GUnix Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCLIENT_ONLY=ON -DDOWNLOAD_SERVERLESS_CONTENT=ON -DCMAKE_CXX_COMPILER=/usr/lib/llvm-7/bin/clang\+\+ -DOpenGL_GL_PREFERENCE=LEGACY)
override_dh_auto_build:
(cd obj-$(DEB_TARGET_MULTIARCH) && make -j4)

View file

@ -500,6 +500,31 @@ function prepareAccessTokenPrompt(callback) {
});
}
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>',
showCancelButton: true,
confirmButtonText: "Create",
closeOnConfirm: false,
html: true
}, function (inputValue) {
if (inputValue === false) {
return false;
}
if (inputValue === "") {
swal.showInputError("Please enter a valid label for your machine.");
return false;
}
if (callback) {
callback(inputValue);
}
});
}
function getMetaverseUrl(callback) {
$.ajax('/api/metaverse_info', {
success: function(data) {

View file

@ -40,6 +40,8 @@ $(document).ready(function(){
// call our method to setup the place names table
setupPlacesTable();
// hide the places table for now because we do not want that interacted with from the domain-server
$('#' + Settings.PLACES_TABLE_ID).hide();
setupDomainNetworkingSettings();
// setupDomainLabelSetting();
@ -363,7 +365,7 @@ $(document).ready(function(){
confirmButtonText: "Create",
closeOnConfirm: false,
html: true
}, function(inputValue){
}, function (inputValue) {
if (inputValue === false) {
swal.close();
@ -373,7 +375,7 @@ $(document).ready(function(){
}
} else {
// we're going to change the alert to a new one with a spinner while we create this domain
showSpinnerAlert('Creating domain ID');
// showSpinnerAlert('Creating domain ID');
createNewDomainID(inputValue, justConnected);
}
});
@ -385,7 +387,12 @@ $(document).ready(function(){
"label": label
}
$.post("/api/domains", domainJSON, function(data){
$.post("/api/domains", domainJSON, function(data) {
if (data.status === "failure") {
failedToCreateDomainID(data, justConnected);
return;
}
// we successfully created a domain ID, set it on that field
var domainID = data.domain.domainId;
console.log("Setting domain id to ", data, domainID);
@ -406,40 +413,50 @@ $(document).ready(function(){
text: successText,
html: true,
confirmButtonText: 'Save'
}, function(){
}, function () {
saveSettings();
});
}, 'json').fail(function(){
}, 'json').fail(function (data) {
failedToCreateDomainID(data, justConnected);
});
}
function failedToCreateDomainID(data, justConnected) {
var errorText = "There was a problem creating your new domain ID. Do you want to try again or";
var errorText = "There was a problem creating your new domain ID. Do you want to try again or";
if (data && data.status === "failure") {
errorText = "Error: " + data.error + "</br>Do you want to try again or";
console.log("Error: " + data.error);
} else {
console.log("Error: Failed to post to metaverse.");
}
if (justConnected) {
errorText += " just save your new access token?</br></br>You can always create a new domain ID later.";
if (justConnected) {
errorText += " just save your new access token?</br></br>You can always create a new domain ID later.";
} else {
errorText += " cancel?"
}
// we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel
swal({
title: '',
type: 'error',
text: errorText,
html: true,
confirmButtonText: 'Try again',
showCancelButton: true,
closeOnConfirm: false
}, function (isConfirm) {
if (isConfirm) {
// they want to try creating a domain ID again
showDomainCreationAlert(justConnected);
} else {
errorText += " cancel?"
}
// we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel
swal({
title: '',
type: 'error',
text: errorText,
html: true,
confirmButtonText: 'Try again',
showCancelButton: true,
closeOnConfirm: false
}, function(isConfirm){
if (isConfirm) {
// they want to try creating a domain ID again
showDomainCreationAlert(justConnected);
} else {
// they want to cancel
if (justConnected) {
// since they just connected we need to save the access token here
saveSettings();
}
// they want to cancel
if (justConnected) {
// since they just connected we need to save the access token here
saveSettings();
}
});
}
});
}
@ -711,8 +728,8 @@ $(document).ready(function(){
name: 'places',
label: 'Places',
html_id: Settings.PLACES_TABLE_ID,
help: "The following places currently point to this domain.</br>To point places to this domain, "
+ " go to the <a href='" + METAVERSE_URL + "/user/places'>My Places</a> "
help: "To point places to this domain, "
+ " go to the <a href='" + METAVERSE_URL + "/user/places'>Places</a> "
+ "page in your Metaverse account.",
read_only: true,
can_add_new_rows: false,
@ -745,9 +762,10 @@ $(document).ready(function(){
var errorEl = createDomainLoadingError("There was an error retrieving your places.");
$("#" + Settings.PLACES_TABLE_ID).after(errorEl);
var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
temporaryPlaceButton.hide();
$('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
// DISABLE TEMP PLACE NAME BUTTON...
// var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
// temporaryPlaceButton.hide();
// $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
if (accessTokenIsSet()) {
appendAddButtonToPlacesTable();
}

View file

@ -19,7 +19,7 @@
</dl>
</div>
<div class="wizard-step col-md-8 col-centered" style="display: none;">
<!-- <div class="wizard-step col-md-8 col-centered" style="display: none;">
<h4 class="step-title"></h4>
<div class="row">
<div class="col-md-12">
@ -51,7 +51,7 @@
<button type="button" class="btn btn-md btn-block btn-primary next-button">Next</button>
</dd>
</dl>
</div>
</div> -->
<div class="wizard-step col-md-9 col-centered" style="display: none;">
<h4 class="step-title"></h4>
@ -204,7 +204,7 @@
</div>
<br />
<div id="automatic-threading-options-row" class="row">
<p class="col-md-2">
<p class="col-md-12">
<label>
<input id="enable-automatic-threading" name="threading-checkbox" type="checkbox" value="true"> Enable Automatic Threading
</label>

View file

@ -50,6 +50,7 @@ $(document).ready(function(){
prepareAccessTokenPrompt(function(accessToken) {
Metaverse.accessToken = accessToken;
saveAccessToken();
promptToCreateDomainID();
});
});
@ -171,6 +172,45 @@ function setupWizardSteps() {
$(currentStep).show();
}
function promptToCreateDomainID() {
setTimeout(function () {
createDomainIDPrompt(function (label) {
var domainJSON = {
"label": label
};
$.post("/api/domains", domainJSON, function (data) {
if (data.status === "failure") {
swal.showInputError("Error: " + data.error);
return;
}
swal.close();
// we successfully created a domain ID, set it on that field
var domainID = data.domain.domainId;
console.log("Setting domain ID to ", data, domainID);
var formJSON = {
"metaverse": {
"id": domainID
}
};
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON, goToNextStep);
}, 'json').fail(function (data) {
if (data && data.status === "failure") {
swal.showInputError("Error: " + data.error);
} else {
swal.showInputError("Error: Failed to post to metaverse.");
}
console.log("Failed to create domain ID...");
});
});
}, 500); // Apparently swal needs time before opening another prompt.
}
function updatePlaceNameLink(address) {
if (address) {
var url = URLs.PLACE_URL + '/' + address;
@ -341,7 +381,7 @@ function saveAccessToken() {
$(this).blur();
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON, goToNextStep);
postSettings(formJSON);
}
function getSettingDescriptionForKey(groupKey, settingKey) {

View file

@ -17,7 +17,8 @@
#include <openssl/x509.h>
#include <random>
#include <QDataStream>
#include <QtCore/QDataStream>
#include <QtCore/QMetaMethod>
#include <AccountManager.h>
#include <Assignment.h>

View file

@ -340,7 +340,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
void DomainServer::parseCommandLine(int argc, char* argv[]) {
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Domain Server");
parser.setApplicationDescription("Vircadia Domain Server");
const QCommandLineOption versionOption = parser.addVersionOption();
const QCommandLineOption helpOption = parser.addHelpOption();
@ -778,32 +778,51 @@ void DomainServer::setupNodeListAndAssignments() {
// register as the packet receiver for the types we want
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket");
packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket");
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket");
packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacket");
packetReceiver.registerListener(PacketType::RequestAssignment,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processRequestAssignmentPacket));
packetReceiver.registerListener(PacketType::DomainListRequest,
PacketReceiver::makeSourcedListenerReference<DomainServer>(this, &DomainServer::processListRequestPacket));
packetReceiver.registerListener(PacketType::DomainServerPathQuery,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processPathQueryPacket));
packetReceiver.registerListener(PacketType::NodeJsonStats,
PacketReceiver::makeSourcedListenerReference<DomainServer>(this, &DomainServer::processNodeJSONStatsPacket));
packetReceiver.registerListener(PacketType::DomainDisconnectRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processNodeDisconnectRequestPacket));
packetReceiver.registerListener(PacketType::AvatarZonePresence,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processAvatarZonePresencePacket));
// NodeList won't be available to the settings manager when it is created, so call registerListener here
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket");
packetReceiver.registerListener(PacketType::DomainSettingsRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainServerSettingsManager>(&_settingsManager, &DomainServerSettingsManager::processSettingsRequestPacket));
packetReceiver.registerListener(PacketType::NodeKickRequest,
PacketReceiver::makeSourcedListenerReference<DomainServerSettingsManager>(&_settingsManager, &DomainServerSettingsManager::processNodeKickRequestPacket));
packetReceiver.registerListener(PacketType::UsernameFromIDRequest,
PacketReceiver::makeSourcedListenerReference<DomainServerSettingsManager>(&_settingsManager, &DomainServerSettingsManager::processUsernameFromIDRequestPacket));
// register the gatekeeper for the packets it needs to receive
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
packetReceiver.registerListener(PacketType::DomainConnectRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processConnectRequestPacket));
packetReceiver.registerListener(PacketType::ICEPing,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processICEPingPacket));
packetReceiver.registerListener(PacketType::ICEPingReply,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processICEPingReplyPacket));
packetReceiver.registerListener(PacketType::ICEServerPeerInformation,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processICEPeerInformationPacket));
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processICEServerHeartbeatDenialPacket));
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processICEServerHeartbeatACK));
packetReceiver.registerListener(PacketType::OctreeDataFileRequest, this, "processOctreeDataRequestMessage");
packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage");
packetReceiver.registerListener(PacketType::OctreeDataFileRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processOctreeDataRequestMessage));
packetReceiver.registerListener(PacketType::OctreeDataPersist,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processOctreeDataPersistMessage));
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest");
packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, this, "handleDomainContentReplacementFromURLRequest");
packetReceiver.registerListener(PacketType::OctreeFileReplacement,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::handleOctreeFileReplacementRequest));
packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::handleDomainContentReplacementFromURLRequest));
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);

View file

@ -212,6 +212,8 @@ private:
/// guard read/write access from multiple threads to settings
QReadWriteLock _settingsLock { QReadWriteLock::Recursive };
friend class DomainServer;
};
#endif // hifi_DomainServerSettingsManager_h

View file

@ -239,7 +239,7 @@ target_openssl()
target_bullet()
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()
add_crashpad()
target_breakpad()

View file

@ -3,9 +3,9 @@
"channels": [
{ "from": "Keyboard.A", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Q", "when": ["!Application.CameraSelfie", "!Keyboard.Control", "!Application.CaptureMouse"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Q", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": ["!Application.CameraSelfie", "!Keyboard.Control", "!Application.CaptureMouse"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.T", "when": "!Keyboard.Control", "to": "Actions.TogglePushToTalk" },
@ -72,46 +72,20 @@
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
]
},
"when": ["Application.CameraFirstPerson", "!Keyboard.Shift"],
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity", "!Application.CaptureMouse", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraFirstPersonLookat", "!Keyboard.Shift"],
"to": "Actions.Yaw"
{ "from": "Keyboard.Left",
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity", "Application.CaptureMouse", "!Keyboard.Shift"],
"to": "Actions.LATERAL_LEFT"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraThirdPerson", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraLookAt", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraSelfie", "!Keyboard.Shift"],
"to": "Actions.Yaw"
{ "from": "Keyboard.Right",
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity", "Application.CaptureMouse", "!Keyboard.Shift"],
"to": "Actions.LATERAL_RIGHT"
},
{ "from": { "makeAxis" : [
@ -119,53 +93,18 @@
["Keyboard.D"]
]
},
"when": ["Application.CameraFirstPerson", "!Keyboard.Control"],
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity", "!Application.CaptureMouse", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraFirstPersonLookat", "!Keyboard.Control"],
"to": "Actions.Yaw"
{ "from": "Keyboard.A",
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity", "Application.CaptureMouse", "!Keyboard.Control"],
"to": "Actions.LATERAL_LEFT"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraThirdPerson", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraLookAt", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraSelfie", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraFirstPerson",
"to": "Actions.Yaw"
{ "from": "Keyboard.D",
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity", "Application.CaptureMouse", "!Keyboard.Control"],
"to": "Actions.LATERAL_RIGHT"
},
{ "from": { "makeAxis" : [
@ -173,39 +112,12 @@
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraFirstPersonLookat",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraThirdPerson",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraLookAt",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraSelfie",
"when": ["!Application.CameraFSM", "!Application.CameraIndependent", "!Application.CameraEntity"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": "Keyboard.RightMouseButton",
"when": ["Keyboard.RightMouseButton", "!Application.CaptureMouse"],
"to": "Actions.DeltaYaw",
"filters":
[
@ -213,8 +125,17 @@
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"to": "Actions.DeltaYaw",
"when": "Application.CaptureMouse",
"filters":
[
{ "type": "scale", "scale": 0.2 }
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": ["!Application.CameraSelfie", "!Application.CameraLookAt", "Keyboard.RightMouseButton"],
"when": ["!Application.CameraSelfie", "!Application.CameraLookAt", "!Application.CaptureMouse", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
@ -222,6 +143,15 @@
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"to": "Actions.DeltaPitch",
"when": "Application.CaptureMouse",
"filters":
[
{ "type": "scale", "scale": 0.2 }
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": ["Application.CameraLookAt", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",

Binary file not shown.

View file

@ -333,6 +333,8 @@ Rectangle {
anchors.left: parent.left;
anchors.right: parent.right;
height: childrenRect.height;
// FIXME: Reuse or remove wallet-related code.
visible: false;
Rectangle {
id: walletHeaderContainer;

View file

@ -27,6 +27,7 @@ Item {
width: parent.width
property string title: "Controls"
property var openVRDevices: ["HTC Vive", "Valve Index", "Valve HMD", "Valve"]
HifiConstants { id: hifi }
@ -244,7 +245,7 @@ Item {
source: InputConfiguration.configurationLayout(box.textAt(box.currentIndex));
onLoaded: {
if (loader.item.hasOwnProperty("pluginName")) {
if (box.textAt(box.currentIndex) === "HTC Vive") {
if (openVRDevices.indexOf(box.textAt(box.currentIndex)) !== -1) {
loader.item.pluginName = "OpenVR";
} else {
loader.item.pluginName = box.textAt(box.currentIndex);
@ -298,7 +299,7 @@ Item {
loader.source = "";
var selectedDevice = box.textAt(box.currentIndex);
var source = "";
if (selectedDevice == "HTC Vive") {
if (openVRDevices.indexOf(selectedDevice) !== -1) {
source = InputConfiguration.configurationLayout("OpenVR");
} else {
source = InputConfiguration.configurationLayout(selectedDevice);

View file

@ -681,6 +681,9 @@ private:
* <tr><td><code>CameraIndependent</code></td><td>number</td><td>number</td><td>The camera is in independent mode.</td></tr>
* <tr><td><code>CameraEntity</code></td><td>number</td><td>number</td><td>The camera is in entity mode.</td></tr>
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
* <tr><td><code>CaptureMouse</code></td><td>number</td><td>number</td><td>The mouse is captured. In this mode,
* the mouse is invisible and cannot leave the bounds of Interface, as long as Interface is the active window and
* no menu item is selected.</td></tr>
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement (walking) controls are
* enabled.</td></tr>
* <tr><td><code>StrafeEnabled</code></td><td>number</td><td>number</td><td>Strafing is enabled</td></tr>
@ -716,6 +719,7 @@ static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid";
static const QString STATE_LEFT_HAND_DOMINANT = "LeftHandDominant";
static const QString STATE_RIGHT_HAND_DOMINANT = "RightHandDominant";
static const QString STATE_STRAFE_ENABLED = "StrafeEnabled";
static const QString STATE_CAPTURE_MOUSE = "CaptureMouse";
// Statically provided display and input plugins
extern DisplayPluginList getDisplayPlugins();
@ -919,7 +923,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<MessagesClient>();
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_FIRST_PERSON_LOOK_AT, STATE_CAMERA_THIRD_PERSON,
STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_CAMERA_LOOK_AT, STATE_CAMERA_SELFIE,
STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_CAMERA_LOOK_AT, STATE_CAMERA_SELFIE, STATE_CAPTURE_MOUSE,
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } });
DependencyManager::set<UserInputMapper>();
@ -1892,6 +1896,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_applicationStateDevice->setInputVariant(STATE_CAMERA_INDEPENDENT, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_INDEPENDENT ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAPTURE_MOUSE, []() -> float {
return qApp->getCamera().getCaptureMouse() ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float {
return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0;
});
@ -2407,6 +2414,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
updateSystemTabletMode();
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
connect(&_myCamera, &Camera::captureMouseChanged, this, &Application::captureMouseChanged);
DependencyManager::get<PickManager>()->setShouldPickHUDOperator([]() { return DependencyManager::get<HMDScriptingInterface>()->isHMDMode(); });
DependencyManager::get<PickManager>()->setCalculatePos2DFromHUDOperator([this](const glm::vec3& intersection) {
@ -4704,6 +4712,11 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) const {
void Application::mouseMoveEvent(QMouseEvent* event) {
PROFILE_RANGE(app_input_mouse, __FUNCTION__);
if (_ignoreMouseMove) {
_ignoreMouseMove = false;
return;
}
maybeToggleMenuVisible(event);
auto& compositor = getApplicationCompositor();
@ -4749,7 +4762,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
}
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->mouseMoveEvent(event);
_keyboardMouseDevice->mouseMoveEvent(event, _captureMouse, _mouseCaptureTarget);
}
}
@ -5950,6 +5963,20 @@ void Application::cameraModeChanged() {
cameraMenuChanged();
}
bool Application::shouldCaptureMouse() const {
return _captureMouse && _glWidget->isActiveWindow() && !ui::Menu::isSomeSubmenuShown();
}
void Application::captureMouseChanged(bool captureMouse) {
_captureMouse = captureMouse;
if (_captureMouse) {
_glWidget->setCursor(QCursor(Qt::BlankCursor));
} else {
_mouseCaptureTarget = QPointF(NAN, NAN);
_glWidget->unsetCursor();
}
}
void Application::changeViewAsNeeded(float boomLength) {
// Switch between first and third person views as needed
// This is called when the boom length has changed
@ -6300,6 +6327,15 @@ void Application::update(float deltaTime) {
PROFILE_ASYNC_END(app, "Scene Loading", "");
}
if (shouldCaptureMouse()) {
QPoint point = _glWidget->mapToGlobal(_glWidget->geometry().center());
if (QCursor::pos() != point) {
_mouseCaptureTarget = point;
_ignoreMouseMove = true;
QCursor::setPos(point);
}
}
auto myAvatar = getMyAvatar();
{
PerformanceTimer perfTimer("devices");
@ -6355,8 +6391,8 @@ void Application::update(float deltaTime) {
if (deltaTime > FLT_EPSILON && userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z) == 0.0f) {
myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH));
myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW));
myAvatar->setDriveKey(MyAvatar::DELTA_PITCH, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_PITCH));
myAvatar->setDriveKey(MyAvatar::DELTA_YAW, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_YAW));
myAvatar->setDriveKey(MyAvatar::DELTA_PITCH, -_myCamera.getSensitivity() * userInputMapper->getActionState(controller::Action::DELTA_PITCH));
myAvatar->setDriveKey(MyAvatar::DELTA_YAW, -_myCamera.getSensitivity() * userInputMapper->getActionState(controller::Action::DELTA_YAW));
myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW));
}
}

View file

@ -431,6 +431,7 @@ public slots:
void cycleCamera();
void cameraModeChanged();
void cameraMenuChanged();
void captureMouseChanged(bool captureMouse);
void toggleOverlays();
void setOverlaysVisible(bool visible);
Q_INVOKABLE void centerUI();
@ -603,6 +604,7 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event) const;
void toggleTabletUI(bool shouldOpen = false) const;
bool shouldCaptureMouse() const;
void userKickConfirmation(const QUuid& nodeID);
@ -756,7 +758,9 @@ private:
bool _settingsLoaded { false };
bool _fakedMouseEvent { false };
bool _captureMouse { false };
bool _ignoreMouseMove { false };
QPointF _mouseCaptureTarget { NAN, NAN };
bool _isMissingSequenceNumbers { false };

View file

@ -36,6 +36,10 @@ class FancyCamera : public Camera {
* @property {ViewFrustum} frustum - The camera frustum.
* @property {Uuid} cameraEntity - The ID of the entity that is used for the camera position and orientation when the
* camera is in entity mode.
* @property {boolean} captureMouse - The mouse capture state. When <code>true</code>, the mouse is invisible and cannot leave the bounds of
* Interface, as long as Interface is the active window and no menu item is selected. When <code>false</code>, the mouse
* behaves normally.
* @property {number} sensitivity - The current camera sensitivity. Must be positive.
*/
Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)

View file

@ -288,8 +288,10 @@ Wallet::Wallet() {
auto& packetReceiver = nodeList->getPacketReceiver();
_passphrase = new QString("");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnership,
PacketReceiver::makeSourcedListenerReference<Wallet>(this, &Wallet::handleChallengeOwnershipPacket));
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest,
PacketReceiver::makeSourcedListenerReference<Wallet>(this, &Wallet::handleChallengeOwnershipPacket));
connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) {
auto wallet = DependencyManager::get<Wallet>();

View file

@ -81,7 +81,7 @@ int main(int argc, const char* argv[]) {
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
QCommandLineOption runServerOption("runServer", "Whether to run the server");
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content <path>", "serverContentPath");
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts <path>", "path");

View file

@ -25,7 +25,8 @@ OctreePacketProcessor::OctreePacketProcessor():
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
const PacketReceiver::PacketTypeList octreePackets =
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete };
packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket");
packetReceiver.registerDirectListenerForTypes(octreePackets,
PacketReceiver::makeSourcedListenerReference<OctreePacketProcessor>(this, &OctreePacketProcessor::handleOctreePacket));
}
OctreePacketProcessor::~OctreePacketProcessor() { }

View file

@ -44,7 +44,8 @@ ScreenshareScriptingInterface::ScreenshareScriptingInterface() {
// This packet listener handles the packet containing information about the latest zone ID in which we are allowed to share.
auto nodeList = DependencyManager::get<NodeList>();
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacketOnClient");
packetReceiver.registerListener(PacketType::AvatarZonePresence,
PacketReceiver::makeUnsourcedListenerReference<ScreenshareScriptingInterface>(this, &ScreenshareScriptingInterface::processAvatarZonePresencePacketOnClient));
};
ScreenshareScriptingInterface::~ScreenshareScriptingInterface() {

View file

@ -9,7 +9,8 @@
//
#include "DomainConnectionModel.h"
#include <QLoggingCategory>
#include <QtCore/QLoggingCategory>
#include <QtCore/QMetaMethod>
#include <NodeList.h>
#include <NumericalConstants.h>

View file

@ -358,6 +358,16 @@ void setupPreferences() {
preference->setItems(items);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->float { return qApp->getCamera().getSensitivity(); };
auto setter = [myAvatar](float value) { qApp->getCamera().setSensitivity(value); };
auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Camera Sensitivity", getter, setter);
preference->setMin(0.01f);
preference->setMax(5.0f);
preference->setStep(0.1);
preference->setDecimals(2);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->int { return myAvatar->getControlScheme(); };
auto setter = [myAvatar](int index) { myAvatar->setControlScheme(index); };

View file

@ -274,7 +274,7 @@ private: \
* of 2 if smaller than 128 pixels, or a multiple of 128 if greater than 128 pixels.
* <em>Read-only.</em>
* @property {number} decimatedTextureCount - The number of textures that have been reduced in size because they were over the
* maximum allowed dimensions of 4096 pixels on desktop or 2048 pixels on mobile.
* maximum allowed dimensions of 8192 pixels on desktop or 2048 pixels on mobile.
* <em>Read-only.</em>
* @property {number} gpuBuffers - The number of OpenGL buffer objects managed by the GPU back-end.
* <em>Read-only.</em>

View file

@ -81,7 +81,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply,
PacketReceiver::makeSourcedListenerReference<ContextOverlayInterface>(this, &ContextOverlayInterface::handleChallengeOwnershipReplyPacket));
_challengeOwnershipTimeoutTimer.setSingleShot(true);
}

View file

@ -369,13 +369,20 @@ AudioClient::AudioClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket");
packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket");
packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket");
packetReceiver.registerListener(PacketType::MixedAudio, this, "handleAudioDataPacket");
packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
packetReceiver.registerListener(PacketType::AudioStreamStats,
PacketReceiver::makeSourcedListenerReference<AudioIOStats>(&_stats, &AudioIOStats::processStreamStatsPacket));
packetReceiver.registerListener(PacketType::AudioEnvironment,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleAudioEnvironmentDataPacket));
packetReceiver.registerListener(PacketType::SilentAudioFrame,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleAudioDataPacket));
packetReceiver.registerListener(PacketType::MixedAudio,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleAudioDataPacket));
packetReceiver.registerListener(PacketType::NoisyMute,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleNoisyMutePacket));
packetReceiver.registerListener(PacketType::MuteEnvironment,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleMuteEnvironmentPacket));
packetReceiver.registerListener(PacketType::SelectedAudioFormat,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleSelectedAudioFormat));
auto& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {

View file

@ -123,10 +123,14 @@ AvatarHashMap::AvatarHashMap() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits");
packetReceiver.registerListener(PacketType::BulkAvatarData,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processAvatarDataPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processKillAvatar));
packetReceiver.registerListener(PacketType::AvatarIdentity,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processAvatarIdentityPacket));
packetReceiver.registerListener(PacketType::BulkAvatarTraits,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processBulkAvatarTraits));
connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);

View file

@ -28,7 +28,8 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
}
});
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride");
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits,
PacketReceiver::makeSourcedListenerReference<ClientTraitsHandler>(this, &ClientTraitsHandler::processTraitOverride));
}
void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) {

View file

@ -13,7 +13,7 @@ include_hifi_library_headers(ktx)
include_hifi_library_headers(render)
include_hifi_library_headers(procedural)
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()
GroupSources("src/display-plugins")

View file

@ -1,6 +1,7 @@
//
// Created by Benjamin Arnold on 5/27/14.
// Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -213,7 +214,6 @@ void CompositorHelper::setAllowMouseCapture(bool capture) {
_allowMouseCapture = capture;
emit allowMouseCaptureChanged();
}
_allowMouseCapture = capture;
}
void CompositorHelper::handleLeaveEvent() {

View file

@ -1,6 +1,7 @@
//
// Created by Bradley Austin Davis Arnold on 2015/06/13
// Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html

View file

@ -26,7 +26,8 @@
EntityEditPacketSender::EntityEditPacketSender() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerDirectListener(PacketType::EntityEditNack, this, "processEntityEditNackPacket");
packetReceiver.registerDirectListener(PacketType::EntityEditNack,
PacketReceiver::makeSourcedListenerReference<EntityEditPacketSender>(this, &EntityEditPacketSender::processEntityEditNackPacket));
}
void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {

View file

@ -1214,7 +1214,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* ],
* strokeWidths: [ 0.1, 0.1, 0.1 ],
* color: { red: 255, green: 0, blue: 0 }, // Use just the red channel from the image.
* textures: "http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/flowArts/trails.png",
* textures: "https://cdn-1.vircadia.com/us-e-1/DomainContent/Toybox/flowArts/trails.png",
* isUVModeStretch: true,
* lifetime: 300 // Delete after 5 minutes.
* });

View file

@ -14,7 +14,8 @@
EntityScriptServerLogClient::EntityScriptServerLogClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
packetReceiver.registerListener(PacketType::EntityServerScriptLog,
PacketReceiver::makeSourcedListenerReference<EntityScriptServerLogClient>(this, &EntityScriptServerLogClient::handleEntityServerScriptLogPacket));
QObject::connect(nodeList.data(), &NodeList::nodeActivated, this, &EntityScriptServerLogClient::nodeActivated);
QObject::connect(nodeList.data(), &NodeList::nodeKilled, this, &EntityScriptServerLogClient::nodeKilled);

View file

@ -59,7 +59,8 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged);
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
packetReceiver.registerListener(PacketType::EntityScriptCallMethod,
PacketReceiver::makeSourcedListenerReference<EntityScriptingInterface>(this, &EntityScriptingInterface::handleEntityScriptCallMethodPacket));
}
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,

View file

@ -709,6 +709,8 @@ public slots:
/**jsdoc
* Finds all domain and avatar entities that intersect a sphere.
* <p><strong>Note:</strong> Server entity scripts only find entities that have a server entity script
* running in them or a parent entity. You can apply a dummy script to entities that you want found in a search.</p>
* @function Entities.findEntities
* @param {Vec3} center - The point about which to search.
* @param {number} radius - The radius within which to search.
@ -723,6 +725,8 @@ public slots:
/**jsdoc
* Finds all domain and avatar entities whose axis-aligned boxes intersect a search axis-aligned box.
* <p><strong>Note:</strong> Server entity scripts only find entities that have a server entity script
* running in them or a parent entity. You can apply a dummy script to entities that you want found in a search.</p>
* @function Entities.findEntitiesInBox
* @param {Vec3} corner - The corner of the search AA box with minimum co-ordinate values.
* @param {Vec3} dimensions - The dimensions of the search AA box.
@ -734,6 +738,8 @@ public slots:
/**jsdoc
* Finds all domain and avatar entities whose axis-aligned boxes intersect a search frustum.
* <p><strong>Note:</strong> Server entity scripts only find entities that have a server entity script
* running in them or a parent entity. You can apply a dummy script to entities that you want found in a search.</p>
* @function Entities.findEntitiesInFrustum
* @param {ViewFrustum} frustum - The frustum to search in. The <code>position</code>, <code>orientation</code>,
* <code>projection</code>, and <code>centerRadius</code> properties must be specified. The <code>fieldOfView</code>
@ -749,6 +755,8 @@ public slots:
/**jsdoc
* Finds all domain and avatar entities of a particular type that intersect a sphere.
* <p><strong>Note:</strong> Server entity scripts only find entities that have a server entity script
* running in them or a parent entity. You can apply a dummy script to entities that you want found in a search.</p>
* @function Entities.findEntitiesByType
* @param {Entities.EntityType} entityType - The type of entity to search for.
* @param {Vec3} center - The point about which to search.
@ -764,6 +772,8 @@ public slots:
/**jsdoc
* Finds all domain and avatar entities with a particular name that intersect a sphere.
* <p><strong>Note:</strong> Server entity scripts only find entities that have a server entity script
* running in them or a parent entity. You can apply a dummy script to entities that you want found in a search.</p>
* @function Entities.findEntitiesByName
* @param {string} entityName - The name of the entity to search for.
* @param {Vec3} center - The point about which to search.

View file

@ -1586,12 +1586,16 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
int targetIndex = weightedIndex;
hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex));
if (!names.isEmpty() && names.contains(keys[weightedIndex])) {
if (!names.isEmpty()) {
targetIndex = names.indexOf(keys[weightedIndex]);
if (targetIndex == -1) {
continue; // Ignore blendshape targets not present in glTF file.
}
indexFromMapping = values[weightedIndex].first;
weight = values[weightedIndex].second;
hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex];
}
HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping];
auto target = primitive.targets[targetIndex];

View file

@ -2,6 +2,6 @@ set(TARGET_NAME gl)
setup_hifi_library(Gui Widgets)
link_hifi_libraries(shared)
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()

View file

@ -74,6 +74,10 @@ static void* getGlProcessAddress(const char *namez) {
#else
typedef Bool (*PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC) (int attribute, unsigned int *value);
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC QueryCurrentRendererIntegerMESA;
static void* getGlProcessAddress(const char *namez) {
return (void*)glXGetProcAddressARB((const GLubyte*)namez);
}
@ -92,6 +96,10 @@ void gl::initModuleGl() {
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)getGlProcessAddress("wglCreateContextAttribsARB");
#endif
#if defined(Q_OS_LINUX)
QueryCurrentRendererIntegerMESA = (PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)getGlProcessAddress("glXQueryCurrentRendererIntegerMESA");
#endif
#if defined(USE_GLES)
gladLoadGLES2Loader(getGlProcessAddress);
#else
@ -124,3 +132,14 @@ void gl::setSwapInterval(int interval) {
Q_UNUSED(interval);
#endif
}
bool gl::queryCurrentRendererIntegerMESA(int attr, unsigned int *value) {
#if defined(Q_OS_LINUX)
if (QueryCurrentRendererIntegerMESA) {
return QueryCurrentRendererIntegerMESA(attr, value);
}
#endif
*value = 0;
return false;
}

View file

@ -52,6 +52,7 @@ namespace gl {
void initModuleGl();
int getSwapInterval();
void setSwapInterval(int swapInterval);
bool queryCurrentRendererIntegerMESA(int attr, unsigned int *value);
}
#endif // hifi_gpu_GPUConfig_h

View file

@ -3,6 +3,6 @@ setup_hifi_library(Concurrent)
link_hifi_libraries(shared gl gpu shaders)
GroupSources("src")
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()

View file

@ -10,11 +10,13 @@
//
#include "GLBackend.h"
#include <mutex>
#include <queue>
#include <list>
#include <functional>
#include <glm/gtc/type_ptr.hpp>
#include "gl/Config.h"
#if defined(NSIGHT_FOUND)
#include "nvToolsExt.h"
@ -105,13 +107,27 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
};
#define GL_GET_INTEGER(NAME) glGetIntegerv(GL_##NAME, &const_cast<GLint&>(NAME));
#define BYTES_PER_KIB 1024L
#define BYTES_PER_MIB (1024L * BYTES_PER_KIB)
GLint GLBackend::MAX_TEXTURE_IMAGE_UNITS{ 0 };
GLint GLBackend::MAX_UNIFORM_BUFFER_BINDINGS{ 0 };
GLint GLBackend::MAX_COMBINED_UNIFORM_BLOCKS{ 0 };
GLint GLBackend::MAX_COMBINED_TEXTURE_IMAGE_UNITS{ 0 };
GLint GLBackend::MAX_UNIFORM_BLOCK_SIZE{ 0 };
GLint GLBackend::UNIFORM_BUFFER_OFFSET_ALIGNMENT{ 1 };
GLint GLBackend::GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX{ 0 };
GLint GLBackend::GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX{ 0 };
GLint GLBackend::GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX{ 0 };
GLint GLBackend::TEXTURE_FREE_MEMORY_ATI{ 0 };
size_t GLBackend::_totalMemory{ 0 };
size_t GLBackend::_dedicatedMemory{ 0 };
GLBackend::VideoCardType GLBackend::_videoCard{ GLBackend::Unknown };
#define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187
void GLBackend::init() {
static std::once_flag once;
@ -132,13 +148,59 @@ void GLBackend::init() {
GL_GET_INTEGER(MAX_UNIFORM_BLOCK_SIZE);
GL_GET_INTEGER(UNIFORM_BUFFER_OFFSET_ALIGNMENT);
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
unsigned int mem;
if (vendor.contains("NVIDIA") ) {
qCDebug(gpugllogging) << "NVIDIA card detected";
GL_GET_INTEGER(GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX);
GL_GET_INTEGER(GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX);
GL_GET_INTEGER(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: " << GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX;
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX: " << GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX;
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX: " << GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX;
_totalMemory = GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX * BYTES_PER_KIB;
_dedicatedMemory = GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX * BYTES_PER_KIB;
_videoCard = NVIDIA;
} else if (vendor.contains("ATI")) {
qCDebug(gpugllogging) << "ATI card detected";
GL_GET_INTEGER(TEXTURE_FREE_MEMORY_ATI);
_totalMemory = TEXTURE_FREE_MEMORY_ATI * BYTES_PER_KIB;
_dedicatedMemory = _totalMemory;
_videoCard = ATI;
} else if ( ::gl::queryCurrentRendererIntegerMESA(GLX_RENDERER_VIDEO_MEMORY_MESA, &mem) ) {
// This works only on Linux. queryCurrentRendererIntegerMESA will return false if the
// function is not supported because we're not on Linux, or for any other reason.
qCDebug(gpugllogging) << "MESA card detected";
_totalMemory = mem * BYTES_PER_MIB;
_dedicatedMemory = _totalMemory;
_videoCard = MESA;
} else {
qCCritical(gpugllogging) << "Don't know how to get memory for OpenGL vendor " << vendor << "; renderer " << renderer << ", trying fallback";
_videoCard = Unknown;
_dedicatedMemory = gpu->getMemory();
_totalMemory = _dedicatedMemory;
}
qCDebug(gpugllogging) << "dedicated: " << _dedicatedMemory;
qCDebug(gpugllogging) << "total: " << _totalMemory;
LOG_GL_CONTEXT_INFO(gpugllogging, contextInfo);
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
// From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers.
qCDebug(gpugllogging) << "GPU:";
qCDebug(gpugllogging) << "\tcard:" << gpu->getName();
qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver();
qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB";
qCDebug(gpugllogging) << "\ttotal memory:" << (_totalMemory / BYTES_PER_KIB) << "KB";
qCDebug(gpugllogging) << "\tdedicated memory:" << (_dedicatedMemory / BYTES_PER_KIB) << "KB";
qCDebug(gpugllogging) << "\tavailable memory:" << (getAvailableMemory() / BYTES_PER_KIB) << "KB";
qCDebug(gpugllogging) << "Limits:";
qCDebug(gpugllogging) << "\tmax textures:" << MAX_TEXTURE_IMAGE_UNITS;
qCDebug(gpugllogging) << "\tmax texture binding:" << MAX_COMBINED_TEXTURE_IMAGE_UNITS;
@ -152,6 +214,41 @@ void GLBackend::init() {
});
}
size_t GLBackend::getAvailableMemory() {
GLint mem;
switch( _videoCard ) {
case NVIDIA:
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem);
return mem * BYTES_PER_KIB;
case ATI:
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem);
return mem * BYTES_PER_KIB;
case MESA:
return 0; // Don't know the current value
case Unknown:
break;
}
return 0;
}
bool GLBackend::availableMemoryKnown() {
switch( _videoCard ) {
case NVIDIA:
return true;
case ATI:
return true;
case MESA:
return false;
case Unknown:
return false;
}
return false;
}
GLBackend::GLBackend(bool syncCache) {
_pipeline._cameraCorrectionBuffer._buffer->flush();
initShaderBinaryCache();

View file

@ -67,6 +67,13 @@ protected:
GLBackend();
public:
enum VideoCardType {
ATI,
NVIDIA,
MESA,
Unknown
};
#if defined(USE_GLES)
// https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml
static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16;
@ -89,6 +96,25 @@ public:
static GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS;
static GLint MAX_UNIFORM_BLOCK_SIZE;
static GLint UNIFORM_BUFFER_OFFSET_ALIGNMENT;
static GLint GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX;
static GLint GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX;
static GLint GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX;
static GLint TEXTURE_FREE_MEMORY_ATI;
static size_t _totalMemory;
static size_t _dedicatedMemory;
static VideoCardType _videoCard;
static size_t getTotalMemory() { return _totalMemory; }
static size_t getDedicatedMemory() { return _dedicatedMemory; }
static size_t getAvailableMemory();
static bool availableMemoryKnown();
virtual ~GLBackend();

View file

@ -19,6 +19,8 @@
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define NO_BUFFER_WORK_SLEEP_TIME_MS 2
#define THREADED_TEXTURE_BUFFERING 1
#define MAX_AUTO_FRACTION_OF_TOTAL_MEMORY 0.8f
#define AUTO_RESERVE_TEXTURE_MEMORY MB_TO_BYTES(64)
static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB);
@ -183,9 +185,25 @@ void GLTextureTransferEngineDefault::manageMemory() {
void GLTextureTransferEngineDefault::updateMemoryPressure() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
bool useAvailableGlMemory = false;
size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage();
if (0 == allowedMemoryAllocation) {
allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY;
// Automatic allocation
if (GLBackend::availableMemoryKnown()) {
// If we know how much is free, then we use that
useAvailableGlMemory = true;
} else {
// We don't know how much is free, so leave some reasonable spare room
// and hope it works.
allowedMemoryAllocation = GLBackend::getTotalMemory() * MAX_AUTO_FRACTION_OF_TOTAL_MEMORY;
if (0 == allowedMemoryAllocation) {
// Last resort, if we failed to detect
allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY;
}
}
}
// Clear any defunct textures (weak pointers that no longer have a valid texture)
@ -205,7 +223,7 @@ void GLTextureTransferEngineDefault::updateMemoryPressure() {
idealMemoryAllocation += texture->evalTotalSize();
// Track how much we're actually using
totalVariableMemoryAllocation += gltexture->size();
if (vartexture->canDemote()) {
if (!gltexture->_gpuObject.getImportant() && vartexture->canDemote()) {
canDemote |= true;
}
if (vartexture->canPromote()) {
@ -218,7 +236,22 @@ void GLTextureTransferEngineDefault::updateMemoryPressure() {
Backend::textureResourceIdealGPUMemSize.set(idealMemoryAllocation);
size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation;
float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
float pressure = 0;
if (useAvailableGlMemory) {
size_t totalMem = GLBackend::getTotalMemory();
size_t availMem = GLBackend::getAvailableMemory();
if (availMem >= AUTO_RESERVE_TEXTURE_MEMORY) {
availMem -= AUTO_RESERVE_TEXTURE_MEMORY;
} else {
availMem = 0;
}
pressure = ((float)totalMem - (float)availMem) / (float)totalMem;
} else {
pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
}
// If we're oversubscribed we need to demote textures IMMEDIATELY
if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
@ -470,7 +503,7 @@ void GLTextureTransferEngineDefault::processDemotes(size_t reliefRequired, const
for (const auto& texture : strongTextures) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
if (vargltexture->canDemote()) {
if (!gltexture->_gpuObject.getImportant() && vargltexture->canDemote()) {
demoteQueue.push({ texture, (float)gltexture->size() });
}
}

View file

@ -6,6 +6,6 @@ if (UNIX)
endif(UNIX)
GroupSources("src")
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()

View file

@ -571,6 +571,9 @@ public:
void setExternalRecycler(const ExternalRecycler& recycler);
ExternalRecycler getExternalRecycler() const;
bool getImportant() const { return _important; }
void setImportant(bool important) { _important = important; }
const GPUObjectPointer gpuObject {};
ExternalUpdates getUpdates() const;
@ -632,6 +635,7 @@ protected:
bool _autoGenerateMips = false;
bool _isIrradianceValid = false;
bool _defined = false;
bool _important = false;
static TexturePointer create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler);

View file

@ -4,6 +4,7 @@
//
// Created by Sam Gateau on 4/27/15.
// Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -36,13 +37,12 @@ void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputC
_inputDevice->_axisStateMap[MOUSE_AXIS_X].value = _lastCursor.x();
_inputDevice->_axisStateMap[MOUSE_AXIS_Y].value = _lastCursor.y();
QPoint currentMove = _lastCursor - _previousCursor;
updateDeltaAxisValue(MOUSE_AXIS_X_POS, currentMove.x() > 0 ? currentMove.x() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_X_NEG, currentMove.x() < 0 ? -currentMove.x() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_X_POS, _accumulatedMove.x() > 0 ? _accumulatedMove.x() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_X_NEG, _accumulatedMove.x() < 0 ? -_accumulatedMove.x() : 0.0f);
// Y mouse is inverted positive is pointing up the screen
updateDeltaAxisValue(MOUSE_AXIS_Y_POS, currentMove.y() < 0 ? -currentMove.y() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_Y_NEG, currentMove.y() > 0 ? currentMove.y() : 0.0f);
_previousCursor = _lastCursor;
updateDeltaAxisValue(MOUSE_AXIS_Y_POS, _accumulatedMove.y() < 0 ? -_accumulatedMove.y() : 0.0f);
updateDeltaAxisValue(MOUSE_AXIS_Y_NEG, _accumulatedMove.y() > 0 ? _accumulatedMove.y() : 0.0f);
_accumulatedMove = QPoint(0.0f, 0.0f);
});
// For touch event, we need to check that the last event is not too long ago
@ -113,9 +113,16 @@ void KeyboardMouseDevice::eraseMouseClicked() {
_inputDevice->_buttonPressedMap.erase(_inputDevice->makeInput(Qt::RightButton, true).getChannel());
}
void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) {
void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, bool capture, QPointF captureTarget) {
QPoint currentPos = event->pos();
if (!capture) {
_accumulatedMove += currentPos - _lastCursor;
} else if (!isNaN(captureTarget.x())) {
QPointF change = event->globalPos() - captureTarget;
_accumulatedMove += QPoint(change.x(), change.y());
}
// FIXME - this has the characteristic that it will show large jumps when you move the cursor
// outside of the application window, because we don't get MouseEvents when the cursor is outside
// of the application window.

View file

@ -4,6 +4,7 @@
//
// Created by Sam Gateau on 4/27/15.
// Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -79,7 +80,7 @@ public:
void keyPressEvent(QKeyEvent* event);
void keyReleaseEvent(QKeyEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event, bool capture, QPointF captureTarget);
void mousePressEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
void eraseMouseClicked();
@ -123,7 +124,7 @@ public:
protected:
QPoint _lastCursor;
QPoint _previousCursor;
QPoint _accumulatedMove;
QPoint _mousePressPos;
quint64 _mousePressTime;
qreal _lastTotalScaleFactor;

View file

@ -43,10 +43,14 @@ AssetClient::AssetClient() {
auto nodeList = DependencyManager::get<LimitedNodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetMappingOperationReply, this, "handleAssetMappingOperationReply");
packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply");
packetReceiver.registerListener(PacketType::AssetGetReply, this, "handleAssetGetReply", true);
packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply");
packetReceiver.registerListener(PacketType::AssetMappingOperationReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetMappingOperationReply));
packetReceiver.registerListener(PacketType::AssetGetInfoReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetGetInfoReply));
packetReceiver.registerListener(PacketType::AssetGetReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetGetReply), true);
packetReceiver.registerListener(PacketType::AssetUploadReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetUploadReply));
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &AssetClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,

View file

@ -76,9 +76,9 @@ const quint16 DOMAIN_SERVER_EXPORTER_PORT =
const quint16 DOMAIN_SERVER_METADATA_EXPORTER_PORT =
QProcessEnvironment::systemEnvironment()
.contains("DOMAIN_SERVER_METADATA_EXPORTER_PORT")
.contains("VIRCADIA_DOMAIN_SERVER_METADATA_EXPORTER_PORT")
? QProcessEnvironment::systemEnvironment()
.value("DOMAIN_SERVER_METADATA_EXPORTER_PORT")
.value("VIRCADIA_DOMAIN_SERVER_METADATA_EXPORTER_PORT")
.toUInt()
: 9704;

View file

@ -34,7 +34,8 @@ EntityScriptClient::EntityScriptClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply, this, "handleGetScriptStatusReply");
packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply,
PacketReceiver::makeSourcedListenerReference<EntityScriptClient>(this, &EntityScriptClient::handleGetScriptStatusReply));
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,

View file

@ -13,6 +13,7 @@
#include <QtCore/QFile>
#include <QtCore/QFileSelector>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <StatTracker.h>
@ -54,6 +55,7 @@ void FileResourceRequest::doSend() {
} else {
QFile file(filename);
if (file.exists()) {
setProperty("last-modified", toHttpDateString(QFileInfo(file).lastModified().toMSecsSinceEpoch()));
if (file.open(QFile::ReadOnly)) {
if (file.size() < _byteRange.fromInclusive || file.size() < _byteRange.toExclusive) {

View file

@ -28,7 +28,8 @@ MessagesClient::MessagesClient() {
});
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessagesPacket");
packetReceiver.registerListener(PacketType::MessagesData,
PacketReceiver::makeSourcedListenerReference<MessagesClient>(this, &MessagesClient::handleMessagesPacket));
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated);
}

View file

@ -139,20 +139,34 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
startSTUNPublicSocketUpdate();
auto& packetReceiver = getPacketReceiver();
packetReceiver.registerListener(PacketType::DomainList, this, "processDomainServerList");
packetReceiver.registerListener(PacketType::Ping, this, "processPingPacket");
packetReceiver.registerListener(PacketType::PingReply, this, "processPingReplyPacket");
packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket");
packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode");
packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket");
packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket");
packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList");
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket");
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket");
packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket");
packetReceiver.registerListener(PacketType::DomainServerPathResponse, this, "processDomainServerPathResponse");
packetReceiver.registerListener(PacketType::DomainServerRemovedNode, this, "processDomainServerRemovedNode");
packetReceiver.registerListener(PacketType::UsernameFromIDReply, this, "processUsernameFromIDReply");
packetReceiver.registerListener(PacketType::DomainList,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerList));
packetReceiver.registerListener(PacketType::Ping,
PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingPacket));
packetReceiver.registerListener(PacketType::PingReply,
PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingReplyPacket));
packetReceiver.registerListener(PacketType::ICEPing,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processICEPingPacket));
packetReceiver.registerListener(PacketType::DomainServerAddedNode,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerAddedNode));
packetReceiver.registerListener(PacketType::DomainServerConnectionToken,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerConnectionTokenPacket));
packetReceiver.registerListener(PacketType::DomainConnectionDenied,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processDomainServerConnectionDeniedPacket));
packetReceiver.registerListener(PacketType::DomainSettings,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processSettingsPacketList));
packetReceiver.registerListener(PacketType::ICEServerPeerInformation,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processICEResponsePacket));
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processDTLSRequirementPacket));
packetReceiver.registerListener(PacketType::ICEPingReply,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processICEPingReplyPacket));
packetReceiver.registerListener(PacketType::DomainServerPathResponse,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerPathResponse));
packetReceiver.registerListener(PacketType::DomainServerRemovedNode,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerRemovedNode));
packetReceiver.registerListener(PacketType::UsernameFromIDReply,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processUsernameFromIDReply));
}
qint64 NodeList::sendStats(QJsonObject statsObject, HifiSockAddr destination) {

View file

@ -12,7 +12,8 @@
#include "PacketReceiver.h"
#include <QMutexLocker>
#include <QtCore/QMetaObject>
#include <QtCore/QMutexLocker>
#include "DependencyManager.h"
#include "NetworkLogging.h"
@ -25,85 +26,57 @@ PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) {
qRegisterMetaType<QSharedPointer<ReceivedMessage>>();
}
bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) {
bool PacketReceiver::ListenerReference::invokeWithQt(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode) {
ListenerReferencePointer thisPointer = sharedFromThis();
return QMetaObject::invokeMethod(getObject(), [=]() {
thisPointer->invokeDirectly(receivedMessagePointer, sourceNode);
});
}
bool PacketReceiver::registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) {
Q_ASSERT_X(!types.empty(), "PacketReceiver::registerListenerForTypes", "No types to register");
Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerListenerForTypes", "No slot to register");
Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No listener to register");
// Partition types based on whether they are sourced or not (non sourced in front)
auto middle = std::partition(std::begin(types), std::end(types), [](PacketType type) {
return PacketTypeEnum::getNonSourcedPackets().contains(type);
});
QMetaMethod nonSourcedMethod, sourcedMethod;
// Check we have a valid method for non sourced types if any
if (middle != std::begin(types)) {
nonSourcedMethod = matchingMethodForListener(*std::begin(types), listener, slot);
if (!nonSourcedMethod.isValid()) {
return false;
}
}
// Check we have a valid method for sourced types if any
if (middle != std::end(types)) {
sourcedMethod = matchingMethodForListener(*middle, listener, slot);
if (!sourcedMethod.isValid()) {
return false;
}
}
// Register non sourced types
std::for_each(std::begin(types), middle, [this, &listener, &nonSourcedMethod](PacketType type) {
registerVerifiedListener(type, listener, nonSourcedMethod);
});
// Register sourced types
std::for_each(middle, std::end(types), [this, &listener, &sourcedMethod](PacketType type) {
registerVerifiedListener(type, listener, sourcedMethod);
std::for_each(std::begin(types), std::end(types), [this, &listener](PacketType type) {
registerVerifiedListener(type, listener);
});
return true;
}
void PacketReceiver::registerDirectListener(PacketType type, QObject* listener, const char* slot) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerDirectListener", "No slot to register");
void PacketReceiver::registerDirectListener(PacketType type, const ListenerReferencePointer& listener) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No listener to register");
bool success = registerListener(type, listener, slot);
bool success = registerListener(type, listener);
if (success) {
QMutexLocker locker(&_directConnectSetMutex);
// if we successfully registered, add this object to the set of objects that are directly connected
_directlyConnectedObjects.insert(listener);
_directlyConnectedObjects.insert(listener->getObject());
}
}
void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types,
QObject* listener, const char* slot) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerDirectListenerForTypes", "No slot to register");
void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No listener to register");
// just call register listener for types to start
bool success = registerListenerForTypes(std::move(types), listener, slot);
bool success = registerListenerForTypes(std::move(types), listener);
if (success) {
QMutexLocker locker(&_directConnectSetMutex);
// if we successfully registered, add this object to the set of objects that are directly connected
_directlyConnectedObjects.insert(listener);
_directlyConnectedObjects.insert(listener->getObject());
}
}
bool PacketReceiver::registerListener(PacketType type, QObject* listener, const char* slot,
bool deliverPending) {
Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerListener", "No slot to register");
bool PacketReceiver::registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) {
Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No listener to register");
QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot);
bool matchingMethod = matchingMethodForListener(type, listener);
if (matchingMethod.isValid()) {
if (matchingMethod) {
qCDebug(networking) << "Registering a packet listener for packet list type" << type;
registerVerifiedListener(type, listener, matchingMethod, deliverPending);
registerVerifiedListener(type, listener, deliverPending);
return true;
} else {
qCWarning(networking) << "FAILED to Register a packet listener for packet list type" << type;
@ -111,62 +84,23 @@ bool PacketReceiver::registerListener(PacketType type, QObject* listener, const
}
}
QMetaMethod PacketReceiver::matchingMethodForListener(PacketType type, QObject* object, const char* slot) const {
Q_ASSERT_X(object, "PacketReceiver::matchingMethodForListener", "No object to call");
Q_ASSERT_X(slot, "PacketReceiver::matchingMethodForListener", "No slot to call");
bool PacketReceiver::matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const {
Q_ASSERT_X(listener, "PacketReceiver::matchingMethodForListener", "No listener to call");
// normalize the slot with the expected parameters
static const QString SIGNATURE_TEMPLATE("%1(%2)");
static const QString NON_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>";
bool isSourced = listener->isSourced();
bool isNonSourcedPacket = PacketTypeEnum::getNonSourcedPackets().contains(type);
QSet<QString> possibleSignatures {
SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_MESSAGE_LISTENER_PARAMETERS)
};
if (!PacketTypeEnum::getNonSourcedPackets().contains(type)) {
static const QString SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>,QSharedPointer<Node>";
static const QString TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>,SharedNodePointer";
// a sourced packet must take the shared pointer to the ReceivedMessage but optionally could include
// a shared pointer to the node
possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS);
possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_MESSAGE_LISTENER_PARAMETERS);
assert(!isSourced || !isNonSourcedPacket);
if (isSourced && isNonSourcedPacket) {
qCDebug(networking) << "PacketReceiver::registerListener cannot support a sourced listener for type" << type;
return false;
}
int methodIndex = -1;
foreach(const QString& signature, possibleSignatures) {
QByteArray normalizedSlot =
QMetaObject::normalizedSignature(signature.toStdString().c_str());
// does the constructed normalized method exist?
methodIndex = object->metaObject()->indexOfSlot(normalizedSlot.toStdString().c_str());
if (methodIndex >= 0) {
break;
}
}
if (methodIndex < 0) {
qCDebug(networking) << "PacketReceiver::registerListener expected a slot with one of the following signatures:"
<< possibleSignatures.toList() << "- but such a slot was not found."
<< "Could not complete listener registration for type" << type;
}
Q_ASSERT(methodIndex >= 0);
// return the converted QMetaMethod
if (methodIndex >= 0) {
return object->metaObject()->method(methodIndex);
} else {
// if somehow (scripting?) something bad gets in here at runtime that doesn't hit the asserts above
// return a non-valid QMetaMethod
return QMetaMethod();
}
return true;
}
void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot, bool deliverPending) {
Q_ASSERT_X(object, "PacketReceiver::registerVerifiedListener", "No object to register");
void PacketReceiver::registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) {
Q_ASSERT_X(listener, "PacketReceiver::registerVerifiedListener", "No listener to register");
QMutexLocker locker(&_packetListenerLock);
if (_messageListenerMap.contains(type)) {
@ -175,7 +109,7 @@ void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object,
}
// add the mapping
_messageListenerMap[type] = { QPointer<QObject>(object), slot, deliverPending };
_messageListenerMap[type] = { listener, deliverPending };
}
void PacketReceiver::unregisterListener(QObject* listener) {
@ -188,7 +122,7 @@ void PacketReceiver::unregisterListener(QObject* listener) {
auto it = _messageListenerMap.begin();
while (it != _messageListenerMap.end()) {
if (it.value().object == listener) {
if (it.value().listener->getObject() == listener) {
it = _messageListenerMap.erase(it);
} else {
++it;
@ -261,7 +195,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
QMutexLocker packetListenerLocker(&_packetListenerLock);
auto it = _messageListenerMap.find(receivedMessage->getType());
if (it != _messageListenerMap.end() && it->method.isValid()) {
if (it != _messageListenerMap.end() && !it->listener.isNull()) {
auto listener = it.value();
@ -271,36 +205,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
bool success = false;
Qt::ConnectionType connectionType;
bool isDirectConnect = false;
// check if this is a directly connected listener
{
QMutexLocker directConnectLocker(&_directConnectSetMutex);
connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection;
isDirectConnect = _directlyConnectedObjects.contains(listener.listener->getObject());
}
QMetaMethod metaMethod = listener.method;
static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer<Node>");
static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer");
// one final check on the QPointer before we go to invoke
if (listener.object) {
if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) {
success = metaMethod.invoke(listener.object,
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage),
Q_ARG(SharedNodePointer, matchingNode));
} else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) {
success = metaMethod.invoke(listener.object,
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage),
Q_ARG(QSharedPointer<Node>, matchingNode));
if (listener.listener->getObject()) {
if (isDirectConnect) {
success = listener.listener->invokeDirectly(receivedMessage, matchingNode);
} else {
success = metaMethod.invoke(listener.object,
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage));
success = listener.listener->invokeWithQt(receivedMessage, matchingNode);
}
} else {
qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType()
@ -310,19 +227,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
// if it exists, remove the listener from _directlyConnectedObjects
{
QMutexLocker directConnectLocker(&_directConnectSetMutex);
_directlyConnectedObjects.remove(listener.object);
_directlyConnectedObjects.remove(listener.listener->getObject());
}
}
if (!success) {
qCDebug(networking).nospace() << "Error delivering packet " << receivedMessage->getType() << " to listener "
<< listener.object << "::" << qPrintable(listener.method.methodSignature());
<< listener.listener->getObject();
}
} else if (it == _messageListenerMap.end()) {
qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType();
// insert a dummy listener so we don't print this again
_messageListenerMap.insert(receivedMessage->getType(), { nullptr, QMetaMethod(), false });
_messageListenerMap.insert(receivedMessage->getType(), { ListenerReferencePointer(), false });
}
}

View file

@ -16,12 +16,12 @@
#include <vector>
#include <unordered_map>
#include <QtCore/QMap>
#include <QtCore/QMetaMethod>
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QSet>
#include <QtCore/QSharedPointer>
#include <QtCore/QEnableSharedFromThis>
#include "NLPacket.h"
#include "NLPacketList.h"
@ -29,6 +29,7 @@
#include "udt/PacketHeaders.h"
class EntityEditPacketSender;
class Node;
class OctreePacketProcessor;
namespace std {
@ -42,6 +43,22 @@ namespace std {
class PacketReceiver : public QObject {
Q_OBJECT
public:
class ListenerReference : public QEnableSharedFromThis<ListenerReference> {
public:
virtual bool invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode) = 0;
bool invokeWithQt(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode);
virtual bool isSourced() const = 0;
virtual QObject* getObject() const = 0;
};
typedef QSharedPointer<ListenerReference> ListenerReferencePointer;
template<class T>
static ListenerReferencePointer makeUnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>));
template <class T>
static ListenerReferencePointer makeSourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>));
public:
using PacketTypeList = std::vector<PacketType>;
@ -55,8 +72,8 @@ public:
// If deliverPending is false, ReceivedMessage will only be delivered once all packets for the message have
// been received. If deliverPending is true, ReceivedMessage will be delivered as soon as the first packet
// for the message is received.
bool registerListener(PacketType type, QObject* listener, const char* slot, bool deliverPending = false);
bool registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot);
bool registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false);
bool registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener);
void unregisterListener(QObject* listener);
void handleVerifiedPacket(std::unique_ptr<udt::Packet> packet);
@ -64,9 +81,34 @@ public:
void handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber);
private:
template <class T>
class UnsourcedListenerReference : public ListenerReference {
public:
inline UnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>));
virtual bool invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode);
virtual bool isSourced() const { return false; }
virtual QObject* getObject() const { return _target; }
private:
QPointer<T> _target;
void (T::*_slot)(QSharedPointer<ReceivedMessage>);
};
template <class T>
class SourcedListenerReference : public ListenerReference {
public:
inline SourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>));
virtual bool invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode);
virtual bool isSourced() const { return true; }
virtual QObject* getObject() const { return _target; }
private:
QPointer<T> _target;
void (T::*_slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>);
};
struct Listener {
QPointer<QObject> object;
QMetaMethod method;
ListenerReferencePointer listener;
bool deliverPending;
};
@ -74,11 +116,11 @@ private:
// these are brutal hacks for now - ideally GenericThread / ReceivedPacketProcessor
// should be changed to have a true event loop and be able to handle our QMetaMethod::invoke
void registerDirectListenerForTypes(PacketTypeList types, QObject* listener, const char* slot);
void registerDirectListener(PacketType type, QObject* listener, const char* slot);
void registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener);
void registerDirectListener(PacketType type, const ListenerReferencePointer& listener);
QMetaMethod matchingMethodForListener(PacketType type, QObject* object, const char* slot) const;
void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot, bool deliverPending = false);
bool matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const;
void registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false);
QMutex _packetListenerLock;
QHash<PacketType, Listener> _messageListenerMap;
@ -93,4 +135,42 @@ private:
friend class OctreePacketProcessor;
};
template <class T>
PacketReceiver::ListenerReferencePointer PacketReceiver::makeUnsourcedListenerReference(T* target, void (T::* slot)(QSharedPointer<ReceivedMessage>)) {
return QSharedPointer<UnsourcedListenerReference<T>>::create(target, slot);
}
template <class T>
PacketReceiver::ListenerReferencePointer PacketReceiver::makeSourcedListenerReference(T* target, void (T::* slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>)) {
return QSharedPointer<SourcedListenerReference<T>>::create(target, slot);
}
template <class T>
PacketReceiver::UnsourcedListenerReference<T>::UnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>)) :
_target(target),_slot(slot) {
}
template <class T>
bool PacketReceiver::UnsourcedListenerReference<T>::invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>&) {
if (_target.isNull()) {
return false;
}
(_target->*_slot)(receivedMessagePointer);
return true;
}
template <class T>
PacketReceiver::SourcedListenerReference<T>::SourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>)) :
_target(target),_slot(slot) {
}
template <class T>
bool PacketReceiver::SourcedListenerReference<T>::invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode) {
if (_target.isNull()) {
return false;
}
(_target->*_slot)(receivedMessagePointer, sourceNode);
return true;
}
#endif // hifi_PacketReceiver_h

View file

@ -16,8 +16,9 @@
#include <cmath>
#include <assert.h>
#include <QThread>
#include <QTimer>
#include <QtCore/QMetaMethod>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <SharedUtil.h>
#include <shared/QtHelpers.h>

View file

@ -15,8 +15,14 @@
#include <DependencyManager.h>
#include <StatTracker.h>
#include <QtCore/QDateTime>
#include <QtCore/QThread>
QString ResourceRequest::toHttpDateString(uint64_t msecsSinceEpoch) {
return QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch)
.toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'")
.toLatin1();
}
void ResourceRequest::send() {
if (QThread::currentThread() != thread()) {

View file

@ -90,6 +90,7 @@ public:
void setCacheEnabled(bool value) { _cacheEnabled = value; }
void setByteRange(ByteRange byteRange) { _byteRange = byteRange; }
static QString toHttpDateString(uint64_t msecsSinceEpoch);
public slots:
void send();

View file

@ -64,7 +64,8 @@ void OctreePersistThread::start() {
cleanupOldReplacementBackups();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply");
packetReceiver.registerListener(PacketType::OctreeDataFileReply,
PacketReceiver::makeUnsourcedListenerReference<OctreePersistThread>(this, &OctreePersistThread::handleOctreeDataFileReply));
auto nodeList = DependencyManager::get<NodeList>();
const DomainHandler& domainHandler = nodeList->getDomainHandler();

View file

@ -3,5 +3,5 @@ setup_hifi_library(Multimedia Network Qml Quick WebChannel WebSockets ${PLATFORM
link_hifi_libraries(shared networking gl)
# Required for some low level GL interaction in the OffscreenQMLSurface
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()

View file

@ -260,6 +260,7 @@ void Font::read(QIODevice& in) {
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR));
_texture->setStoredMipFormat(formatMip);
_texture->assignStoredMip(0, image.sizeInBytes(), image.constBits());
_texture->setImportant(true);
}
void Font::setupGPU() {

View file

@ -85,11 +85,25 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
Lock lock(_containerLock);
if (_scriptCache.contains(url) && !forceDownload) {
auto scriptContent = _scriptCache[url];
lock.unlock();
qCDebug(scriptengine) << "Found script in cache:" << url.fileName();
contentAvailable(url.toString(), scriptContent, true, true, STATUS_CACHED);
} else {
auto entry = _scriptCache[url];
if (url.isLocalFile() || url.scheme().isEmpty()) {
auto modifiedTime = QFileInfo(url.toLocalFile()).lastModified();
QString localTime = ResourceRequest::toHttpDateString(modifiedTime.toMSecsSinceEpoch());
QString cachedTime = entry["last-modified"].toString();
if (cachedTime != localTime) {
forceDownload = true;
qCDebug(scriptengine) << "Found script in cache, but local file modified; reloading:" << url.fileName()
<< "(memory:" << cachedTime << "disk:" << localTime << ")";
}
}
if (!forceDownload) {
lock.unlock();
qCDebug(scriptengine) << "Found script in cache:" << url.fileName();
contentAvailable(url.toString(), entry["data"].toString(), true, true, STATUS_CACHED);
return;
}
}
{
auto& scriptRequest = _activeScriptRequests[url];
bool alreadyWaiting = scriptRequest.scriptUsers.size() > 0;
scriptRequest.scriptUsers.push_back(contentAvailable);
@ -140,7 +154,10 @@ void ScriptCache::scriptContentAvailable(int maxRetries) {
_activeScriptRequests.remove(url);
_scriptCache[url] = scriptContent = req->getData();
_scriptCache[url] = {
{ "data", scriptContent = req->getData() },
{ "last-modified", req->property("last-modified") },
};
} else {
auto result = req->getResult();
bool irrecoverable =
@ -177,7 +194,7 @@ void ScriptCache::scriptContentAvailable(int maxRetries) {
allCallbacks = scriptRequest.scriptUsers;
if (_scriptCache.contains(url)) {
scriptContent = _scriptCache[url];
scriptContent = _scriptCache[url]["data"].toString();
}
_activeScriptRequests.remove(url);
qCWarning(scriptengine) << "Error loading script from URL (" << status <<")";

View file

@ -60,7 +60,7 @@ private:
Mutex _containerLock;
QMap<QUrl, ScriptRequest> _activeScriptRequests;
QHash<QUrl, QString> _scriptCache;
QHash<QUrl, QVariantMap> _scriptCache;
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
};

View file

@ -130,7 +130,7 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
// This message was sent by one of our script engines, let's try to see if we can find the source.
// Note that the first entry in the backtrace should be "print" and is somewhat useless to us
AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get();
if (loggerInterface->showSourceDebugging()) {
if (loggerInterface && loggerInterface->showSourceDebugging()) {
QScriptContext* userContext = context;
while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) {
userContext = userContext->parentContext();

View file

@ -17,6 +17,10 @@
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#ifdef Q_OS_UNIX
#include <stdio.h>
#include <unistd.h>
#endif
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
@ -32,6 +36,40 @@ LogHandler& LogHandler::getInstance() {
return staticInstance;
}
LogHandler::LogHandler() {
QString logOptions = qgetenv("VIRCADIA_LOG_OPTIONS").toLower();
#ifdef Q_OS_UNIX
// Enable color by default if we're on Unix, and output is a tty (so we're not being piped into something)
//
// On Windows the situation is more complex, and color is supported or not depending on version and
// registry settings, so for now it's off by default and it's up to the user to do what's required.
if (isatty(fileno(stdout))) {
_useColor = true;
}
#endif
auto optionList = logOptions.split(",");
for (auto option : optionList) {
option = option.trimmed();
if (option == "color") {
_useColor = true;
} else if (option == "nocolor") {
_useColor = false;
} else if (option == "process_id") {
_shouldOutputProcessID = true;
} else if (option == "thread_id") {
_shouldOutputThreadID = true;
} else if (option == "milliseconds") {
_shouldDisplayMilliseconds = true;
} else if (option != "") {
fprintf(stdout, "Unrecognized option in VIRCADIA_LOG_OPTIONS: '%s'\n", option.toUtf8().constData());
}
}
}
const char* stringForLogType(LogMsgType msgType) {
switch (msgType) {
case LogInfo:
@ -51,6 +89,29 @@ const char* stringForLogType(LogMsgType msgType) {
}
}
const char* colorForLogType(LogMsgType msgType) {
switch (msgType) {
case LogInfo:
return "\u001b[37;1m"; // Bold white
case LogDebug:
return "";
case LogWarning:
return "\u001b[35;1m"; // Bright magenta
case LogCritical:
return "\u001b[31;1m"; // Bright red
case LogFatal:
return "\u001b[31;1m"; // Bright red
case LogSuppressed:
return "";
default:
return "";
}
}
const char* colorReset() {
return "\u001b[0m";
}
// the following will produce 11/18 13:55:36
const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";
@ -133,7 +194,15 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
QString logMessage = QString("%1 %2\n").arg(prefixString, message.split('\n').join('\n' + prefixString + " "));
fprintf(stdout, "%s", qPrintable(logMessage));
const char* color = "";
const char* resetColor = "";
if (_useColor) {
color = colorForLogType(type);
resetColor = colorReset();
}
fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor);
#ifdef Q_OS_WIN
// On windows, this will output log lines into the Visual Studio "output" tab
OutputDebugStringA(qPrintable(logMessage));

View file

@ -57,7 +57,7 @@ public:
void setupRepeatedMessageFlusher();
private:
LogHandler() = default;
LogHandler();
~LogHandler() = default;
void flushRepeatedMessages();
@ -66,6 +66,7 @@ private:
bool _shouldOutputProcessID { false };
bool _shouldOutputThreadID { false };
bool _shouldDisplayMilliseconds { false };
bool _useColor { false };
int _currentMessageID { 0 };
struct RepeatedMessageRecord {

View file

@ -3,6 +3,7 @@
// interface/src
//
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -43,6 +44,8 @@ class Camera : public QObject {
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
Q_PROPERTY(QString mode READ getModeString WRITE setModeString NOTIFY modeUpdated)
Q_PROPERTY(QVariantMap frustum READ getViewFrustum CONSTANT)
Q_PROPERTY(bool captureMouse READ getCaptureMouse WRITE setCaptureMouse NOTIFY captureMouseChanged)
Q_PROPERTY(float sensitivity READ getSensitivity WRITE setSensitivity)
public:
Camera();
@ -110,6 +113,37 @@ public slots:
*/
void setOrientation(const glm::quat& orientation);
/**jsdoc
* Gets the current mouse capture state.
* @function Camera.getCaptureMouse
* @returns {boolean} <code>true</code> if the mouse is captured (is invisible and cannot leave the bounds of Interface,
* if Interface is the active window and no menu item is selected), <code>false</code> if the mouse is behaving normally.
*/
bool getCaptureMouse() const { return _captureMouse; }
/**jsdoc
* Sets the mouse capture state. When <code>true</code>, the mouse is invisible and cannot leave the bounds of
* Interface, as long as Interface is the active window and no menu item is selected. When <code>false</code>, the mouse
* behaves normally.
* @function Camera.setCaptureMouse
* @param {boolean} captureMouse - <code>true</code> to capture the mouse, <code>false</code> to release the mouse.
*/
void setCaptureMouse(bool captureMouse) { _captureMouse = captureMouse; emit captureMouseChanged(captureMouse); }
/**jsdoc
* Gets the current camera sensitivity.
* @function Camera.getSensitivity
* @returns {number} The current camera sensitivity. Must be positive.
*/
float getSensitivity() const { return _sensitivity; }
/**jsdoc
* Sets the camera sensitivity. Higher values mean that the camera will be more sensitive to mouse movements.
* @function Camera.setSensitivity
* @param {number} sensitivity - The desired camera sensitivity. Must be positive.
*/
void setSensitivity(float sensitivity) { _sensitivity = glm::max(0.0f, sensitivity); }
/**jsdoc
* Computes a {@link PickRay} based on the current camera configuration and the specified <code>x, y</code> position on the
* screen. The {@link PickRay} can be used in functions such as {@link Entities.findRayIntersection} and
@ -181,6 +215,20 @@ signals:
*/
void modeUpdated(const QString& newMode);
/**jsdoc
* Triggered when the camera mouse capture state changes.
* @function Camera.captureMouseChanged
* @param {boolean} newCaptureMouse - The new mouse capture state.
* @returns {Signal}
* @example <caption>Report mouse capture state changes.</caption>
* function onCaptureMouseChanged(newCaptureMouse) {
* print("The mouse capture has changed to " + newCaptureMouse);
* }
*
* Camera.captureMouseChanged.connect(onCaptureMouseChanged);
*/
void captureMouseChanged(bool newCaptureMouse);
private:
void recompose();
void decompose();
@ -194,6 +242,9 @@ private:
glm::quat _orientation;
bool _isKeepLookingAt{ false };
glm::vec3 _lookingAt;
bool _captureMouse { false };
float _sensitivity { 1.0f };
};
#endif // hifi_Camera_h

View file

@ -4,5 +4,5 @@ link_hifi_libraries(shared networking qml gl audio audio-client plugins pointers
include_hifi_library_headers(controllers)
# Required for some low level GL interaction in the OffscreenQMLSurface
set(OpenGL_GL_PREFERENCE "GLVND")
set(OpenGL_GL_PREFERENCE "LEGACY")
target_opengl()

View file

@ -172,31 +172,31 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* fileOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* fileOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* fileSaveDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* fileSaveDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* existingDirectoryDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* existingDirectoryDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* assetOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* assetOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
// Compatibility with QFileDialog::getOpenFileName
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getOpenFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getOpenFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
// Compatibility with QFileDialog::getSaveFileName
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getSaveFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getSaveFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
// Compatibility with QFileDialog::getExistingDirectory
static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getExistingDirectoryAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getExistingDirectoryAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getOpenAssetNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getOpenAssetNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QVariant inputDialog(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
Q_INVOKABLE ModalDialogListener* inputDialogAsync(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
@ -209,12 +209,12 @@ public:
// Compatibility with QInputDialog::getText
static QString getText(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0,
Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
Qt::WindowFlags flags = Qt::WindowFlags(), Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getText(OffscreenUi::ICON_NONE, title, label, text, ok);
}
// Compatibility with QInputDialog::getItem
static QString getItem(void *ignored, const QString & title, const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = Qt::WindowFlags(),
Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItem(OffscreenUi::ICON_NONE, title, label, items, current, editable, ok);
}
@ -222,12 +222,12 @@ public:
// Compatibility with QInputDialog::getText
static ModalDialogListener* getTextAsync(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0,
Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
Qt::WindowFlags flags = Qt::WindowFlags(), Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getTextAsync(OffscreenUi::ICON_NONE, title, label, text);
}
// Compatibility with QInputDialog::getItem
static ModalDialogListener* getItemAsync(void *ignored, const QString & title, const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = Qt::WindowFlags(),
Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItemAsync(OffscreenUi::ICON_NONE, title, label, items, current, editable);
}

View file

@ -5,10 +5,12 @@
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO
//
// Authored by: Humbletim (humbletim@gmail.com)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Supporting file containing all QtScript specific integration.
// Supporting file containing all QtScript specific integration.
#ifndef EXAMPLE_SCRIPT_PLUGIN_H
#define EXAMPLE_SCRIPT_PLUGIN_H

View file

@ -5,10 +5,12 @@
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO
//
// Authored by: Humbletim (humbletim@gmail.com)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Example of prototyping new JS APIs by leveraging the existing plugin system.
// Example of prototyping new JS APIs by leveraging the existing plugin system.
#include "ExampleScriptPlugin.h"

View file

@ -43,7 +43,7 @@
lazy-validation>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="domainName" :rules="domainNameRules" label="Domain Display Name *" required hint="This is the name that shows on the listing" persistent-hint></v-text-field>
<v-text-field v-model="domainName" :rules="domainNameRules" label="Location Name *" required hint="This is the name that shows on the listing" persistent-hint></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="owner" :rules="ownerRules" label="Owner *" hint="Owner name to show in listing" required persistent-hint></v-text-field>

View file

@ -2,9 +2,7 @@
//
// Created by Darlingnotin in 2019.
// Copyright 2019 Darlingnotin
//
// App maintained in: https://github.com/kasenvr/Decentralized_GoTo_Experimental
// App copied to: https://github.com/kasenvr/project-athena
// Copyright 2020 Vircadia contributors.
//
// Distributed under the ISC license.
// See the accompanying file LICENSE or https://opensource.org/licenses/ISC

View file

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

View file

@ -16,7 +16,7 @@ var FRAME_RATE = 30; // 30 default
function getFrame(callback) {
// A model exported from blender with a texture named 'Picture' on one face.
var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx";
var FRAME_URL = "https://cdn-1.vircadia.com/us-e-1/Developer/Tutorials/pictureFrame/finalFrame.fbx";
var model = ModelCache.prefetch(FRAME_URL);
if (model.state === Resource.State.FINISHED) {

View file

@ -0,0 +1,34 @@
//
// audioFeedback.js
//
// Created by Alezia Kurdis on September 30, 2020.
// Copyright 2020 Vircadia contributors.
//
// This script add audio feedback (confirmation and rejection) for user interactions that require one.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
audioFeedback = (function() {
var that = {};
var confirmationSound = SoundCache.getSound(Script.resolvePath("./sounds/confirmation.mp3"));
var rejectionSound = SoundCache.getSound(Script.resolvePath("./sounds/rejection.mp3"));
that.confirmation = function() { //Play a confirmation sound
var injector = Audio.playSound(confirmationSound, {
"volume": 0.3,
"localOnly": true
});
}
that.rejection = function() { //Play a rejection sound
var injector = Audio.playSound(rejectionSound, {
"volume": 0.3,
"localOnly": true
});
}
return that;
})();

View file

@ -34,7 +34,8 @@ Script.include([
"../libraries/entityIconOverlayManager.js",
"../libraries/gridTool.js",
"entityList/entityList.js",
"entitySelectionTool/entitySelectionTool.js"
"entitySelectionTool/entitySelectionTool.js",
"audioFeedback/audioFeedback.js"
]);
var CreateWindow = Script.require('./modules/createWindow.js');
@ -104,6 +105,8 @@ var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleE
}
});
var hmdMultiSelectMode = false;
var cameraManager = new CameraManager();
var grid = new Grid();
@ -824,7 +827,7 @@ var toolBar = (function () {
HMD.displayModeChanged.connect(function() {
if (isActive) {
tablet.gotoHomeScreen();
tablet.gotoHomeScreen();
}
that.setActive(false);
});
@ -1131,7 +1134,11 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
var entity = entityIconOverlayManager.findEntity(data.overlayID);
if (entity !== null) {
selectionManager.setSelections([entity], this);
if (hmdMultiSelectMode) {
selectionManager.addEntity(entity, true, this);
} else {
selectionManager.setSelections([entity], this);
}
}
}
}
@ -1689,11 +1696,12 @@ function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) {
}
function unparentSelectedEntities() {
if (SelectionManager.hasSelection()) {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections;
var parentCheck = false;
if (selectedEntities.length < 1) {
audioFeedback.rejection();
Window.notifyEditError("You must have an entity selected in order to unparent it.");
return;
}
@ -1706,12 +1714,17 @@ function unparentSelectedEntities() {
return true;
});
if (parentCheck) {
audioFeedback.confirmation();
if (selectedEntities.length > 1) {
Window.notify("Entities unparented");
} else {
Window.notify("Entity unparented");
}
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
if (selectedEntities.length > 1) {
Window.notify("Selected Entities have no parents");
} else {
@ -1719,13 +1732,15 @@ function unparentSelectedEntities() {
}
}
} else {
Window.notifyEditError("You have nothing selected to unparent");
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
function parentSelectedEntities() {
if (SelectionManager.hasSelection()) {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections;
if (selectedEntities.length <= 1) {
audioFeedback.rejection();
Window.notifyEditError("You must have multiple entities selected in order to parent them");
return;
}
@ -1742,16 +1757,22 @@ function parentSelectedEntities() {
});
if (parentCheck) {
audioFeedback.confirmation();
Window.notify("Entities parented");
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
Window.notify("Entities are already parented to last");
}
} else {
Window.notifyEditError("You have nothing selected to parent");
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
function deleteSelectedEntities() {
if (SelectionManager.hasSelection()) {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var deletedIDs = [];
SelectionManager.saveProperties();
@ -1782,6 +1803,9 @@ function deleteSelectedEntities() {
pushCommandForSelections([], savedProperties);
entityListTool.deleteEntities(deletedIDs);
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
@ -2339,6 +2363,15 @@ var PropertiesTool = function (opts) {
};
function updateSelections(selectionUpdated, caller) {
if (HMD.active && visible) {
webView.setLandscape(true);
} else {
if (!visible) {
hmdMultiSelectMode = false;
webView.setLandscape(false);
}
}
if (blockPropertyUpdates) {
return;
}
@ -2939,4 +2972,22 @@ function zoneSortOrder(a, b) {
return 0;
}
function getParentState(id) {
var state = "NONE";
var properties = Entities.getEntityProperties(id, ["parentID"]);
var children = Entities.getChildrenIDs(id);
if (properties.parentID !== Uuid.NULL) {
if (children.length > 0) {
state = "PARENT_CHILDREN";
} else {
state = "CHILDREN";
}
} else {
if (children.length > 0) {
state = "PARENT";
}
}
return state;
}
}()); // END LOCAL_SCOPE

View file

@ -3,6 +3,7 @@
// entityList.js
//
// Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -14,6 +15,7 @@
var PROFILING_ENABLED = false;
var profileIndent = '';
const PROFILE_NOOP = function(_name, fn, args) {
fn.apply(this, args);
};
@ -73,7 +75,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
that.setVisible = function(newVisible) {
visible = newVisible;
webView.setVisible(shouldUseEditTabletApp() && visible);
entityListWindow.setVisible(!shouldUseEditTabletApp() && visible);
entityListWindow.setVisible(!shouldUseEditTabletApp() && visible);
};
that.isVisible = function() {
@ -163,6 +165,15 @@ EntityListTool = function(shouldUseEditTabletApp) {
}
that.sendUpdate = function() {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (HMD.active) {
tablet.setLandscape(true);
}
emitJSONScriptEvent({
"type": "confirmHMDstate",
"isHmd": HMD.active
});
PROFILE('Script-sendUpdate', function() {
var entities = [];
@ -179,7 +190,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
PROFILE("getMultipleProperties", function () {
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['position', 'name', 'type', 'locked',
'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID',
'skybox.url', 'ambientLight.url']);
'skybox.url', 'ambientLight.url', 'created', 'lastEdited']);
for (var i = 0; i < multipleProperties.length; i++) {
var properties = multipleProperties[i];
@ -192,6 +203,17 @@ EntityListTool = function(shouldUseEditTabletApp) {
} else if (properties.type === "Image") {
url = properties.imageURL;
}
var parentStatus = getParentState(ids[i]);
var parentState = "";
if (parentStatus === "PARENT") {
parentState = "A";
} else if (parentStatus === "CHILDREN") {
parentState = "C";
} else if (parentStatus === "PARENT_CHILDREN") {
parentState = "B";
}
entities.push({
id: ids[i],
name: properties.name,
@ -211,7 +233,10 @@ EntityListTool = function(shouldUseEditTabletApp) {
isBaked: entityIsBaked(properties),
drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""),
hasScript: properties.script !== ""
hasScript: properties.script !== "",
parentState: parentState,
created: formatToStringDateTime(properties.created),
lastEdited: formatToStringDateTime(properties.lastEdited)
});
}
}
@ -231,6 +256,22 @@ EntityListTool = function(shouldUseEditTabletApp) {
});
};
function formatToStringDateTime(timestamp) {
var d = new Date(Math.floor(timestamp/1000));
var dateTime = d.getUTCFullYear() + "-" + zeroPad((d.getUTCMonth() + 1), 2) + "-" + zeroPad(d.getUTCDate(), 2);
dateTime = dateTime + " " + zeroPad(d.getUTCHours(), 2) + ":" + zeroPad(d.getUTCMinutes(), 2) + ":" + zeroPad(d.getUTCSeconds(), 2);
dateTime = dateTime + "." + zeroPad(d.getUTCMilliseconds(), 3);
return dateTime;
}
function zeroPad(num, size) {
num = num.toString();
while (num.length < size) {
num = "0" + num;
}
return num;
}
function onFileSaveChanged(filename) {
Window.saveFileChanged.disconnect(onFileSaveChanged);
if (filename !== "") {
@ -302,6 +343,34 @@ EntityListTool = function(shouldUseEditTabletApp) {
SelectionDisplay.toggleSpaceMode();
} else if (data.type === 'keyUpEvent') {
keyUpEventFromUIWindow(data.keyUpEvent);
} else if (data.type === 'undo') {
undoHistory.undo();
} else if (data.type === 'redo') {
undoHistory.redo();
} else if (data.type === 'parent') {
parentSelectedEntities();
} else if (data.type === 'unparent') {
unparentSelectedEntities();
} else if (data.type === 'hmdMultiSelectMode') {
hmdMultiSelectMode = data.value;
} else if (data.type === 'selectAllInBox') {
selectAllEntitiesInCurrentSelectionBox(false);
} else if (data.type === 'selectAllTouchingBox') {
selectAllEntitiesInCurrentSelectionBox(true);
} else if (data.type === 'selectParent') {
SelectionManager.selectParent();
} else if (data.type === 'selectTopParent') {
SelectionManager.selectTopParent();
} else if (data.type === 'addChildrenToSelection') {
SelectionManager.addChildrenToSelection();
} else if (data.type === 'selectFamily') {
SelectionManager.selectFamily();
} else if (data.type === 'selectTopFamily') {
SelectionManager.selectTopFamily();
} else if (data.type === 'teleportToEntity') {
SelectionManager.teleportToEntity();
} else if (data.type === 'moveEntitySelectionToAvatar') {
SelectionManager.moveEntitiesSelectionToAvatar();
}
};

View file

@ -3,6 +3,7 @@
//
// Created by Ryan Huffman on 19 Nov 2014
// Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -30,7 +31,9 @@
<input type="button" id="visible" class="glyph" value="&#xe007;" />
</div>
<button id="toggle-space-mode" class="hifi-edit-button space-mode-local">Local</button>
<input type="button" class="red glyph" id="delete" value="{" />
<input type="button" class="vglyph" id="hmdmultiselect" value="I" style="display: none;" />
<input type="button" class="normal" id="selection" value="Selection..." />
<input type="button" class="normal" id="actions" value="Actions..." />
</div>
<div id="entity-list">
<div id="filter-area">
@ -88,5 +91,142 @@
</div>
</div>
</div>
<div class="entity-list-menu" id="actions-menu" >
<button class="menu-button" id="undo" >
<div class = "menu-item">
<div class = "menu-item-caption">Undo</div>
<div class = "menu-item-shortcut">Ctrl-Z</div>
</div>
</button>
<button class="menu-button" id="redo" >
<div class = "menu-item">
<div class = "menu-item-caption">Redo</div>
<div class = "menu-item-shortcut">Ctrl-Y</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="hmdcut" >
<div class = "menu-item">
<div class = "menu-item-caption">Cut</div>
<div class = "menu-item-shortcut">Ctrl-X</div>
</div>
</button>
<button class="menu-button" id="hmdcopy" >
<div class = "menu-item">
<div class = "menu-item-caption">Copy</div>
<div class = "menu-item-shortcut">Ctrl-C</div>
</div>
</button>
<button class="menu-button" id="hmdpaste" >
<div class = "menu-item">
<div class = "menu-item-caption">Paste</div>
<div class = "menu-item-shortcut">Ctrl-V</div>
</div>
</button>
<button class="menu-button" id="hmdduplicate" >
<div class = "menu-item">
<div class = "menu-item-caption">Duplicate</div>
<div class = "menu-item-shortcut">Ctrl-D</div>
</div>
</button>
<button class="menu-button" id="delete" >
<div class = "menu-item">
<div class = "menu-item-caption">Delete</div>
<div class = "menu-item-shortcut">Del</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="parent" >
<div class = "menu-item">
<div class = "menu-item-caption">Parent Entities to the Last Selected</div>
<div class = "menu-item-shortcut">Ctrl-P</div>
</div>
</button>
<button class="menu-button" id="unparent" >
<div class = "menu-item">
<div class = "menu-item-caption">Unparent Entity</div>
<div class = "menu-item-shortcut">Ctrl-Shift-P</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="moveEntitySelectionToAvatar" >
<div class = "menu-item">
<div class = "menu-item-caption">Move Selected Entities to Avatar</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
</div>
<div class="entity-list-menu" id="selection-menu" >
<button class="menu-button" id="selectall" >
<div class = "menu-item">
<div class = "menu-item-caption">Select All</div>
<div class = "menu-item-shortcut">Ctrl-A</div>
</div>
</button>
<button class="menu-button" id="selectnone" >
<div class = "menu-item">
<div class = "menu-item-caption">Select None</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selectinverse" >
<div class = "menu-item">
<div class = "menu-item-caption">Inverse Selection</div>
<div class = "menu-item-shortcut">Ctrl-I</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="selectallinbox" >
<div class = "menu-item">
<div class = "menu-item-caption">Select All Entities In Box</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selectalltouchingbox" >
<div class = "menu-item">
<div class = "menu-item-caption">Select All Entities Touching Box</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="selectparent" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Parent</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selecttopparent" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Top Parent</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="addchildrentoselection" >
<div class = "menu-item">
<div class = "menu-item-caption">Add Children To Selection</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selectfamily" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Family</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selecttopfamily" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Top Family</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="teleport-to-entity" >
<div class = "menu-item">
<div class = "menu-item-caption">Teleport To Selected Entities</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
</div>
<div id="menuBackgroundOverlay" ></div>
</body>
</html>

View file

@ -2,6 +2,7 @@
//
// Created by Ryan Huffman on 19 Nov 2014
// Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -46,6 +47,14 @@ const COLUMNS = {
alwaysShown: true,
defaultSortOrder: ASCENDING_SORT,
},
parentState: {
columnHeader: "A",
vglyph: true,
dropdownLabel: "Hierarchy",
propertyID: "parentState",
initialWidth: 0.08,
defaultSortOrder: DESCENDING_SORT,
},
name: {
columnHeader: "Name",
propertyID: "name",
@ -133,6 +142,20 @@ const COLUMNS = {
initialWidth: 0.06,
defaultSortOrder: DESCENDING_SORT,
},
created: {
columnHeader: "Created (UTC)",
dropdownLabel: "Creation Date",
propertyID: "created",
initialWidth: 0.38,
defaultSortOrder: DESCENDING_SORT,
},
lastEdited: {
columnHeader: "Modified (UTC)",
dropdownLabel: "Modification Date",
propertyID: "lastEdited",
initialWidth: 0.38,
defaultSortOrder: DESCENDING_SORT,
},
};
const FILTER_TYPES = [
@ -164,6 +187,7 @@ let selectedEntities = [];
let entityList = null; // The ListView
let hmdMultiSelectMode = false;
/**
* @type EntityListContextMenu
*/
@ -198,7 +222,31 @@ let elEntityTable,
elRefresh,
elToggleLocked,
elToggleVisible,
elActionsMenu,
elSelectionMenu,
elMenuBackgroundOverlay,
elHmdMultiSelect,
elHmdCopy,
elHmdCut,
elHmdPaste,
elHmdDuplicate,
elUndo,
elRedo,
elParent,
elUnparent,
elDelete,
elMoveEntitySelectionToAvatar,
elSelectAll,
elSelectInverse,
elSelectNone,
elSelectAllInBox,
elSelectAllTouchingBox,
elSelectParent,
elSelectTopParent,
elAddChildrenToSelection,
elSelectFamily,
elSelectTopFamily,
elTeleportToEntity,
elFilterTypeMultiselectBox,
elFilterTypeText,
elFilterTypeOptions,
@ -233,7 +281,7 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms");
};
function loaded() {
function loaded() {
openEventBridge(function() {
elEntityTable = document.getElementById("entity-table");
elEntityTableHeader = document.getElementById("entity-table-header");
@ -241,8 +289,32 @@ function loaded() {
elEntityTableScroll = document.getElementById("entity-table-scroll");
elRefresh = document.getElementById("refresh");
elToggleLocked = document.getElementById("locked");
elToggleVisible = document.getElementById("visible");
elToggleVisible = document.getElementById("visible");
elHmdMultiSelect = document.getElementById("hmdmultiselect");
elActionsMenu = document.getElementById("actions");
elSelectionMenu = document.getElementById("selection");
elMenuBackgroundOverlay = document.getElementById("menuBackgroundOverlay");
elHmdCopy = document.getElementById("hmdcopy");
elHmdCut = document.getElementById("hmdcut");
elHmdPaste = document.getElementById("hmdpaste");
elHmdDuplicate = document.getElementById("hmdduplicate");
elUndo = document.getElementById("undo");
elRedo = document.getElementById("redo");
elParent = document.getElementById("parent");
elUnparent = document.getElementById("unparent");
elDelete = document.getElementById("delete");
elMoveEntitySelectionToAvatar = document.getElementById("moveEntitySelectionToAvatar");
elSelectAll = document.getElementById("selectall");
elSelectInverse = document.getElementById("selectinverse");
elSelectNone = document.getElementById("selectnone");
elSelectAllInBox = document.getElementById("selectallinbox");
elSelectAllTouchingBox = document.getElementById("selectalltouchingbox");
elSelectParent = document.getElementById("selectparent");
elSelectTopParent = document.getElementById("selecttopparent");
elAddChildrenToSelection = document.getElementById("addchildrentoselection");
elSelectFamily = document.getElementById("selectfamily");
elSelectTopFamily = document.getElementById("selecttopfamily");
elTeleportToEntity = document.getElementById("teleport-to-entity");
elFilterTypeMultiselectBox = document.getElementById("filter-type-multiselect-box");
elFilterTypeText = document.getElementById("filter-type-text");
elFilterTypeOptions = document.getElementById("filter-type-options");
@ -270,8 +342,156 @@ function loaded() {
elExport.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'export'}));
};
elHmdMultiSelect.onclick = function() {
if (hmdMultiSelectMode) {
elHmdMultiSelect.className = "vglyph";
hmdMultiSelectMode = false;
} else {
elHmdMultiSelect.className = "white vglyph";
hmdMultiSelectMode = true;
}
EventBridge.emitWebEvent(JSON.stringify({ type: 'hmdMultiSelectMode', value: hmdMultiSelectMode }));
};
elActionsMenu.onclick = function() {
document.getElementById("menuBackgroundOverlay").style.display = "block";
document.getElementById("actions-menu").style.display = "block";
};
elSelectionMenu.onclick = function() {
document.getElementById("menuBackgroundOverlay").style.display = "block";
document.getElementById("selection-menu").style.display = "block";
};
elMenuBackgroundOverlay.onclick = function() {
closeAllEntityListMenu();
};
elHmdCopy.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' }));
closeAllEntityListMenu();
};
elHmdCut.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' }));
closeAllEntityListMenu();
};
elHmdPaste.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' }));
closeAllEntityListMenu();
};
elHmdDuplicate.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' }));
closeAllEntityListMenu();
};
elParent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' }));
closeAllEntityListMenu();
};
elUnparent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' }));
closeAllEntityListMenu();
};
elUndo.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'undo' }));
closeAllEntityListMenu();
};
elRedo.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'redo' }));
closeAllEntityListMenu();
};
elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
closeAllEntityListMenu();
};
elMoveEntitySelectionToAvatar.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'moveEntitySelectionToAvatar' }));
closeAllEntityListMenu();
};
elSelectAll.onclick = function() {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
});
let selection = [];
if (!selectionIncludesAllVisibleEntityIDs) {
selection = visibleEntityIDs;
}
updateSelectedEntities(selection, false);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": selection
}));
closeAllEntityListMenu();
};
elSelectInverse.onclick = function() {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
});
let selection = [];
if (!selectionIncludesAllVisibleEntityIDs) {
visibleEntityIDs.forEach(function(id) {
if (!selectedEntities.includes(id)) {
selection.push(id);
}
});
}
updateSelectedEntities(selection, false);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": selection
}));
closeAllEntityListMenu();
};
elSelectNone.onclick = function() {
updateSelectedEntities([], false);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": []
}));
closeAllEntityListMenu();
};
elSelectAllInBox.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectAllInBox' }));
closeAllEntityListMenu();
};
elSelectAllTouchingBox.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectAllTouchingBox' }));
closeAllEntityListMenu();
};
elSelectParent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectParent' }));
closeAllEntityListMenu();
};
elSelectTopParent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectTopParent' }));
closeAllEntityListMenu();
};
elAddChildrenToSelection.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'addChildrenToSelection' }));
closeAllEntityListMenu();
};
elSelectFamily.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectFamily' }));
closeAllEntityListMenu();
};
elSelectTopFamily.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectTopFamily' }));
closeAllEntityListMenu();
};
elTeleportToEntity.onclick = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: "teleportToEntity" }));
closeAllEntityListMenu();
};
elToggleSpaceMode.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleSpaceMode' }));
@ -330,9 +550,12 @@ function loaded() {
elTh.setAttribute("id", thID);
elTh.setAttribute("columnIndex", columnIndex);
elTh.setAttribute("columnID", columnID);
if (columnData.glyph) {
if (columnData.glyph || columnData.vglyph) {
let elGlyph = document.createElement("span");
elGlyph.className = "glyph";
if (columnData.vglyph) {
elGlyph.className = "vglyph";
}
elGlyph.innerHTML = columnData.columnHeader;
elTh.appendChild(elGlyph);
} else {
@ -538,7 +761,7 @@ function loaded() {
let selection = [entityID];
let controlKey = window.navigator.platform.startsWith("Mac") ? clickEvent.metaKey : clickEvent.ctrlKey;
if (controlKey) {
if (controlKey || hmdMultiSelectMode) {
let selectedIndex = selectedEntities.indexOf(entityID);
if (selectedIndex >= 0) {
selection = [];
@ -632,10 +855,13 @@ function loaded() {
isBaked: entity.isBaked,
drawCalls: displayIfNonZero(entity.drawCalls),
hasScript: entity.hasScript,
parentState: entity.parentState,
created: entity.created,
lastEdited: entity.lastEdited,
elRow: null, // if this entity has a visible row element assigned to it
selected: false // if this entity is selected for edit regardless of having a visible row
};
entities.push(entityData);
entitiesByID[entityData.id] = entityData;
});
@ -817,7 +1043,7 @@ function loaded() {
function updateSelectedEntities(selectedIDs, autoScroll) {
let notFound = false;
// reset all currently selected entities and their rows first
selectedEntities.forEach(function(id) {
let entity = entitiesByID[id];
@ -828,7 +1054,7 @@ function loaded() {
}
}
});
// then reset selected entities list with newly selected entities and set them selected
selectedEntities = [];
selectedIDs.forEach(function(id) {
@ -891,6 +1117,8 @@ function loaded() {
let elCell = elRow.childNodes[i];
if (column.data.glyph) {
elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null;
} else if (column.data.vglyph) {
elCell.innerHTML = itemData[column.data.propertyID];
} else {
let value = itemData[column.data.propertyID];
if (column.data.format) {
@ -978,6 +1206,9 @@ function loaded() {
let column = columnsByID[columnID];
let visible = column.elTh.style.visibility !== "hidden";
let className = column.data.glyph ? "glyph" : "";
if (column.data.vglyph) {
className = "vglyph";
}
className += visible ? "" : " hidden";
return className;
}
@ -1327,7 +1558,7 @@ function loaded() {
break;
}
if (controlKey && keyCodeString === "A") {
if (controlKey && !shiftKey && !altKey && keyCodeString === "A") {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
@ -1350,6 +1581,32 @@ function loaded() {
return;
}
if (controlKey && !shiftKey && !altKey && keyCodeString === "I") {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
});
let selection = [];
if (!selectionIncludesAllVisibleEntityIDs) {
visibleEntityIDs.forEach(function(id) {
if (!selectedEntities.includes(id)) {
selection.push(id);
}
});
}
updateSelectedEntities(selection);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": selection
}));
return;
}
EventBridge.emitWebEvent(JSON.stringify({
type: 'keyUpEvent',
@ -1364,10 +1621,12 @@ function loaded() {
}
}));
}, false);
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type === "clearEntityList") {
clearEntities();
} else if (data.type === "selectionUpdate") {
@ -1395,6 +1654,12 @@ function loaded() {
removeEntities(data.ids);
} else if (data.type === "setSpaceMode") {
setSpaceMode(data.spaceMode);
} else if (data.type === "confirmHMDstate") {
if (data.isHmd) {
document.getElementById("hmdmultiselect").style.display = "inline";
} else {
document.getElementById("hmdmultiselect").style.display = "none";
}
}
});
}
@ -1419,4 +1684,11 @@ function loaded() {
$(window).blur(function() {
entityListContextMenu.close();
});
function closeAllEntityListMenu() {
document.getElementById("menuBackgroundOverlay").style.display = "none";
document.getElementById("selection-menu").style.display = "none";
document.getElementById("actions-menu").style.display = "none";
}
}

View file

@ -1618,7 +1618,8 @@ const GROUPS = [
type: "vec3",
vec3Type: "pyr",
multiplier: DEGREES_TO_RADIANS,
decimals: 4,
decimals: 6,
step: 1,
subLabels: [ "x", "y", "z" ],
unit: "deg/s",
propertyID: "localAngularVelocity",

View file

@ -19,6 +19,7 @@
const SPACE_LOCAL = "local";
const SPACE_WORLD = "world";
const HIGHLIGHT_LIST_NAME = "editHandleHighlightList";
const MIN_DISTANCE_TO_REZ_FROM_AVATAR = 3;
Script.include([
"../../libraries/controllers.js",
@ -26,7 +27,6 @@ Script.include([
"../../libraries/utils.js"
]);
function deepCopy(v) {
return JSON.parse(JSON.stringify(v));
}
@ -103,7 +103,11 @@ SelectionManager = (function() {
if (wantDebug) {
print("setting selection to " + messageParsed.entityID);
}
that.setSelections([messageParsed.entityID], that);
if (hmdMultiSelectMode) {
that.addEntity(messageParsed.entityID, true, that);
} else {
that.setSelections([messageParsed.entityID], that);
}
}
} else if (messageParsed.method === "clearSelection") {
if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {
@ -314,6 +318,7 @@ SelectionManager = (function() {
that.addChildrenEntities(originalEntityID, entitiesToDuplicate, entityHostTypes[i].entityHostType);
}
var duplicateInterrupted = false;
// duplicate entities from above and store their original to new entity mappings and children needing re-parenting
for (var i = 0; i < entitiesToDuplicate.length; i++) {
var originalEntityID = entitiesToDuplicate[i];
@ -360,6 +365,8 @@ SelectionManager = (function() {
duplicatedChildrenWithOldParents[newEntityID] = properties.parentID;
}
originalEntityToNewEntityID[originalEntityID] = newEntityID;
} else {
duplicateInterrupted = true;
}
}
@ -378,6 +385,11 @@ SelectionManager = (function() {
}
});
if (duplicateInterrupted) {
audioFeedback.rejection();
} else {
audioFeedback.confirmation();
}
return duplicatedEntityIDs;
};
@ -624,6 +636,141 @@ SelectionManager = (function() {
}
};
that.teleportToEntity = function() {
if (that.hasSelection()) {
var distanceFromTarget = MIN_DISTANCE_TO_REZ_FROM_AVATAR + Math.max(Math.max(that.worldDimensions.x, that.worldDimensions.y), that.worldDimensions.z);
var teleportTargetPosition = Vec3.sum(that.worldPosition, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: distanceFromTarget }));
MyAvatar.goToLocation(teleportTargetPosition, false);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
that.moveEntitiesSelectionToAvatar = function() {
if (that.hasSelection() && that.hasUnlockedSelection()) {
that.saveProperties();
var distanceFromTarget = MIN_DISTANCE_TO_REZ_FROM_AVATAR + Math.max(Math.max(that.worldDimensions.x, that.worldDimensions.y), that.worldDimensions.z);
var targetPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -distanceFromTarget }));
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in that.selections
var toMove = that.selections.filter(function (selection) {
if (that.selections.indexOf(that.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var id = toMove[i];
var properties = that.savedProperties[id];
var relativePosition = Vec3.subtract(properties.position, that.worldPosition);
var newPosition = Vec3.sum(relativePosition, targetPosition);
Entities.editEntity(id, { "position": newPosition });
}
that._update(false, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
};
that.selectParent = function() {
if (that.hasSelection()) {
var currentSelection = that.selections;
that.selections = [];
for (var i = 0; i < currentSelection.length; i++) {
var properties = Entities.getEntityProperties(currentSelection[i], ['parentID']);
if (properties.parentID !== Uuid.NULL) {
that.selections.push(properties.parentID);
}
}
that._update(true, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
that.selectTopParent = function() {
if (that.hasSelection()) {
var currentSelection = that.selections;
that.selections = [];
for (var i = 0; i < currentSelection.length; i++) {
var topParentId = getTopParent(currentSelection[i]);
if (topParentId !== Uuid.NULL) {
that.selections.push(topParentId);
}
}
that._update(true, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
function getTopParent(id) {
var topParentId = Uuid.NULL;
var properties = Entities.getEntityProperties(id, ['parentID']);
if (properties.parentID === Uuid.NULL) {
topParentId = id;
} else {
topParentId = getTopParent(properties.parentID);
}
return topParentId;
}
that.addChildrenToSelection = function() {
if (that.hasSelection()) {
for (var i = 0; i < that.selections.length; i++) {
var childrenIDs = Entities.getChildrenIDs(that.selections[i]);
var collectNewChildren;
var j;
var k = 0;
do {
collectNewChildren = Entities.getChildrenIDs(childrenIDs[k]);
if (collectNewChildren.length > 0) {
for (j = 0; j < collectNewChildren.length; j++) {
childrenIDs.push(collectNewChildren[j]);
}
}
k++;
} while (k < childrenIDs.length);
if (childrenIDs.length > 0) {
for (j = 0; j < childrenIDs.length; j++) {
that.selections.push(childrenIDs[j]);
}
}
}
that._update(true, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
that.hasUnlockedSelection = function() {
var selectionNotLocked = true;
for (var i = 0; i < that.selections.length; i++) {
var properties = Entities.getEntityProperties(that.selections[i], ['locked']);
if (properties.locked) {
selectionNotLocked = false;
break;
}
}
return selectionNotLocked;
};
that.selectFamily = function() {
that.selectParent();
that.addChildrenToSelection();
};
that.selectTopFamily = function() {
that.selectTopParent();
that.addChildrenToSelection();
};
return that;
})();
@ -648,8 +795,10 @@ SelectionDisplay = (function() {
const COLOR_HOVER = { red: 255, green: 220, blue: 82 };
const COLOR_DUPLICATOR = { red: 162, green: 0, blue: 255 };
const COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 };
const COLOR_BOUNDING_EDGE = { red: 128, green: 128, blue: 128 };
const COLOR_SCALE_CUBE = { red: 160, green: 160, blue: 160 };
const COLOR_BOUNDING_EDGE = { red: 160, green: 160, blue: 160 };
const COLOR_BOUNDING_EDGE_PARENT = { red: 194, green: 123, blue: 0 };
const COLOR_BOUNDING_EDGE_CHILDREN = { red: 0, green: 168, blue: 214 };
const COLOR_SCALE_CUBE = { red: 192, green: 192, blue: 192 };
const COLOR_DEBUG_PICK_PLANE = { red: 255, green: 255, blue: 255 };
const COLOR_DEBUG_PICK_PLANE_HIT = { red: 255, green: 165, blue: 0 };
@ -1779,6 +1928,18 @@ SelectionDisplay = (function() {
var rotationZ = Quat.multiply(rotation, localRotationZ);
worldRotationZ = rotationZ;
var handleBoundingBoxColor = COLOR_BOUNDING_EDGE;
if (SelectionManager.selections.length === 1) {
var parentState = getParentState(SelectionManager.selections[0]);
if (parentState === "CHILDREN") {
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_CHILDREN;
} else {
if (parentState === "PARENT" || parentState === "PARENT_CHILDREN") {
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_PARENT;
}
}
}
var selectionBoxGeometry = {
position: position,
rotation: rotation,
@ -1900,6 +2061,7 @@ SelectionDisplay = (function() {
Entities.editEntity(handleBoundingBox, {
position: position,
rotation: rotation,
color: handleBoundingBoxColor,
dimensions: dimensions
});

View file

@ -55,18 +55,18 @@ TabBar {
font.pixelSize: 14
font.bold: true
anchors.top: parent.top
anchors.topMargin: 28
anchors.topMargin: 30
anchors.left: parent.left
anchors.leftMargin: 28
anchors.leftMargin: 30
}
Flow {
id: createEntitiesFlow
spacing: 35
spacing: 20
anchors.right: parent.right
anchors.rightMargin: 55
anchors.rightMargin: 30
anchors.left: parent.left
anchors.leftMargin: 55
anchors.leftMargin: 30
anchors.top: parent.top
anchors.topMargin: 70
@ -186,9 +186,9 @@ TabBar {
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors.right: parent.right
anchors.rightMargin: 55
anchors.rightMargin: 30
anchors.left: parent.left
anchors.leftMargin: 55
anchors.leftMargin: 30
anchors.top: createEntitiesFlow.bottom
anchors.topMargin: 35
onClicked: {
@ -205,9 +205,9 @@ TabBar {
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors.right: parent.right
anchors.rightMargin: 55
anchors.rightMargin: 30
anchors.left: parent.left
anchors.leftMargin: 55
anchors.leftMargin: 30
anchors.top: assetServerButton.bottom
anchors.topMargin: 20
onClicked: {

View file

@ -55,7 +55,7 @@ Rectangle {
Text {
id: text1
text: qsTr("Material URL (Optional)")
text: qsTr("Material URL <i>(Optional)</i>")
color: "#ffffff"
font.pixelSize: 12
}

View file

@ -55,7 +55,7 @@ Rectangle {
Text {
id: text1
text: qsTr("Model URL")
text: qsTr("Model URL <i>(.fbx, .fst, .glb, .gltf, .obj, .gz)</i>")
color: "#ffffff"
font.pixelSize: 12
}

View file

@ -65,6 +65,14 @@
url(../fonts/hifi-glyphs.ttf);
}
@font-face {
font-family: Vircadia-Glyphs;
src: url(../../../../resources/fonts/vircadia_glyphs.ttf),
url(../../../../fonts/vircadia_glyphs.ttf),
url(../../../../interface/resources/fonts/vircadia_glyphs.ttf),
url(../fonts/vircadia_glyphs.ttf);
}
* {
margin: 0;
padding: 0;
@ -407,6 +415,21 @@ input[type=button].glyph, button.hifi-edit-button.glyph {
padding: 0;
}
input[type=button].normal, button.hifi-edit-button.normal {
font-family: FiraSans-SemiBold;
font-size: 15px;
text-transform: none;
padding: 0;
}
input[type=button].vglyph, button.hifi-edit-button.vglyph {
font-family: Vircadia-Glyphs;
font-size: 20px;
text-transform: none;
min-width: 32px;
padding: 0;
}
input[type=button].red, button.hifi-edit-button.red {
color: #fff;
background-color: #94132e;
@ -417,6 +440,16 @@ input[type=button].blue, button.hifi-edit-button.blue {
background-color: #1080b8;
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
}
input[type=button].orange, button.hifi-edit-button.orange {
color: #fff;
background-color: #8f5100;
background: linear-gradient(#d97b00 20%, #8f5100 100%);
}
input[type=button].green, button.hifi-edit-button.green {
color: #fff;
background-color: #078a00;
background: linear-gradient(#00cc07 20%, #078a00 100%);
}
input[type=button].white, button.hifi-edit-button.white {
color: #121212;
background-color: #afafaf;
@ -435,6 +468,14 @@ input[type=button].blue:enabled:hover, button.hifi-edit-button.blue:enabled:hove
background: linear-gradient(#00b4ef, #00b4ef);
border: none;
}
input[type=button].orange:enabled:hover, button.hifi-edit-button.orange:enabled:hover {
background: linear-gradient(#d97b00, #d97b00);
border: none;
}
input[type=button].green:enabled:hover, button.hifi-edit-button.green:enabled:hover {
background: linear-gradient(#00cc07, #00cc07);
border: none;
}
input[type=button].white:enabled:hover, button.hifi-edit-button.white:enabled:hover {
background: linear-gradient(#fff, #fff);
border: none;
@ -449,6 +490,12 @@ input[type=button].red:active, button.hifi-edit-button.red:active {
input[type=button].blue:active, button.hifi-edit-button.blue:active {
background: linear-gradient(#1080b8, #1080b8);
}
input[type=button].orange:active, button.hifi-edit-button.orange:active {
background: linear-gradient(#8f5100, #8f5100);
}
input[type=button].green:active, button.hifi-edit-button.green:active {
background: linear-gradient(#078a00, #078a00);
}
input[type=button].white:active, button.hifi-edit-button.white:active {
background: linear-gradient(#afafaf, #afafaf);
}
@ -1196,7 +1243,6 @@ textarea:enabled[scrolling="true"]::-webkit-resizer {
background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right;
}
div#grid-section, body#entity-list-body {
padding-bottom: 0;
margin: 16px;
@ -1227,12 +1273,6 @@ div#grid-section, body#entity-list-body {
border-bottom-left-radius: 0;
}
#delete {
float: right;
margin-right: 0;
background-color: #ff0000;
}
#entity-list {
position: relative; /* New positioning context. */
}
@ -1408,6 +1448,11 @@ input[type=button]#export {
font-size: 15px;
}
#entity-table-scroll .vglyph {
font-family: Vircadia-Glyphs;
font-size: 15px;
}
#entity-table {
margin-top: -28px;
margin-bottom: -18px;
@ -1420,7 +1465,7 @@ input[type=button]#export {
background: none;
}
#entity-table .glyph {
#entity-table .glyph .vglyph {
margin: 0 -2px 0 -2px;
vertical-align: middle;
}
@ -1453,11 +1498,11 @@ input[type=button]#export {
outline: none;
}
#entity-table th .glyph {
#entity-table th .glyph .vglyph {
position: relative;
left: 4px;
}
#entity-table th .glyph + .sort-order {
#entity-table th .glyph .vglyph + .sort-order {
position: relative;
left: 4px;
}
@ -1484,7 +1529,7 @@ input[type=button]#export {
#entity-table td {
box-sizing: border-box;
}
#entity-table td.glyph {
#entity-table td .glyph .vglyph {
text-align: center;
padding: 0;
}
@ -1820,3 +1865,71 @@ div.multiZoneSelToolbar {
padding: 0px;
}
#menuBackgroundOverlay{
background-color:transparent;
position:fixed;
width: 100%;
height: 100%;
top:0;
left:0;
right:0;
bottom:0;
display:none;
}
div.entity-list-menu {
position: fixed;
display: none;
width: 70%;
height: 30px;
top: 42px;
left: 150px;
right: 0;
bottom: 0;
border-style: solid;
border-color: #505050;
border-width: 1px;
background-color: #c0c0c0;
z-index: 2;
cursor: pointer;
}
div.menu-separator{
width: 90%;
height: 2px;
background-color: #505050;
}
button.menu-button {
font-family: FiraSans-SemiBold;
font-size: 15px;
width: 100%;
height: auto;
border-radius: 0;
padding: 6px;
text-align: left;
background-color: #c0c0c0;
border: none;
}
button.menu-button:hover {
background-color: #00B4EF;
border: none;
}
button.menu-button:active {
background-color: #00B4EF;
border: none;
}
div.menu-item {
width: 100%;
}
div.menu-item-caption {
float: left;
}
div.menu-item-shortcut {
float: right;
}

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